Key takeaways

  • Build a full redirect map before you touch code. Every URL from the old site needs a mapped destination. Check status codes. Don't leave this to guesswork after launch.
  • Use 301 redirects only. Keep chains to a single hop. Test the map on staging and production, both before and after go-live.
  • Ship self-referencing canonical tags and a working sitemap on day one. Submit both to Google Search Console right away.
  • Watch Search Console and analytics daily for the first two weeks. A real problem shows up as a sustained double-digit traffic drop, not normal post-launch fluctuation.
  • Pairing careful redirects with a real platform upgrade compounds the benefit: the SleepDr.com move from WordPress to Next.js and Payload CMS lifted Lighthouse performance from 35 to 94 alongside the URL and metadata work.

Updated 15 August 2026: sources added, experience claims checked against our project record, summary added.

CMS Migration Without Losing SEO: 2026 Complete Guide

Why CMS Migrations Destroy Rankings

Google doesn't care what CMS you use. It cares about URLs, content, page speed, internal linking, and structured data. Change your CMS, and you risk breaking all of these at once.

Here's what typically goes wrong:

  • URL structures change. WordPress uses /2024/03/my-post/ or /category/subcategory/post-name/. Your new system probably uses /blog/post-name. That's hundreds or thousands of broken URLs.
  • Internal links break. Every link from one page to another was built for the old URL structure.
  • Metadata disappears. Your Yoast or RankMath SEO titles, meta descriptions, and OG tags don't automatically transfer to a headless CMS.
  • Structured data vanishes. Schema markup from plugins doesn't exist in your new frontend unless you rebuild it.
  • Page speed changes. Sometimes for the better. Sometimes for the worse, if you're not careful with client-side rendering.

A meaningful share of sites that migrate CMS platforms see a traffic drop that lasts for months. The sites that avoid this aren't lucky. They're prepared.

Pre-Migration Audit: The Foundation

Before you write a line of code on your new platform, get a full snapshot of your current SEO state. This isn't optional. Skip it, and you're flying blind.

Crawl Everything

Use Screaming Frog, Sitebulb, or Ahrefs Site Audit to crawl your existing site. You need:

  • Every URL (including paginated pages, tag pages, author pages)
  • HTTP status codes for every URL
  • All internal links and their anchor text
  • Meta titles and descriptions for every page
  • Canonical tags on every page
  • Hreflang tags if you have multilingual content
  • Structured data per page
  • Image URLs and alt text

Export this to a spreadsheet. This is your migration bible.

Document Your Top Performers

Pull Google Search Console data for as much history as is available. Search Console keeps up to 16 months. Identify:

  • Top 100 pages by organic clicks
  • Top 100 pages by impressions
  • Pages ranking in positions 1-10 for high-value keywords
  • Pages with the most backlinks (use Ahrefs or Semrush)

These are your VIP pages. Test them first, monitor them first, and fix them first if anything goes wrong.

Baseline Your Metrics

Record these numbers the week before migration:

Metric Tool Why It Matters
Total indexed pages Google Search Console Catch deindexing quickly
Organic sessions/week GA4 Primary success metric
Average position GSC Detect ranking drops
Core Web Vitals PageSpeed Insights Performance comparison
Total referring domains Ahrefs/Semrush Ensure backlinks still resolve
Crawl errors GSC Baseline for comparison
Sitemap pages submitted vs indexed GSC Track indexing health

301 Redirect Strategy That Actually Works

This is where migrations live or die. Teams often treat redirects as an afterthought, something to sort out after launch. That habit is how sites lose a large share of organic traffic overnight.

Map Every URL Before You Build

Create a redirect map spreadsheet with these columns:

Old URL | New URL | Status Code | Priority | Notes

Every single URL from your crawl needs a destination. Yes, even those tag pages and author archives you forgot existed.

The Redirect Decision Framework

Old Page Type Recommended Action Redirect To
Blog post (keeping content) 301 redirect New URL for same content
Blog post (removing content) 301 to most relevant page Related blog post or category
Category page 301 redirect Equivalent new category/tag page
Tag page (low value) 301 to category Parent category page
Author page 301 to about/team page Team page or homepage
Paginated pages (/page/2/) 301 to main page Parent page (page 1)
Media/attachment pages 301 to parent post Post containing the media
Old WordPress pages (/wp-admin, /xmlrpc.php) 410 Gone N/A
Feed URLs (/feed/, /rss/) 301 or recreate New feed URL if applicable

Implement Redirects at the Right Layer

For Next.js migrations, you have options for where redirects live:

// [next.config.js - Good for known, static redirects](https://nextjs.org/docs/app/api-reference/config/next-config-js/redirects)
module.exports = {
  async redirects() {
    return [
      {
        source: '/2024/03/my-old-post/',
        destination: '/blog/my-old-post',
        permanent: true, // 301
      },
      // Pattern-based redirects
      {
        source: '/category/:slug',
        destination: '/blog/category/:slug',
        permanent: true,
      },
    ]
  },
}

For large-scale migrations (500+ redirects), middleware or edge functions usually handle the volume better than static config:

// middleware.ts - Better for large redirect maps
import { NextResponse } from 'next/server'
import type { NextRequest } from 'next/server'
import redirectMap from './redirects.json'

export function middleware(request: NextRequest) {
  const path = request.nextUrl.pathname
  const redirect = redirectMap[path]
  
  if (redirect) {
    return NextResponse.redirect(
      new URL(redirect.destination, request.url),
      redirect.permanent ? 301 : 302
    )
  }
}

export const config = {
  matcher: [
    // Match old WordPress URL patterns
    '/:year(\\d{4})/:month(\\d{2})/:slug*',
    '/category/:path*',
    '/tag/:path*',
    '/author/:path*',
  ],
}

For sites with thousands of redirects, consider handling them at the CDN/edge level (Vercel Edge Config, Cloudflare Workers, or Netlify redirects file) to avoid bloating your application code.

Test Every Single Redirect

Test every redirect, not just a sample. A simple script can automate this:

## test-redirects.sh
while IFS=, read -r old_url new_url; do
  status=$(curl -o /dev/null -s -w "%{http_code}" -L "$old_url")
  final=$(curl -o /dev/null -s -w "%{url_effective}" -L "$old_url")
  echo "$status | $old_url -> $final"
done < redirects.csv

Run this against staging before go-live. Run it again in production right after launch.

CMS Migration Without Losing SEO: 2026 Complete Guide - architecture

Canonical Tags: The Misunderstood Safety Net

Canonical tags aren't a replacement for redirects. But they're a critical layer of defense during migration.

Self-Referencing Canonicals on Every Page

Every page on your new site should have a self-referencing canonical tag:

<link rel="canonical" href="https://yourdomain.com/blog/exact-current-url" />

In Next.js with the App Router:

// app/blog/[slug]/page.tsx
import { Metadata } from 'next'

export async function generateMetadata({ params }): Promise<Metadata> {
  const post = await getPost(params.slug)
  return {
    alternates: {
      canonical: `https://yourdomain.com/blog/${params.slug}`,
    },
  }
}

Common Canonical Mistakes During Migration

  • Trailing slash inconsistency. /blog/post and /blog/post/ are different URLs to Google. Pick one, redirect the other, and make sure your canonical matches.
  • HTTP vs HTTPS in canonicals. Always use HTTPS. This mistake is common enough to check for directly.
  • Staging URLs leaking into production. If your canonical tags point to staging.yourdomain.com, you're telling Google to index your staging site. Check for this in QA.
  • Missing canonicals on paginated content. If you paginate blog listings, each page needs its own canonical, not one that points back to page 1.

Sitemap Preservation and Submission

Generate a New Sitemap Immediately

Your new sitemap should be ready on day one. For Next.js projects, sitemaps can be generated dynamically:

// app/sitemap.ts (Next.js 14+/15)
import { MetadataRoute } from 'next'

export default async function sitemap(): Promise<MetadataRoute.Sitemap> {
  const posts = await getAllPosts() // From your headless CMS
  
  const blogEntries = posts.map((post) => ({
    url: `https://yourdomain.com/blog/${post.slug}`,
    lastModified: new Date(post.updatedAt),
    changeFrequency: 'weekly' as const,
    priority: 0.8,
  }))

  const staticPages = [
    {
      url: 'https://yourdomain.com',
      lastModified: new Date(),
      changeFrequency: 'daily' as const,
      priority: 1,
    },
    // ... other static pages
  ]

  return [...staticPages, ...blogEntries]
}

Submission Strategy

  1. Before migration: Download your old sitemap and save it
  2. After migration: Submit the new sitemap in Google Search Console right away
  3. Keep the old sitemap temporarily: For the first 30 days, make sure old sitemap URLs redirect properly so Google can follow the chain
  4. Use Google's URL Inspection tool: Manually request indexing for your top 50 VIP pages
  5. Monitor the Index Coverage report daily for the first two weeks

Don't Forget robots.txt

Your new robots.txt needs to:

  • Allow Googlebot to crawl everything it could before
  • Point to your new sitemap location
  • Not accidentally block JS/CSS files that Next.js needs for rendering
User-agent: *
Allow: /

Sitemap: https://yourdomain.com/sitemap.xml

Technical Migration Checklist

This is a working checklist for a clean cutover. Print it out, laminate it, tattoo it on your arm. Whatever works.

Pre-Launch (2-4 Weeks Before)

  • Complete site crawl of existing site exported to spreadsheet
  • Top pages by traffic and backlinks identified
  • Full redirect map created and reviewed
  • New URL structure finalized (no changes after this point)
  • Meta titles and descriptions migrated to new CMS
  • Structured data (JSON-LD) implemented on new site
  • Open Graph and Twitter Card tags implemented
  • Internal links updated to use new URL structure
  • Image alt text migrated
  • Canonical tags verified on every template
  • Hreflang tags implemented (if multilingual)
  • robots.txt reviewed
  • New sitemap generating correctly
  • 404 page created with helpful navigation
  • Core Web Vitals passing on staging
  • Analytics and tracking codes installed
  • GSC verified for new domain/subdomain (if changing)

Launch Day

  • DNS changes propagated
  • SSL certificate active
  • All redirects tested and verified
  • New sitemap submitted to GSC
  • Manual index request for top 20 pages
  • Smoke test: spot-check 50 random old URLs for proper redirects
  • Verify no staging URLs in canonical tags
  • Verify no noindex tags on production pages
  • Check server response times stay within the range web.dev considers good for Time to First Byte

Post-Launch (First 30 Days)

  • Daily GSC monitoring for crawl errors
  • Weekly comparison of organic traffic vs baseline
  • Monitor index coverage report for drops
  • Check for soft 404s in GSC
  • Verify backlinks are resolving correctly (spot check top 20)
  • Monitor Core Web Vitals in field data
  • Address any new 404s that appear in GSC

WordPress to Headless Next.js: Step by Step

This is a common path when moving away from a monolithic WordPress build. Here's how to approach it when working on headless CMS development projects.

Choose Your Headless CMS

You're leaving WordPress-the-monolith. But you might keep WordPress-the-CMS as a headless backend, or move to something else entirely.

CMS Best For Content Migration Effort Pricing (2026)
WordPress (headless via WPGraphQL) Teams who know WP Minimal, content stays put Hosting costs only
Sanity Structured content, developer teams Medium, export/import needed Free tier, then $99+/mo
Contentful Enterprise, multi-channel Medium-High Free tier, then $300+/mo
Strapi Self-hosted control Medium Free (self-hosted) or $29+/mo cloud
Payload CMS Next.js native, TypeScript teams Medium Free (self-hosted) or $35+/mo cloud

If you use WordPress as a headless backend, you skip the content migration problem entirely. Your editorial team keeps its familiar WordPress admin, and the frontend becomes a fast Next.js app. We cover this approach in more detail on our Next.js development page.

Content Migration Script

If you're moving to a new CMS, you'll need a migration script. Here's a simplified version that pulls content from WordPress:

// scripts/migrate-wp-to-sanity.ts
import WPAPI from 'wpapi'
import { createClient } from '@sanity/client'

const wp = new WPAPI({ endpoint: 'https://old-site.com/wp-json' })
const sanity = createClient({
  projectId: 'your-project',
  dataset: 'production',
  token: process.env.SANITY_TOKEN,
  apiVersion: '2026-01-01',
})

async function migratePosts() {
  let page = 1
  let hasMore = true

  while (hasMore) {
    const posts = await wp.posts().page(page).perPage(100)
    
    for (const post of posts) {
      await sanity.create({
        _type: 'post',
        title: post.title.rendered,
        slug: { current: post.slug },
        // Convert WP HTML to Portable Text or MDX
        body: convertHtmlToPortableText(post.content.rendered),
        publishedAt: post.date,
        // Preserve the old URL for redirect mapping
        legacyUrl: new URL(post.link).pathname,
        seo: {
          metaTitle: post.yoast_head_json?.title || post.title.rendered,
          metaDescription: post.yoast_head_json?.description || '',
        },
      })
    }

    hasMore = posts._paging?.totalPages > page
    page++
  }
}

The key detail most migration guides miss: preserve the old URL as a field in your new CMS. This makes redirect generation trivial and gives you a permanent record of where content came from.

Structured Data Migration

WordPress plugins like Yoast generate structured data automatically. In Next.js, you need to build it yourself:

// components/ArticleSchema.tsx
export function ArticleSchema({ post }) {
  const schema = {
    '@context': 'https://schema.org',
    '@type': 'Article',
    headline: post.title,
    datePublished: post.publishedAt,
    dateModified: post.updatedAt,
    author: {
      '@type': 'Person',
      name: post.author.name,
    },
    publisher: {
      '@type': 'Organization',
      name: 'Your Company',
      logo: {
        '@type': 'ImageObject',
        url: 'https://yourdomain.com/logo.png',
      },
    },
    image: post.featuredImage?.url,
    description: post.excerpt,
  }

  return (
    <script
      type="application/ld+json"
      dangerouslySetInnerHTML={{ __html: JSON.stringify(schema) }}
    />
  )
}

Don't forget BreadcrumbList, FAQPage, and any other schema types your WordPress site was generating. Check them with Google's Rich Results Test before and after migration.

Post-Migration Monitoring

The first 48 hours after migration matter most. Here's what to watch:

The First 48 Hours

  • Watch server logs for 404s in real time. Every 404 is a missed redirect.
  • Check GSC's URL Inspection tool for your VIP pages. Are they being recrawled?
  • Monitor your CDN/hosting for unexpected traffic spikes or drops.

The First 2 Weeks

Some ranking fluctuation is normal. Google needs to recrawl and reprocess your entire site. What's not normal:

  • More than 15% traffic drop sustained for more than 5 days
  • VIP pages losing more than 3 positions
  • Index coverage dropping by more than 10%

If you see any of these, check your redirects first. Then check for accidental noindex tags. Then check that your content actually rendered (SSR issues in Next.js can serve empty pages to Googlebot).

The First 3 Months

Set up weekly automated reports comparing:

  • Organic traffic week-over-week
  • Average position for your top 50 keywords
  • Number of indexed pages
  • Core Web Vitals scores

Well-executed migrations often see traffic recover to baseline within a few weeks, and sometimes exceed it as Core Web Vitals improve. The SleepDr.com migration from WordPress to Next.js and Payload CMS, for example, lifted Lighthouse performance from 35 to 94. Durable traffic gains tend to follow that kind of performance jump once the redirect and metadata work is solid.

Common Migration Mistakes to Avoid

Changing URL structure AND content at the same time. Don't. Migrate your content as-is, launch, let Google settle, then optimize content later. Changing too many signals at once makes problems hard to diagnose.

Forgetting about images. If your images used to live at yourdomain.com/wp-content/uploads/ and now sit on a CDN with different URLs, every image link from every external site pointing to your images is broken. Redirect those paths too.

Not handling trailing slashes consistently. Next.js has a trailingSlash config option. Pick true or false, and make sure every redirect, canonical, and sitemap entry matches.

Launching on a Friday. Just don't. Launch Tuesday or Wednesday morning so you have the full week to monitor and fix issues.

Not telling Google about the migration. If you're changing domains, use GSC's Change of Address tool. Even if you stay on the same domain, resubmit your sitemap and use the Removals tool to clear old URLs that shouldn't be indexed.

If you're feeling overwhelmed by all this, that's understandable. It's genuinely complex work. Our team can help you plan or review a migration.

FAQ

How long does it take for Google to recognize 301 redirects?

Google typically finds and processes 301 redirects within a few days to two weeks, depending on how often Googlebot crawls your site. High-authority pages with lots of backlinks tend to get recrawled faster. Speed things up by submitting your updated sitemap and using the URL Inspection tool to request recrawling of key pages.

Will I lose link equity (link juice) from 301 redirects?

Google confirmed in 2016 that 301 redirects pass full link equity, removing the old "PageRank tax" for permanent redirects. Redirect chains (A to B to C) can still slow the transfer and waste crawl budget. Keep redirects to a single hop wherever possible.

Can I use 302 redirects instead of 301s during migration?

No. Use 301 (permanent) redirects for migration. A 302 tells Google the move is temporary and it should keep the old URL in its index, which directly contradicts what you want during a CMS migration. The only exception is if you genuinely plan to revert. But if you're migrating your CMS, you're not going back.

How many 301 redirects is too many for Next.js?

Next.js handles redirects defined in next.config.js well for smaller lists. Very large maps work better from middleware, edge functions, or your CDN. Vercel's Edge Config is built for large-scale redirect lookups at the edge. For self-hosted Next.js, a Redis-backed lookup in middleware works well too.

Should I redirect WordPress tag and author pages?

Yes, but strategically. If your tag pages had real traffic or backlinks, redirect them to the most relevant equivalent page on your new site. If they were thin content pages with little traffic, which describes most WordPress tag pages, redirect them to the parent category or blog index. Author pages should typically redirect to an about page or team page.

What happens to my Google Business Profile and other citations after migration?

If your domain stays the same, most citations and your Google Business Profile won't be affected. However, if specific URLs were listed, like a services page, make sure those redirect properly. Update any URLs in your Google Business Profile, social media profiles, and major directory listings within the first week after migration.

Is it better to migrate to headless WordPress or a different headless CMS?

It depends on your team and content model. If your content editors know WordPress well and your content fits its structure, using WordPress as a headless backend with WPGraphQL removes the content migration risk entirely. If you're hitting WordPress's limitations on content modeling, or want a more modern editing experience, Sanity, Payload CMS, or Contentful are strong alternatives. We break down the options further on our headless CMS development page.

How do I handle multilingual content during a CMS migration?

Multilingual migrations add another layer of complexity. You need to preserve hreflang tags exactly as they were, redirect each language version to its corresponding new URL, and confirm your new CMS supports the same language and region structure. That covers the core requirement, but the details take more care. If you're switching from subdirectories (/es/, /fr/) to subdomains, or vice versa, that's essentially a domain change for each language. It needs extra care with redirects and Search Console setup.