Wunderlandmedia

The WordPress to Astro Migration Problems Nobody Warns You About

The Astro migration guide is 400 words. Here is the rest: redirects, ACF, page builders, forms, comments, and what to do about each.

Kemal Esensoy·Modified on September 23, 2026

The WordPress to Astro Migration Problems Nobody Warns You About
Case Studies

The official Astro guide for moving off WordPress is about 400 words long. It suggests two options, points you at one export tool, and ends with a sentence admitting that "you may now have to build many of your existing features yourself."

That's the whole guide. No section on redirects. Nothing about comments. Nothing about forms. Nothing about custom fields.

I've migrated three client sites from WordPress to Astro. The posts and pages were never the hard part. The hard part was everything the plugins were doing quietly in the background, and the several hundred URLs that existed on the old site that nobody had a list of.

I already wrote the optimistic version of this story, where Claude flattened a pile of builder markup into clean components and almost nothing broke. That post is true. This one is the other half of it: the list of wordpress to astro migration problems the guides skip, and what I actually do about each one.

This assumes you've already decided to leave. If you're still weighing that up, I wrote about whether WordPress is still the right call in 2026 and about what the performance and cost numbers actually look like. Neither of those is this post.

The Export Tool Gives You Blog Posts, Not a Website

wordpress-export-to-markdown is the tool the Astro docs recommend, and it's genuinely good at what it does. Feed it your WordPress XML export and it writes one Markdown file per post with frontmatter, downloads the attached images, rewrites the image references, and handles drafts, pages and custom post types as separate files.

Here's the scope of it, though. It reads the post_content field. That's the field Gutenberg and the classic editor write to. Anything that isn't in that field does not exist as far as the export is concerned.

What isn't in that field: every custom field value, every ACF group, every taxonomy term that isn't a category or tag, your menu structure, your widget areas, your form definitions, your comment threads, your permalink settings, and any content a plugin stores in its own database table. That's not a bug in the tool. It's a WordPress XML export limitation that the tool inherits.

So run the export first, absolutely. Then treat the output as one input among several, not as "the migration."

Page Builder Pages Have to Be Rendered, Not Exported

Open a Divi or WPBakery page in the database and you'll see post_content full of shortcodes: [vc_row][vc_column][vc_column_text]. Run that through a Markdown converter and you get the shortcodes as literal text. Elementor is worse in a different way. It doesn't put its layout in post_content at all. It stores the whole page as JSON in postmeta and leaves post_content nearly empty, which means a clean XML export of an Elementor page can come out blank.

There is no reliable automatic converter here, and I'd be suspicious of anyone selling you one. What works is the boring approach: render the page in a browser, take the rendered HTML, and convert that. A heading is an h2 in the DOM even when it's a shortcode in the database.

My actual process is to crawl the live site and save the rendered HTML of every URL before I touch anything. Then I work from that. For blog posts I convert the rendered article body to Markdown. For builder-made landing pages I don't convert at all, I rebuild them as Astro components using the rendered HTML as the reference. Three sites in, I've stopped trying to automate the landing pages. Five or ten marketing pages rebuilt by hand is a day of work. Fighting an Elementor JSON parser is a week and the result is worse.

ACF Is Schema, Not Content

This is the one that catches people who mostly migrate blogs. A blog is a flat list of posts, so it maps onto Astro content collections almost one to one. A site with Advanced Custom Fields is not flat.

Custom fields and custom post types flattened into flat content files

Repeaters and flexible content layouts are the difficult ones. A flexible content field is essentially a per-page list of arbitrary blocks with arbitrary shapes, stored in postmeta as a set of keys like sections_0_hero_title and sections_1_gallery_images_0_image. Reconstructing that from an XML export is possible but miserable.

I stopped doing it from the export. Instead I pull the structured data out over the WordPress REST API, with ACF exposed via show_in_rest, and write it as JSON or YAML into a content collection with a Zod schema. You get typed data on the Astro side, which is a genuine upgrade, and Astro will fail your build loudly when a field is missing rather than rendering an empty div.

Custom post types and custom taxonomies need a decision each, not a conversion. A team_member CPT with six entries becomes a data collection. A case_study CPT with 80 entries and its own archive becomes a real content collection with its own routes, and now you own the archive pagination too. Custom taxonomies are the sneaky ones, because every term generated an indexed archive URL on the old site, and those URLs are about to 404.

Of all the WordPress to Astro migration problems on this list, this is the one most likely to double your estimate. Count your CPTs and taxonomies before you scope the job. On one of the three sites, the "simple brochure site" turned out to have four custom post types nobody had mentioned.

The Redirect Map Is Most of the Work

If you only take one thing from this post: the redirect map is the migration. Everything else is rendering.

A 301 redirect map drawn as strings between old and new URLs

WordPress generates far more indexed URLs than the page count suggests. The list I work through every time:

  • Permalink structure. If the old site used /2019/03/post-title/ and your Astro site uses /post-title/, every single post URL changes.
  • /?p=123 style URLs. Every post is reachable at its ID-based URL forever, and those get shared, bookmarked and linked to.
  • Attachment pages. WordPress creates a page for every uploaded media file. These get indexed, they rank for image queries, and there can be thousands of them. WordPress 6.4 added an option to redirect them to the file itself, which tells you how much of a problem they've been.
  • Category, tag and custom taxonomy archives, plus author archives and date archives.
  • Paginated archives: /blog/page/2/, /category/news/page/3/, on and on.
  • Feeds: /feed/, /comments/feed/, per-category feeds. Real subscribers live on these.
  • sitemap_index.xml and whatever child sitemaps your SEO plugin generated.
  • Trailing slashes. WordPress ends URLs with a slash by default. Astro's trailingSlash setting and your host's own behaviour both get a vote, and they don't always agree.

Get the real list from three sources and merge them: a crawl of the live site, Search Console's Pages report for anything that ever received an impression, and your server access logs for the last 90 days. The crawl misses orphaned URLs. Search Console misses things with zero impressions that still get direct traffic. Logs catch both.

Then there's a technical detail that surprised me. Astro's built-in redirects config, on a fully static build with no adapter, does not emit a 301. It writes an HTML page containing a <meta http-equiv="refresh"> tag, and the docs say plainly that it "does not support status codes." Google will usually follow a meta refresh, but it is not a permanent redirect and it is not what you want for a few hundred URLs carrying rankings.

So do redirects at the edge, not in the framework. On Cloudflare Pages that's a _redirects file, and mind the ceiling: 2,000 static redirects and 100 dynamic ones, 2,100 combined, with anything beyond that needing Bulk Redirects. Two thousand sounds enormous until you have 1,400 attachment pages. That's where wildcard rules earn their keep: one dynamic rule for /wp-content/uploads/* beats 1,400 static lines.

I keep the whole thing in a CSV in the repo, generate the _redirects file from it at build time, and test it against the crawl list after deploy. I go through the same discipline in my website migration SEO checklist, which covers the search side of this in more depth than I will here.

Forms: Ask Where the Submissions Go, Then Ask Where the Old Ones Are

Two separate problems, and people usually only think about the first.

Form submissions with nowhere to go after leaving WordPress

The new site needs somewhere for form posts to land, because a static build has no PHP to receive them. Depending on the host that's a form endpoint service, a serverless function, or your host's native form handling. Fine, that's a known job.

The part that gets forgotten is the archive. Gravity Forms stores every entry in the WordPress database, so there may be years of leads sitting in Forms > Import/Export that nobody has looked at since 2021. Export them to CSV before the database goes away. WPForms is the same story.

Contact Form 7 is the opposite trap: it stores nothing by default. Unless someone installed Flamingo or a CFDB add-on, every submission that site ever received exists only in whatever inbox got the notification email. If the client asks for their old enquiries after you've decommissioned the server, there is nothing to give them. Check which case you're in while the site is still up.

Also worth checking: what the form was wired into. Notification recipients, Mailchimp or CRM integrations, conditional logic, hidden fields carrying UTM data, spam protection. The visible form is five fields. The plumbing behind it is usually more than that.

Comments: Decide Before You Export

Pull the comment count first, then decide. I've had all three answers on three sites.

If it's near zero, drop them. Nobody will notice, and you save yourself an integration.

If there's a real archive and it has value, migrating to Giscus is the path I'd take now. It's backed by GitHub Discussions, and the community migration scripts follow the same pattern: create one discussion per post using the post URL as the title, then post each old comment into it attributed to the original author with the original date in a header. The comments are no longer editable by their authors and they're all posted by your token's account, which is a real downgrade. It's still better than deleting them.

If in doubt, export the comments to JSON and archive it even if you never display them. Showing them is a decision you can make later. Deleting the database is not.

One thing to remember either way: comment pagination and #comment-1234 anchors were URLs too.

Search, Related Posts, and the Other Silent Plugins

WordPress had a search box. It worked because there was a database and PHP behind it. On a static build, that's gone.

Pagefind is the standard answer and it's good: it indexes your built HTML after the build, ships the index with the site, and runs the query in the browser, downloading only the chunks it needs rather than the whole index. There's an Astro integration for it. Budget an hour, not a day.

Related posts is the one that catches people. On WordPress it was a plugin doing a taxonomy query at request time. On Astro you write the logic yourself, usually shared-tag matching at build time. Not hard, but nobody puts it in the scope document because nobody remembers it was a plugin.

The full list of things I now check before quoting, because each one was a plugin quietly doing a job:

  • Search
  • Related posts
  • Breadcrumbs
  • Schema markup that the SEO plugin was generating
  • The XML sitemap
  • Redirects already stored in an SEO plugin's redirect manager, which need to be merged into the new map, not lost
  • Canonical tags and meta from the SEO plugin
  • Cookie consent
  • Analytics injection
  • Social share buttons
  • Anything on a cron schedule

The redirect manager one is worth saying twice. If the site has been around a while, there is probably a stack of old redirects living inside Yoast or Rank Math from previous URL changes. Those still carry link equity. Export them and fold them into your new map.

Media URLs and the Assets You Don't Control

WordPress writes uploads to /wp-content/uploads/YYYY/MM/filename.jpg and generates several resized copies of each one: thumbnail, medium, medium_large, large, plus whatever sizes your theme registered. Those resized files have their own URLs, they appear in srcset attributes, and they get indexed.

If your Astro site puts images somewhere sensible like /images/, every one of those old URLs breaks. This matters more than it sounds, because other people's sites hotlink to them, Google Images has them indexed, and old newsletters point at them.

What I do now: keep the /wp-content/uploads/ path structure on the new site for existing media. It looks silly in a repo with no WordPress in it. It also means a directory of URLs that people already link to keeps resolving, with zero redirect rules spent. New images go in the new structure. The old ones stay where they are.

Then crawl for hotlinked assets in the other direction: PDFs linked from client emails, images embedded in third-party articles, files that were never referenced by any page but are being downloaded daily. The access logs will tell you. Nothing else will.

The Checklist

Before you start:

  1. Crawl the live site and save the rendered HTML of every URL.
  2. Export URL lists from the crawl, Search Console, and 90 days of access logs. Merge them.
  3. Count custom post types, custom taxonomies, and ACF field groups.
  4. Export form entries from every form plugin to CSV.
  5. Export comments to JSON, whatever you plan to do with them.
  6. Export any redirects already stored in the SEO plugin.
  7. List every plugin and write down what it does for the front end.

During:

  1. Convert posts from the export tool, landing pages from rendered HTML.
  2. Pull ACF and custom data via the REST API into typed content collections.
  3. Build the redirect map as a CSV in the repo, generate the host's redirect file from it.
  4. Keep /wp-content/uploads/ intact for existing media.
  5. Replace search, related posts, sitemap and schema explicitly.
  6. Decide on trailing slashes once, and check that your host agrees.

After:

  1. Test every URL from the merged list against the live new site. Every one, not a sample.
  2. Watch Search Console's coverage report for four weeks.
  3. Don't decommission the old server for at least 30 days.

Want the printable version? Download the complete checklist as a PDF — 9 pages, 70+ items, no login required.

What I Still Get Wrong

Most of the WordPress to Astro migration problems above have a known fix. The bit I keep underestimating is not technical. It's the discovery. Every site I've migrated had something on it nobody mentioned: a custom post type, a landing page from an old campaign that still gets traffic, a PDF linked from a printed brochure. The tooling for the conversion is fine now. The tooling for finding out what actually exists is a crawl, some logs, and a slow afternoon.

So the honest advice is to spend the first day inventorying and not writing any code. It feels like stalling. It isn't. If you want a sanity check on how deep a specific migration goes before you commit to it, get in touch and I'll tell you what I'd look at first.

Find these posts useful? Mark Wunderlandmedia as a preferred source on Google — my articles will then show up more often in your Search results, AI Overviews and AI Mode.

Set as preferred source

About the Author

KE

Kemal Esensoy

Kemal Esensoy, founder of Wunderlandmedia, started his journey as a freelance web developer and designer. He conducted web design courses with over 3,000 students. Today, he leads an award-winning full-stack agency specializing in web development, SEO, and digital marketing.

WordPress to Astro Migration Problems | Wunderlandmedia