Most of the risk in this migration has nothing to do with the code. Rebuilding pages in React is the predictable part. What goes wrong is URLs, and the damage doesn't show up until two weeks after launch when organic traffic is down forty percent and nobody can say exactly which pages were lost.

This is the sequence that avoids that.


Before anything: decide whether you should

A migration is justified when the site is becoming an application, when plugin debt has made changes genuinely risky, or when performance is measurably costing you conversions.

It is not justified by "our site feels slow." That usually has a cheaper answer — caching, image optimisation, removing four plugins — and we've told clients exactly that. A rebuild is a big spend to fix something a week of optimisation would have handled.

Also settle the editing question now, not later. If your team publishes weekly and the new site needs a developer for every change, you've made the business slower. Budget a headless CMS into the build, or seriously consider headless WordPress — keep WordPress as the editor, render the front end in Next.js. It solves the actual complaint with much less risk.


Step 1: Inventory every URL that exists

Before writing any code. You need the complete list of what currently resolves, because anything you don't know about is something you'll silently drop.

Pull from four sources and combine them — none is complete alone:

  1. Your existing sitemap.xml — what you think you have
  2. Google Search Console → Pages — what Google has actually indexed, including URLs you forgot
  3. Server access logs — what's genuinely being requested, including old URLs still receiving links
  4. A crawl (Screaming Frog free tier, or any crawler) — what's reachable by following links

The gaps between these lists are the interesting part. Search Console routinely surfaces indexed URLs missing from the sitemap: old category archives, paginated pages, attachment pages, feed URLs, tag pages nobody remembers creating.

Sort the combined list by traffic and inbound links. The top 10% of URLs typically carry the overwhelming majority of value, and those are the ones that must not break.


Step 2: Build the redirect map first

Write the map before building pages. It forces the URL structure decision early, when it's cheap.

Old URLNew URLNotes
/2019/03/old-post-title//blog/old-post-title/Dropping date prefixes
/services/web-design//services/web-development/Consolidating
/category/news//blog/Archive collapse
/?p=412/blog/actual-slug/Legacy ID links

Rules that matter:

  • 301, not 302. Permanent redirects pass ranking signals; temporary ones don't.
  • One hop. Redirect chains dilute signals and slow crawling. Map old directly to final, never through an intermediate.
  • Never bulk-redirect to the homepage. Google treats an irrelevant redirect as a soft 404 and drops the page entirely. If there's no equivalent, either keep a real page or let it 404 honestly.
  • Preserve trailing-slash form. If WordPress served /about/ and Next.js serves /about, that's a redirect on every page. Set trailingSlash: true and match the old form.

In Next.js:

typescript
// next.config.ts
const nextConfig = {
  trailingSlash: true,
  async redirects() {
    return [
      { source: "/2019/03/old-post-title", destination: "/blog/old-post-title/", permanent: true },
    ];
  },
};

For hundreds of redirects, generate this array from a CSV at build time rather than hand-maintaining it.


Step 3: Extract the content

The WordPress REST API is the cleanest route — /wp-json/wp/v2/posts?per_page=100 — paginated, giving you titles, slugs, dates, content HTML, and media references as JSON.

The parts that take longer than expected:

Media. Download every asset and rewrite every reference. Inline <img> tags in post content point at /wp-content/uploads/... paths that won't exist. Rewrite them during conversion, and keep the old paths working via redirects if images have accumulated inbound links.

Shortcodes. [gallery], [contact-form-7], and every plugin's custom shortcode are meaningless outside WordPress. Find them all before you start — grep -o '\[[a-z_-]*' across exported content — and decide for each: build a React equivalent, or convert to static markup.

Content HTML. Post bodies contain markup from whichever editor produced them, sometimes across three eras of the same site. Converting to markdown is cleaner long-term but lossy for complex layouts. Storing the HTML and rendering it is faster and uglier. Pick deliberately.

Metadata. Yoast or RankMath store titles, descriptions, and canonicals in post meta. Extract these — regenerating them from scratch means losing years of deliberate tuning.


Step 4: Verify before you cut over

On a staging deploy, check:

  • Every redirect resolves in one hop to a 200. Script this against your full URL inventory. It's the single highest-value check in the migration.
  • Content is in the server-rendered HTML. curl a page and grep for body text. If it's only in a client-side fetch, it isn't reliably indexable.
  • Canonicals, sitemap, and internal links agree on URL form.
  • The sitemap contains only live, indexable pages — no redirects, no 404s.
  • Structured data parses. Validate every JSON-LD block.

Step 5: Launch, then watch closely

Launch mid-week, not Friday. You want people available.

Immediately after:

  1. Submit the new sitemap in Search Console.
  2. Use the URL Inspection tool on your top 20 pages and request indexing.
  3. Keep the old server reachable for a while if the redirects run there.

Then watch these for a month:

  • Coverage report — a spike in 404s means redirects you missed. This is the main thing you're watching for, and it's fixable if caught early.
  • Crawl stats — a sustained drop suggests crawlers hitting errors.
  • Top pages by clicks — compare against pre-migration. Individual pages losing traffic points at specific redirect problems.

Expect a dip. A two-to-four week wobble while Google reprocesses is normal even on a clean migration. What isn't normal is a decline that doesn't recover by week six — that means URLs are genuinely lost, and you should go back to the 404 report.


What actually goes wrong

In rough order of frequency:

  1. URLs nobody knew existed. Solved entirely by Step 1.
  2. Redirect chains from partial mapping, diluting signals.
  3. Trailing slash mismatch doubling every URL.
  4. Client-side-only content that crawlers don't see.
  5. Images 404ing because paths changed and nobody checked.
  6. Lost metadata, regenerated generically and worse than before.
  7. A CMS that never got built, so the site freezes after launch.

Every one is preventable, and every one is cheaper to prevent than to diagnose afterwards.


Planning a migration?

We handle WordPress to Next.js migrations including the URL inventory, redirect mapping, and post-launch monitoring — the parts that determine whether it looks like a success or a mistake. Talk to us about web development.