Migrating a WordPress site to Squilla: what worked, what hurt

A first-person retrospective on moving a real client blog off WordPress.

The moment I hit "go" on the cutover, I had two terminal windows open, a coffee that had gone cold an hour earlier, and a very specific kind of stomach feeling. The DNS TTL was already low. The new Squilla instance was warm. The old WordPress site was about to stop being the source of truth for a client blog that had been running for nearly a decade. I clicked the script. Then I watched logs scroll for forty minutes.

This is the honest write-up of that migration. One client. A mid-sized WordPress site: roughly 500 posts, 4 custom post types, a few hundred media items, ten years of accumulated plugin sediment. I wanted a real test of Squilla, not a toy one, and a willing client is the best test there is.

What worked

The export pipeline was the part I was most worried about, and it turned out to be the easiest. WP-CLI dumps clean JSON if you ask it nicely. I piped wp post list --post_type=any --format=json through a small Tengo script that mapped WordPress fields to Squilla node fields. Title, slug, content, excerpt, dates, author, taxonomies, featured image references. The mapping was almost one to one. Custom post types became custom node types via core.nodetype.create calls emitted by the same script. About 80 lines of Tengo did the heavy lifting.

Media was the second pleasant surprise. I batched the WordPress uploads directory through core.media.import_url in groups of 50. The endpoint streams the fetch, stores the file, and returns the new media object. I kept a small lookup table mapping old WordPress attachment IDs to new Squilla media IDs, and the content rewriter used it to fix up <img src> URLs as posts were imported. A few hundred items moved in under ten minutes.

Redirects basically wrote themselves. Because the old slugs were preserved and the new site uses the same /post/<slug> shape for blog posts, almost every URL stayed identical. For the ones that did change (the custom post types had different parent paths on the old site), I emitted a CSV of old_path,new_path pairs during the import and fed it into a tiny redirects extension I had built the week before. Zero broken bookmarks. Search Console barely noticed.

What hurt

Shortcode-heavy posts were the first knife in the gut. The client had years of [gallery], [caption], [embed], and a half dozen plugin shortcodes scattered through the body content. My Tengo script handled the common WordPress core shortcodes by converting them to native Squilla blocks, but the plugin shortcodes were stringly typed mush. I ended up doing manual conversion on about 30 posts. Coffee, headphones, a long evening.

Two plugins had custom database tables with no Squilla equivalent. One was a small events plugin storing event dates and locations. The other was a testimonial widget. Neither was a real "extension" on the WordPress side, they were just a couple of tables and some shortcodes, but the data mattered. I wrote two thin Squilla extensions, each owning one table, each with a tiny admin page. About 4 hours of work for both combined. Not painful, but not free.

The SEO meta migration was the worst part by far. Yoast stores its data in nested serialized PHP structures inside wp_postmeta, and parsing serialized PHP from a non-PHP environment is exactly as fun as it sounds. I wrote a small parser, fed it through, mapped Yoast keys to Squilla's seo_settings shape, and prayed. Most fields came over. A handful of edge cases (focus keywords with unusual characters, custom social card overrides) needed manual fixes.

The numbers

Final tally, written down so I do not forget:

  • 6 hours of scripting across two evenings to build the migration pipeline.
  • 40 minutes of actual migration runtime on cutover night.
  • 12 manual fixes the following morning, mostly shortcode debris and one image that refused to import.

The site has been live on Squilla for six weeks now. Page load is meaningfully faster, the admin is meaningfully calmer, and the client has stopped asking me what a "plugin update" is. I count that as a win.

Honest verdict

A clean WordPress site migrates well. A ten-year-old WordPress site does not. The difference is not the WordPress core data. The difference is everything that has been bolted onto WordPress over those ten years: page builders that stored layout as base64-encoded JSON inside post content, SEO plugins with their own serialization formats, custom field plugins with their own custom field plugins, shortcodes that depended on PHP code that no longer exists in the install. If you are thinking about migrating, the first thing to do is audit the plugin list, not the post count.

WordPress is a 21-year-old codebase carrying everyone's plugin history. Migration is hard not because of WP, but because of WP's plugins.

← all posts