Skip to content
techpotions
astro · wordpress · migration · SEONovember 4, 202613 min read

Migrating WordPress to Astro: What Survives the Move

What you keep, what you rebuild, and the plumbing that decides whether traffic survives the move.

Cover illustration for “Migrating WordPress to Astro: What Survives the Move”

To migrate WordPress to Astro, hold two things fixed — your URLs and your content — and rebuild everything else. The failures in this migration are almost never framework failures. They're permalinks that changed shape, images that 404'd, a contact form that quietly stopped sending, and a redirect plugin whose rules nobody exported before the database got archived.

That framing is boring and correct. Treat the move as an infrastructure project with a frontend attached — which is roughly how teams that do this repeatedly describe it — and you skip the traffic drop.

What you keep and what you rebuild

Content, URLs, and SEO metadata survive. Themes, plugins, and the admin UI don't.

Asset

Fate

What actually happens

Posts, pages, custom post types

Keep

Exported to Markdown/MDX, or left in WordPress behind an API

URL structure

Keep

Mapped 1:1; anything that changes shape gets a 301

Titles, meta descriptions, canonicals

Keep

Re-implemented in Astro layouts and head components

Media files

Keep

Copied out of wp-content/uploads, references rewritten

Theme, templates, template hierarchy

Rebuild

Astro layouts, components, and getStaticPaths

Plugins (forms, search, related posts, SEO)

Rebuild

Each becomes a component, an endpoint, or a hosted service

Comments

Usually dropped

Static read-only archive, or a third-party embed

Admin UI and editor

Rebuild or replace

Git-based editing, a headless CMS, or headless WordPress

The right-hand column is where the estimate goes wrong. Teams budget for templates and discover the work is in the last three rows.

Decide first: headless WordPress or content out

Pick this before you touch a single post, because it decides whether you're building a frontend or a whole publishing stack.

Astro supports both paths. Astro's official WordPress migration guide covers running WordPress as a headless CMS for the new project, and studios routinely offer either keeping WordPress headless or moving the content out for good — the two are different projects with different bills.

Headless WordPress

Content out

Editor keeps

The WordPress UI they know

A new CMS or a Git workflow

You still maintain

PHP, plugins, WordPress security updates, hosting

Astro build only

Content lives in

MySQL

Markdown in the repo, or a headless CMS

Reversible

Yes, trivially

No

Good fit

Large editorial team, heavy ACF usage, migration in stages

Teams whose only reason for WordPress was familiarity

Our take: if you're keeping WordPress only because nobody wants to learn a new editor, headless WordPress is a halfway house where you pay hosting and maintenance twice — once for PHP and once for the static build. Move the content out and put a real editing surface in front of the repo. If your editorial team is large, publishes daily, and depends on custom fields and workflow roles, keep a CMS — but choose a headless one rather than treating wp-json as one.

If you're still weighing Astro against a heavier React framework for the site you're moving to, read our breakdown of choosing between Next.js and Astro by how much interactivity you actually have before you commit.

Export and model your content

Export programmatically, model the schema before you write templates, and store the original permalink in frontmatter.

Two export paths, and they're not equivalent:

  • WXR (Tools → Export) preserves authors, dates, categories, tags, custom fields, and post relationships in one file. Best when fidelity matters and you want a snapshot you can diff against later.
  • The REST API is re-runnable, which matters because you will run it more than once. Every reconcile between the first export and launch day is a re-run, not a re-export.

A scripted pull that walks every page:

JavaScript
// scripts/pull-wordpress.mjs
const BASE = 'https://old-site.com/wp-json/wp/v2';

async function pullAll(type, params = {}) {
  const out = [];
  for (let page = 1; ; page++) {
    const url = new URL(`${BASE}/${type}`);
    url.search = new URLSearchParams({
      per_page: '100',
      page: String(page),
      _embed: '1', // pulls featured media and terms into the same response
      ...params,
    });
    const res = await fetch(url);
    if (res.status === 400) break; // WordPress returns 400 past the last page
    if (!res.ok) throw new Error(`${res.status} ${url}`);
    out.push(...await res.json());
  }
  return out;
}

const posts = await pullAll('posts');
console.log(posts.length, 'posts');

Then convert content.rendered from HTML to Markdown, strip Gutenberg block comments (<!-- wp:paragraph -->), remove the srcset/sizes attributes WordPress injects, and rewrite upload URLs (more on that below).

Model it as a content collection rather than loose files — the schema is what stops a broken date or a missing description from reaching production:

TypeScript
// src/content.config.ts (Astro 5 Content Layer)
import { defineCollection, z } from 'astro:content';
import { glob } from 'astro/loaders';

const blog = defineCollection({
  loader: glob({ pattern: '**/*.md', base: './src/content/blog' }),
  schema: z.object({
    title: z.string(),
    description: z.string(),
    pubDate: z.coerce.date(),
    updated: z.coerce.date().optional(),
    author: z.string(),
    categories: z.array(z.string()).default([]),
    legacyUrl: z.string().optional(), // original WordPress permalink
    draft: z.boolean().default(false),
  }),
});

export const collections = { blog };

That legacyUrl field is the highest-leverage decision in the whole migration. Store the original permalink on every entry and you can generate the redirect map from content instead of maintaining a spreadsheet that drifts out of sync the moment someone renames a slug.

How to migrate WordPress to Astro without breaking URLs

Every URL that changes shape needs a 301 in the same deploy that changes it — not the next one.

Inventory first. Crawl the live site's sitemap plus its internal links to get the complete list of indexed URLs, then crawl the Astro preview build and diff the two sets. The diff is your redirect list.

What changes shape most often:

  • Permalink structure — /2021/03/slug/ becomes /blog/slug/
  • Trailing slashes, if trailingSlash doesn't match the old convention
  • Pagination — /page/2/ on archives
  • Category, tag, and author archives
  • Attachment pages (?attachment_id=123)
  • Search pages (?s=query) — usually blocked in robots.txt, but check
  • /feed/ and any legacy RSS paths
  • Rules from a redirect plugin (Redirection, Yoast Premium) that have been quietly accumulating for years

That last one bites hardest. Export the plugin's rules before you decommission anything, because those redirects are invisible in the frontend and load-bearing in Search Console.

JavaScript
// astro.config.mjs
export default defineConfig({
  site: 'https://example.com',
  trailingSlash: 'ignore',
  redirects: {
    '/2021/03/old-post-slug': '/blog/old-post-slug',
    '/category/engineering': '/blog',
  },
});

One caveat that matters more than it looks: on a static build, entries in redirects are emitted as HTML pages with a meta refresh, not real 301s. For the SEO-critical moves — anything with links and rankings behind it — put the rules at the host or edge instead, in public/_redirects (Netlify, Cloudflare Pages) or vercel.json. Reserve the config-level redirects for convenience cases.

Also: no chains. If A→B and B→C both exist, collapse to A→C before you ship.

Media: keep the paths, then optimize

Copy wp-content/uploads wholesale and preserve the folder structure — the cheapest redirect is the one you never need.

WordPress organises uploads by year and month. Mirroring that structure under public/uploads/2021/03/ means existing image URLs keep working with a single find-and-replace from https://example.com/wp-content/uploads/ to /uploads/. Nothing 404s, no new redirect rules, no broken embeds in old posts that nobody will ever revisit to fix.

Two destinations, two tradeoffs:

  • public/uploads/ — identical paths, zero rewriting, no build-time optimisation. Start here.
  • src/assets/ — hashed filenames through astro:assets and <Image />, real optimisation, but every reference in every post body now needs rewriting, and external links to those images break.

The pragmatic sequence: land everything in public/ to get to launch with intact URLs, then deliberately move the handful of images that actually carry page weight into the asset pipeline.

Watch for two things in the converted Markdown. WordPress references resized variants (photo-1024x768.jpg), not the original — either keep those files or rewrite the reference to the full-size image. And srcset/sizes attributes are baked into the exported HTML; strip them, because Astro will generate its own.

The features plugins were quietly running

Every plugin is a feature you now own.

  • Comments. Most migrations drop them, and that's usually right. If you have a genuine archive or compliance reason to keep them, export the threads to static JSON and render them read-only, or embed a hosted service. Don't pretend a static site has native comments.
  • Forms. Contact Form 7 doesn't come with you, and neither does its honeypot. Rebuild against a form endpoint — an Astro API route on an SSR adapter, or a hosted form backend — and re-test spam protection before launch, not after.
  • Search. Static sites don't have a query engine. Pagefind indexes the built HTML output and runs client-side with no server, which covers most content sites. Sites with faceted search need hosted search.
  • Shortcodes. The sharp edge. Every [gallery], [caption], and [embed] in a post body needs a mapping to Markdown or an MDX component. Inventory them before you start, not while you're halfway through the archive:
Shell
grep -rhoE '\[[a-z_]+[^]]*\]' src/content/blog | sort | uniq -c | sort -rn
  • Sitemap and feed. @astrojs/sitemap covers the first. @astrojs/rss covers the second — and if your old feed lived at /feed/, serve the new one at that path rather than letting subscribers silently drop.

The editor experience after WordPress

The editor is what your content team will judge the migration on, and it's the part engineers under-specify.

Option

What the editor sees

What you maintain

Best for

Markdown in Git

A pull request

Nothing extra

Developer-led blogs

Git-based CMS (Decap and similar)

Forms over the repo

Auth plus build hooks

Small editorial teams

Headless CMS (Sanity, Storyblok, Contentful)

A polished editing UI

Content schema and preview wiring

Non-technical teams publishing often

Headless WordPress

The UI they already know

PHP, plugins, hosting

Teams not ready to leave

Two gaps to close explicitly, because WordPress gave them to you for free and nobody lists them in the requirements:

  1. Preview. WordPress has a Preview button. In a static setup that becomes a deploy preview per branch or per draft, which is a real integration, not a toggle. Budget for it.
  2. Images. Drag-and-drop into a media library is muscle memory. Whatever replaces it must handle binary uploads — a Git-based CMS has to commit them or push them to an asset store, and that decision affects your build times.

SEO parity checklist

Run this before DNS flips, and again a week after.

  • [ ] Every previously indexed URL returns 200 at its new address, or 301s to it
  • [ ] No redirect chains, and no 302s standing in for 301s
  • [ ] <title>, meta description, and canonical tags match or are deliberately improved
  • [ ] Open Graph and Twitter card tags on every template
  • [ ] JSON-LD for Article, BreadcrumbList, and Organization
  • [ ] XML sitemap generated, and the old sitemap path redirected or replaced
  • [ ] robots.txt carries over — and no Disallow leaked from the staging build
  • [ ] RSS feed preserved at its old path
  • [ ] A real 404 page with navigation
  • [ ] Trailing-slash convention consistent across canonicals, sitemap, and internal links
  • [ ] hreflang pairs intact if the site is multilingual
  • [ ] Internal links rewritten from absolute WordPress URLs to relative Astro routes
  • [ ] Analytics and Search Console verification re-installed — they lived in the WordPress theme header and vanish with it

That second-to-last row is worth dwelling on. Internal links to https://example.com/2021/03/slug/ still work if you redirect, but every one of them is an extra hop. Rewrite them in the content export and the redirect map only has to catch external traffic.

The broader point about preserving rankings through the move holds: rankings move because URLs and content moved, not because the renderer changed.

The phased launch that protects traffic

Build on a preview domain while WordPress stays live. Flip in stages, not overnight.

  1. Inventory. Crawl the site, list every URL, every active plugin, every shortcode, and every redirect rule. This is the document the rest of the project is measured against.
  2. Build in parallel. Astro site on a preview domain, noindex on preview only — verify the production build doesn't inherit it.
  3. Content freeze and final sync. Put WordPress into read-only mode, re-run the export script, reconcile. This window should be hours, not weeks.
  4. Ship redirects with the routes. Same commit. The redirect rules and the new URL structure are one atomic change, which is exactly how a preview-first migration approach keeps the old site serving while the new one is assembled.
  5. DNS flip. Lower the TTL a day ahead. Keep WordPress running read-only at a staging host through the rollback window — you will almost certainly not need it, and you'll want it at 11pm.
  6. Post-launch watch. Search Console coverage errors, 404 logs, and a weekly crawl diff for the first month. Most migration damage surfaces in week two, not hour two.

Doing the whole thing on one cutover night is how you get rankings that drop for reasons nobody can attribute — the same-URLs-and-redirects discipline that migration services advertise as "no SEO loss" is really just this checklist, executed in order, without skipping step 4.

If you want the plumbing handled — content export, URL mapping, redirects, media, and the parity audit — that's what our WordPress migration service is for.

FAQ

Can I keep WordPress as a headless CMS for my Astro site?

Yes. WordPress exposes its content over the REST API at /wp-json/wp/v2/, and Astro's official WordPress migration guide walks through consuming it in a new Astro project. You fetch at build time for static output, or per request with an SSR adapter. The tradeoff is that you keep maintaining PHP, plugins, and WordPress security updates — you've added a frontend, not removed a backend.

Will migrating to Astro hurt my rankings?

Not if your URLs map and the redirects ship with the routes. The risk in this migration lives in the plumbing — changed permalinks, dropped redirect plugin rules, missing canonicals, a sitemap that moved — not in the rendering framework. Which is why the checklist above runs before DNS flips and again a week after.

Do I have to rebuild every page by hand?

No. Templates and layouts are rebuilt once, and the content comes across programmatically through the REST API or a WXR export. The manual work is concentrated in three places: shortcodes that need component equivalents, media references that need rewriting, and the plugin features you decide to keep rather than drop.

Written by
techpotions
All entries
6 WordPress Migration Plugins and Where Each One Breaks
The weekly

One email a week, from the workshop.

What we published, what we shipped, and the free packs as they land. No drip sequence, no webinar, unsubscribe in one click.