Three years ago I took a client's blog — 400 posts, 60k monthly organic visits, eight years of accumulated PageRank — and migrated it to a shiny Next.js front-end. Within six weeks we'd lost 34% of that traffic. Not because the new site was slow. Not because the content disappeared. Because I was sloppy with four specific things that I'll walk you through in this post so you don't repeat my mistake.
Headless WordPress with Next.js is genuinely brilliant for performance and developer experience. But Google doesn't care about your Lighthouse score if your canonical tags are wrong, your XML sitemap is pointing at the old domain, and your structured data evaporated somewhere between WPGraphQL andgetStaticProps. The migration itself is the dangerous part. Getting it right is mostly about discipline, not magic.
---
Why This Migration Breaks SEO in the First Place
Here's the thing most tutorials gloss over: WordPress does an enormous amount of SEO heavy lifting for you without you realising it. Yoast or Rank Math is generating your meta tags. WordPress core is handling your permalink structure. Your theme is probably outputtingsomekind of schema markup. Your XML sitemap auto-regenerates every time you publish.
When you pull the content layer into Next.js via theWPGraphQLAPI and serve it from a new front-end, all of that infrastructure becomesyour problemto replicate. Every. Single. Piece.
The other issue is URL structure. Most WordPress sites have/category/post-slug/or/year/month/post-slug/or just/post-slug/. Next.js gives you a blank canvas for routing. That blank canvas is a ranking graveyard if you don't plan it carefully.
The Two Failure Modes I See Constantly
The first is teams that migrate the URLs too and still break things — usually because redirects get applied inconsistently or the new sitemap goes live before the redirects do. The second is teams that intentionally change the URL structure (often to clean it up) and treat the redirect mapping as an afterthought. Both are fixable. Neither is acceptable.
---
Audit Before You Touch Anything
Do not write a single line of Next.js code before you have a complete URL inventory. I useScreaming Frog— crawl the live WordPress site, export every indexable URL, and dump it into a spreadsheet. For a 400-page site that's maybe an hour of work. For a 4,000-page site it's still just an hour, because the tool does it automatically.
What you're capturing:
- Every canonical URL currently indexed
- The HTTP status of each (identify 404s and 301s that already exist)
- The meta title and description for every page
- Which pages have structured data (use the Rich Results Test or just inspect the source)
- Inbound internal links — so you know which pages link to which
Also pull your top 50 pages from Google Search Console sorted by clicks. These are the ones you cannot afford to get wrong. Flag them in the spreadsheet. Treat them like production dependencies.
Seahawk had an e-commerce client in late 2022 — 1,200-product WooCommerce store moving to a headless setup. We spent two full days on the audit before writing code. The client thought we were wasting time. We saved their 90k monthly organic sessions.
---
Setting Up WordPress as a True Headless CMS
This part is mostly straightforward. Install WPGraphQL and expose your content via the GraphQL API. But there are a few things worth being deliberate about.
Keep Yoast (or Rank Math) Running on the WordPress Side
Even though you're no longer serving WordPress as the public front-end, keep your SEO plugin active.WPGraphQL for Yoast SEO(or the equivalent Rank Math extension) exposes all the SEO meta — titles, descriptions, canonical URLs, OG data, robots directives — directly through the GraphQL API. That means you can query it from Next.js and render it exactly as Yoast intended.
This was the lesson from that 2019 traffic drop I mentioned. I'd assumed we could re-generate titles from the post title + site name in Next.js. We could. But Yoast had manually customised meta titles for about 80 of the top-performing posts, and we wiped all of that out. Eight weeks to recover.
Disable the WordPress Front-End Carefully
Once you're ready to point traffic at Next.js, you do NOT want WordPress serving its own front-end simultaneously. Duplicate content at scale. The cleanest way is to set arobots.txton your WordPress install toDisallow: /while your Next.js site goes live, then eventually firewall the WordPress URL entirely so it's only accessible internally or via VPN.
Don't skip the robots.txt step. I've seen teams block WordPress at the CDN level and then discover that Googlebot had a cached route in. Takes months to clean up.
---
Replicating the URL Structure Exactly
My strong default recommendation:keep your URLs identical. Same slug, same permalink structure, same trailing slash behaviour. The closer the Next.js routes mirror the WordPress routes, the fewer redirects you need, and the less risk you carry.
Next.jsdynamic routesmake this easy. If your WordPress posts live at/blog/[slug], createpages/blog/[slug].js. Done.
Where it gets messy is category archives, author pages, tag pages, and paginated archives (/blog/page/2/). WordPress generates all of these automatically. In Next.js you're building them yourself. A lot of teams deprioritise these and then wonder why crawl coverage dropped.
Here's my numbered checklist for URL parity:
- Single posts/pages— match the slug exactly, including any subfolder
- Category archives— recreate
/category/[slug]/withgetStaticPathspulling all categories from WPGraphQL - Tag archives— same as above, don't skip these if they get organic traffic
- Author archives— check Search Console first; if they get zero clicks, you can 301 them to the homepage
- Paginated archives—
/blog/page/[num]/is worth preserving if you have a lot of posts - Attachment pages— almost always 301 these to the parent post; they're SEO dead weight in WordPress too
- Feed URLs—
/feed/should 301 to your new RSS feed if you have one, or return 410 if not
---
Redirects: The Part Everyone Underestimates
If youarechanging any URLs — which I'd push back on, but sometimes it's necessary — your redirect map needs to be built before launch and tested in a staging environment.
In Next.js, redirects live innext.config.js. For small sites (under 200 redirects) that's fine. For anything larger, put them in a JSON file and import it, or use middleware to handle them dynamically.Vercel's edge middlewareis excellent for large redirect tables because it runs before the page is rendered — zero latency penalty.
The format innext.config.js:
``redirects: [ { source: '/old-slug', destination: '/new-slug', permanent: true } ]``
permanent: truesends a 301. Use it for all genuine URL changes. Don't use 302 (temporary) unless you actually intend to revert it — Google treats them very differently.
Test every redirect before launch. I use a simple bash script that loops through the spreadsheet and curls each old URL checking for a 301 response to the correct destination. Takes ten minutes to write, saves hours of post-launch panic.
---
Meta Tags, Canonical URLs, and Structured Data in Next.js
This is where most migrations lose points silently. The content is there, the URLs work, but the SEO signals are wrong.
Meta Tags
Usenext-seo. It's the standard. Pass it the data you've queried from WPGraphQL Yoast. Your_app.jsgets aDefaultSeoconfig, and each page gets aNextSeocomponent with the page-specific overrides. Take the title, description, OG title, OG image, canonical URL, and robots directives directly from the Yoast GraphQL response — don't reinvent them.
One thing that bites people: canonical URLs. In WordPress, Yoast sets canonicals automatically. In Next.js you need to pass the canonical explicitly. If you forget, Next.js will render pages without a canonical tag, and if you have query strings anywhere (pagination, filters), you'll end up with duplicate content issues faster than you'd expect.
Structured Data
WordPress themes and plugins often output JSON-LD automatically. That disappears in headless. You need to rebuild it. For articles, use theArticleschema. For products,Product. For local businesses,LocalBusiness. I write these as React components that accept props and return a<script type="application/ld+json">tag. One component per schema type, reused across the app.
Check every schema type you previously had in the Rich Results Testbeforemigration. Document them. Recreate them. Test the new ones with the same tool post-launch.
The XML Sitemap
Don't use a static sitemap. Generate it dynamically. For small sites,getServerSidePropson a/sitemap.xmlroute works. For large sites with thousands of posts, generate the sitemap at build time via a custom script and output it to thepublic/folder. Vercel runs this at every deployment — your sitemap is always current.
Submit the new sitemap URL to Google Search Console on day one of the new site going live. Not day three. Day one.
---
Post-Launch Monitoring (The 90-Day Window)
The migration doesn't end at launch. It ends when your rankings have stabilised — which Google's documentation suggests can take anywhere from a few weeks to a few months depending on crawl budget and site authority.
What I watch every weekday for the first month:
- Google Search Console → Coverage reportfor new 404s or 'Excluded' URLs that shouldn't be excluded
- Search Console → Performance— compare clicks and impressions week-on-week for your top 50 pages
- Screaming Frog re-crawlof the new site to catch any internal 404s or misconfigured canonical tags
- Core Web Vitals— yes, the Next.js site should be faster, but verify it in the field data (CrUX), not just Lighthouse
If you see a significant drop in the first two to three weeks, don't panic immediately. There's almost always a short-term fluctuation as Google re-crawls and re-indexes. What you're looking for is sustained drops past week four. That's the signal that something structural is wrong.
Back in late 2022 — different project from the e-commerce one — we launched a Next.js migration for a SaaS blog and saw a 20% impressions drop in week two. Turned out our dynamically generated sitemap was includingnoindexpages because we hadn't filtered the WPGraphQL query properly. Fixed it in four hours. Rankings recovered in three weeks. The monitoring caught it before it compounded.
---
FAQ
How long does a WordPress to Next.js migration take?
Honestly, it depends on site complexity more than post count. A 100-page brochure site with clean URLs can be done properly in two to three weeks. A 2,000-post blog with custom post types, ACF fields, and WooCommerce integration is a six to eight week project minimum if you're doing the SEO work properly alongside the development. Don't let anyone tell you it's a weekend job.
Should I use the Pages Router or App Router in Next.js?
As of mid-2024 I'm defaulting to the App Router for new projects. But if your team is more comfortable with the Pages Router and this is a time-sensitive migration, use what you know. The SEO implications are minimal — both support static generation, server-side rendering, and dynamic routes. Thenext-seopackage has App Router support now too.
Do I need to move away from WordPress hosting entirely?
No. WordPress can stay on its existing host — WP Engine, Kinsta, Cloudways, whatever you're using — and act purely as the content API. The Next.js front-end deploys to Vercel or Netlify. The two communicate via HTTP. Some clients actually prefer this because the editorial team keeps the WordPress admin they already know.
What about WordPress plugins that affect SEO — like redirects managed in Redirection?
Export them before you migrate. TheRedirection pluginhas a CSV export. Take all those existing redirects and add them to yournext.config.jsor edge middleware. Don't assume they'll carry over automatically — they won't, because they live in the WordPress database and Next.js has no idea they exist.
Will my Google ranking drop no matter what?
There's almost always some short-term volatility. A well-executed migration with zero URL changes, proper redirects (where needed), replicated meta and structured data, and a re-submitted sitemap should stabilise within four to six weeks. The drops I've seen that lasted months were all caused by specific technical errors — not by the migration itself.
---
The migration is not the hard part. The hard part is the discipline to do every boring, unglamorous step — the audit, the redirect mapping, the schema recreation — before you write any clever Next.js code. Get that order right and you'll come out the other side with a faster front-end and the same rankings you started with. Possibly better ones, once the Core Web Vitals improvements feed through.
