WordPress to Next.js migration SEO: keep your traffic through the move

By Nayan Kyada · · 7 min read

Part of The Sanity + Next.js Guide

If you are planning a WordPress to Next.js migration and your main fear is losing the organic traffic you spent years earning, that fear is reasonable but manageable. Rankings do not depend on the CMS. They depend on URLs, content, metadata, internal links, and speed, and a migration is safe when all of those are either identical or deliberately redirected.

This post covers the five things that decide whether traffic holds: permalink parity, Yoast metadata transfer, image URLs, redirects, and an 8-week measurement plan. For the content-moving mechanics, see my WordPress to Sanity + Next.js technical walkthrough. For the broader redesign checklist, see website redesign without losing SEO.

Symptoms of a migration that is about to lose traffic

These are the warning signs I look for in a plan or a staging site:

  • The new site uses cleaner URLs than the old one, and nobody has a mapping file.
  • Page titles on staging are just the post title, because the Yoast fields were never migrated.
  • Images are re-uploaded under new filenames and the old /wp-content/uploads URLs will 404.
  • Staging still has a noindex header or a robots.txt that blocks everything, and nobody has a launch checklist item to remove it.
  • Nobody has taken a baseline from Google Search Console.

Any one of these can cost weeks of traffic. Together they can cost months.

What it costs to ignore this

When old URLs return 404 or redirect to the homepage, Google treats them as gone and drops them. The pages that earned links and rankings lose both. Recovery is possible, but it is slow, and the traffic you lose during that gap is not coming back. For a business that gets leads from organic search, that is a direct revenue hole, and fixing it after launch costs more than doing it before.

Permalink parity: keep the URLs identical where you can

The safest migration is the one where almost no URL changes. WordPress permalinks vary: /blog/post-name/, /2022/05/post-name/, /category/post-name/. Decide first, then build to match.

My rule: keep the exact path for every page that ranks or has backlinks. Only change a URL when there is a real reason, and then redirect it.

Start by exporting every URL from three sources: the WordPress sitemap, Search Console's pages report, and a crawl. Merge them into one list. That list is your source of truth. Pay attention to trailing slashes. WordPress usually ends URLs with a slash, and Next.js by default redirects to the version without one. You can match the old behaviour with the trailingSlash option in next.config.ts.

// next.config.ts
import type { NextConfig } from 'next'
 
const nextConfig: NextConfig = {
  trailingSlash: true,
}
 
export default nextConfig

In Sanity, store the full legacy path rather than just a slug, so date-based or nested permalinks survive. A simple approach is a slug field plus a read-only legacyPath string used for redirect generation and verification.

Yoast metadata transfer

Yoast stores its data as post meta, and it does not move with a standard content export unless you pull it deliberately. The fields that matter are the SEO title, meta description, canonical URL, robots directives, and Open Graph or social overrides. Pull them through the WordPress REST API or a database query, then map them into a dedicated seo object on each Sanity document.

Two gotchas I see often. First, Yoast titles use template variables, so the stored value may be a placeholder rather than the final text. Resolve them to real strings during export, or compare against the rendered title tags from a crawl. Second, many posts have no custom title at all and rely on the site default. Reproduce that fallback in code so those pages keep the same title pattern.

// app/blog/[slug]/page.tsx
import type { Metadata } from 'next'
import { sanityFetch } from '@/sanity/lib/live'
import { postSeoQuery } from '@/sanity/lib/queries'
 
export async function generateMetadata({
  params,
}: {
  params: Promise<{ slug: string }>
}): Promise<Metadata> {
  const { slug } = await params
  const { data: post } = await sanityFetch({ query: postSeoQuery, params: { slug } })
 
  const title = post?.seo?.title ?? `${post?.title} | Example Co`
  return {
    title,
    description: post?.seo?.description,
    alternates: { canonical: post?.seo?.canonical ?? `/blog/${slug}/` },
    robots: post?.seo?.noindex ? { index: false } : undefined,
  }
}

After import, crawl the old site and the staging site and diff titles, descriptions, canonicals, and H1s URL by URL. This diff is the single most useful QA step in the project.

Image URLs and alt text

Images bring in traffic from Google Images and carry alt text that helps page relevance. Old links to /wp-content/uploads/ files also exist in other sites' content. Two options work: redirect the old upload paths to the new image URLs, or keep serving a small set of high-traffic originals from the same path. Whichever you pick, migrate alt text with each image, because it is easy to drop.

Do not leave images hotlinked to the old WordPress host. When that server is shut down, every image breaks at once. I cover the delivery side in serving WebP and AVIF from Sanity.

Redirects at the edge

Every URL that changes needs a single-hop 301 to its closest equivalent. Avoid chains and avoid sending everything to the homepage. For a few hundred rules, next.config.ts redirects are fine. For thousands, use a lookup in middleware, which Next.js 16 calls proxy, backed by a map or edge storage so you are not bloating config.

// next.config.ts (small redirect sets)
async redirects() {
  return [
    {
      source: '/2022/05/:slug',
      destination: '/blog/:slug/',
      permanent: true,
    },
    {
      source: '/wp-content/uploads/:path*',
      destination: '/media/:path*',
      permanent: true,
    },
  ]
},

Pattern rules cover most date-based permalinks in a few lines. Test the full mapping file against the staging build with a script that requests every old URL and asserts a 301 and a 200 on the final destination.

Launch-day checks

  • Remove noindex and any robots.txt block from staging.
  • Submit the new sitemap in Search Console and keep the old URLs in the redirect map.
  • Confirm canonicals point at the production domain.
  • Re-add analytics and verify structured data renders. See Next.js JSON-LD structured data.
  • Keep the old site reachable on a private URL for a few weeks for reference.

Measuring impact in Search Console for 8 weeks

Before launch, export a baseline: clicks, impressions, and average position by page and by query for the previous 16 weeks, so seasonality is visible. Then check on a schedule.

  1. Week 1: watch the Pages report for new 404s and the Crawl stats for error spikes. Fix redirect gaps immediately.
  2. Week 2: compare clicks for your top 50 pages against the baseline. Small dips are normal while Google recrawls.
  3. Weeks 3 to 4: check indexing. Pages stuck as discovered but not indexed usually have a canonical or internal-link problem.
  4. Weeks 5 to 6: review Core Web Vitals field data. It lags, so judge it only now. If LCP is the issue, start with fixing LCP on image-heavy pages.
  5. Weeks 7 to 8: compare page groups against the baseline. Any group still down by a meaningful margin deserves a page-level investigation, starting with metadata and internal links.

Group pages by template, such as posts, services, and categories, so you can tell a template-level bug from a one-off.

What to ask a developer before you sign

  • Will you deliver a URL mapping file and show me the redirect test results?
  • How will Yoast titles, descriptions, and canonicals be migrated and verified?
  • Who removes the staging noindex, and is it on a launch checklist?
  • What is the plan if a page group drops after launch?

A vague answer to any of these is a reason to keep looking.

This is work I do. If you want a migration planned around SEO parity, see my migration and build packages, where Migration to Sanity starts from $3,000.

Frequently asked questions

Will moving from WordPress to Next.js hurt my SEO?

Not if URLs, titles, meta descriptions, canonicals, and structured data carry over and every changed URL has a 301 redirect. Traffic drops after migrations almost always trace to missed redirects, lost metadata, or noindex left on from staging, not to Next.js itself.

How do I keep my Yoast SEO titles and descriptions after migrating?

Export the Yoast title, meta description, canonical, robots, and Open Graph values for every URL, then import them into dedicated SEO fields on your Sanity documents. Render them with the Next.js generateMetadata function and diff the live output against a crawl of the old site.

How long should I monitor Google Search Console after a WordPress migration?

Watch it for at least 8 weeks. Expect small fluctuations in the first two weeks while Google recrawls, and investigate any page group whose clicks stay down after week four.