The migration ships on schedule, passes QA, and never goes down. Three weeks later organic traffic is off by 30% and no test failed. Search Engine Journal's practitioner guidance puts a normal temporary dip after a migration at 10–30%. That is practitioner experience, not a controlled study. SISTRIX measured a roughly 97% visibility drop for Clarks Spain after a domain migration.
Neither case was an uptime incident, and this website migration SEO checklist assumes you can read a crawl export and write a redirect rule. Headless CMS architecture helps later: slugs and SEO metadata live in content fields, not the content management system (CMS) theme templates a migration replaces, so a frontend rewrite doesn't touch the URLs Google has indexed.
In brief:
- Every indexable old URL needs a single-hop 301 or 308 at the server or edge.
- Baseline the crawl, Search Console export, and Core Web Vitals before touching anything.
- Watch for staging
robots.txtleakage and platform canonical defaults. - Keep redirects live at least a year.
Together, these controls protect the URL and content signals search engines need to process the move.
Why CMS Migrations Break SEO Rankings
Most migration failures aren't uptime incidents — they're signal losses that Googlebot surfaces weeks after launch.
How Search Engines Interpret a Migration
Googlebot must visit every URL on both the old and new site at least once. Google's site-move documentation says that takes "a few weeks or more" for medium-sized sites and longer for large ones, with ranking fluctuations expected during the recrawl. The redirects themselves are safe. The same document states that "301 and other permanent redirects don't cause a loss in PageRank." Changed URLs are one signal among several. A rebuilt internal link graph or a switch to client-side rendering also changes what Googlebot can discover and index.
Redirect type decides which URL Google keeps. Its redirects reference classifies 301 and 308 as permanent, which makes the target canonical, and 302, 303, and 307 as temporary, which Googlebot follows without transferring canonical status.
Before you write a single redirect rule, work out which of these three migrations you are actually running:
- Domain migration. Submit Change of Address requests for all verified variants of the old domain, plus server-side 301s.
- Platform or CMS migration on the same domain. URL patterns, canonical defaults, and rendering behavior change together.
- URL-structure change. Same redirect, sitemap, and canonical work, without Change of Address.
That distinction determines whether the work stays at the URL and platform level or also requires a domain-level signal.
The Most Common Developer-Side Failures
Platform canonical defaults point somewhere wrong. Check whether staging hostnames are hardcoded into canonical and hreflang tags, so production pages do not declare a staging URL as canonical and consolidate signals onto URLs that will eventually 404. In Search Console that disagreement appears as Duplicate, Google chose different canonical than user.
Rendered HTML drifts from source HTML. Compare source and rendered HTML for JavaScript-rendered headings, and verify that rebuilds preserve schema markup and SEO overrides.
Staging robots.txt ships to production. Search Engine Land documents a developer pushing a staging robots.txt live and losing 90% of organic traffic within 24 hours. Password-protect staging, and fail the build when production is about to serve Disallow: /.
Pre-Migration SEO Audit: The Before Snapshot
You can't diff what you didn't baseline — crawl, rank, and Core Web Vitals data captured before cutover is your only recovery reference.
Crawl and Index Your Current Site
Start with a full crawl of the live site in Screaming Frog (v24.3), Sitebulb, or a headless-browser script for custom extraction. Export every indexable URL with its HTTP status, canonical, meta robots directive, and hreflang. You can enable hreflang crawling under Config > Spider > Crawl and review the Hreflang tab for missing x-default and non-canonical entries.
Turn on Crawl Linked XML Sitemaps, run Crawl Analysis, then check the Sitemaps tab for URLs Not In Sitemap, Orphan URLs, and Non-Indexable URLs In Sitemap. Flag Orphan URLs early.
Baseline Your Rankings and Traffic
The Search Console UI caps its table at 1,000 rows, nowhere near enough for a baseline. The Search Analytics API gives you 25,000 rows per request and 50,000 per day per search type. Pull query, page, clicks, impressions, CTR, and average position for the 90 days before cutover.
From that export, take the top 100 URLs by clicks and the top 50 by impressions as the do-not-break set every later check runs against. Snapshot Core Web Vitals per template through the Chrome UX Report (CrUX) API, which reports Largest Contentful Paint (LCP), Interaction to Next Paint (INP), and Cumulative Layout Shift (CLS) per URL or origin over a rolling 28-day window. Good thresholds at the 75th percentile: LCP at or under 2.5 seconds, INP at or under 200 ms, CLS at or under 0.1.
Map Your Internal Link Graph
Export Bulk Export > Links > All Inlinks from Screaming Frog. The CSV carries Source URL, Destination URL, Anchor Text, Follow, and Link Position. Sort the Internal tab by Unique Inlinks descending; the top of that list is your hub set, and each hub has to hold its inbound count after launch.
Then inventory structured data. Under Config > Spider > Extraction, tick JSON-LD, Microdata, and RDFa plus both validation options, then export the Structured Data tab. Record which schema types render on which template.
The Website Migration SEO Checklist
Four layers — redirects, on-page parity, sitemaps, and internal links — each with discrete pass/fail criteria you can build into the deploy pipeline.
URL Mapping and Redirect Plan
Build the map as a CSV, one row per indexable old URL:
- Old URL (absolute)
- New URL (absolute)
- Expected status
- Hop count
- Known backlinks (y/n)
These fields give every redirect a destination, an expected response, and enough context to prioritize failures.
Screaming Frog's near-duplicate export gives a starting 1:1 mapping. Never map many old URLs to one destination. Google warns that redirecting many URLs to the homepage "might be treated as a soft 404 error."
Keep redirect logic at the server or edge layer; a JavaScript redirect is a last resort, not a plan. Google's guidance: "Server-side redirects (HTTP 301/308 for permanent, 302/303/307 for temporary) are preferred," with JavaScript redirects "as a last resort." For bulk maps, Nginx's map module keeps large lookup tables cheap to declare, so thousands of rows cost nothing at config time.
# /etc/nginx/conf.d/redirects.conf
map $request_uri $redirect_target {
/old/pricing /pricing;
/blog/old-post /articles/old-post;
default "";
}
# inside server {}
if ($redirect_target) { return 301 $redirect_target; }If the frontend is Next.js, permanent: true in next.config.js redirects() emits a 308, which preserves the request method and which Google treats as permanent.
Next.js caps that array at 1,024 redirects on Vercel. Larger maps go through Proxy, where NextResponse.redirect supports any status code. Next.js 16 renamed middleware.ts to proxy.ts, so rename both the file and the exported function or your redirect logic silently stops running.
Google can follow up to 10 hops, but its guidance is "ideally no more than 3 and fewer than 5" and a direct redirect whenever possible. Cloudflare's _redirects file won't rescue you: it evaluates rules independently, so /a → /b and /b → /c do not produce /a → /c.
Settle trailing slashes at exactly one layer. Next.js strips them by default, and trailingSlash: true reverses that.
On-Page Element Parity
Title tags, meta descriptions, and canonicals should migrate as data, not be regenerated by the new platform. Google says "Each new URL should have a self-referencing rel="canonical" tag." In Next.js, generateMetadata reads these from the CMS response, and metadataBase should be the production origin, so no emitted canonical carries a preview hostname.
Diff heading hierarchy per template between the old crawl and a staging crawl, checking the rendered DOM as well as source HTML. Carry over Open Graph tags (og:title, og:type, og:image, and og:url are the four the protocol requires) and set twitter:card explicitly if you rely on the large-image format. In Strapi, the community SEO plugin creates a shared.seo component with a nested shared.meta-social component for these fields.
Rebuild the JSON-LD and validate before launch. The Schema Markup Validator checks whether the Schema.org markup is valid and catches deprecated properties. Google's Rich Results Test checks whether Google could show a rich result. In CI, structured-data-testing-tool takes a URL plus Google, Twitter, or Facebook presets and returns pass/fail counts you can assert on.
XML Sitemap and robots.txt Configuration
Generate the new sitemap from the post-migration URL set, within Google's limit of 50,000 URLs or 50 MB uncompressed per file. Google ignores <priority> and <changefreq> and trusts <lastmod> only when it is "consistently and verifiably accurate." At launch, submit two sitemaps, old URLs and new. Google's site-move guidance says the old sitemap's indexed count falls to zero as the new one rises, and warnings about redirecting URLs in it can be ignored.
Read the new robots.txt line by line before launch. Google supports four fields, user-agent, allow, disallow, and sitemap, and noindex there is not supported. A disallowed page can still be indexed if external sites link to it, and its noindex tag stays hidden from Googlebot. Check inherited rules aren't blocking JavaScript bundle paths, since Google Search won't render JavaScript from blocked files.
If you change rendering strategy, confirm crawlers receive complete HTML in the initial response. Verify what Googlebot gets with the URL Inspection live test, whose More Info tab lists blocked JS and CSS resources and rendering console errors.
Internal Linking Integrity
Crawl the new site and diff it against the snapshot with Screaming Frog's Mode > Compare, using Config > Compare > URL Mapping to map old paths to new with regex. Crawl Comparison (v24.0) flags pages that lost significant unique internal links, so sort hub pages by lost unique inlinks.
Rich text written on the old CMS still points at old paths, and each one then routes through the redirect layer. Rewrite them in the content so the internal graph doesn't depend on the redirect layer. Check that breadcrumb markup resolves to new URLs.
How to Execute the Migration Without Losing Traffic
Execution risk lives in the gap between a tested redirect map and a production environment that doesn't behave like staging.
Staged Rollout vs. Big-Bang Cutover
Google's position: "Small or medium sites: We recommend moving all URLs on your site simultaneously," because a single move helps its algorithms "detect the site move and update our index faster." For large sites, moving one section at a time "can make it easier to monitor, detect, and fix problems faster." John Mueller has called staged migrations "perfectly fine," with a caveat: submitting the Change of Address tool midway through a staggered domain move creates what Stan Ventures summarized as "a temporary state of ambiguity."
Big-bang works for smaller sites with a complete 1:1 map. Google warns that after a move it "will crawl your new site more heavily than usual," so provision for the spike.
Testing Redirects Before Go-Live
Freeze the map, deploy it to a staging environment that mirrors production edge behavior (CDN rules, trailing slashes, case sensitivity, HTTPS and www canonicalization), then run the old URL list against it. Automate the assertion:
import requests
def check_redirect(old_url, expected_destination, expected_status=301):
resp = requests.get(old_url, allow_redirects=False)
assert resp.status_code == expected_status, f"{old_url}: got {resp.status_code}"
location = resp.headers.get("Location")
assert location == expected_destination, f"{old_url}: got {location}"allow_redirects=False exposes the raw status and Location header. For chains, run with redirects enabled and walk response.history. Spot-check with curl -IL --max-redirs 10 <url>: a URL appearing twice is a loop. In Screaming Frog, List Mode with Always Follow Redirects feeds the Redirect Chains report, with hop count and a Loop column you can filter to True.
Flag every response that isn't a single-hop 301 or 308: 200s and soft 404s, where the destination returns a friendly error page, 302s, 404s, 500s.
Launch-Day Monitoring Playbook
Submit both sitemaps in Search Console as soon as the deploy flips. You can request indexing for the do-not-break list through URL Inspection in Search Console, where over-quota requests fail silently. If you pull index status programmatically, the URL Inspection API allows 2,000 requests per day and 600 per minute per property.
Tail server logs for Googlebot 404s, repeated 301 chains, and 503 spikes. The Crawl Stats report is available on Domain properties or root-level URL-prefix properties and counts each hop in a chain as a separate request, so chain problems show up as a rise in Other file type requests. Check Host Status details when crawl rate suddenly drops; the report breaks out robots.txt availability, DNS, and host connectivity.
Keep the redirect layer running for at least one year. Google's wording is "Keep the redirects for as long as possible, generally at least 1 year." Keeping the old CMS reachable for the first two to four weeks is a rollback decision. The redirects are a search requirement.
Post-Migration SEO Validation
Launch is the start of the validation window, not the end — the first 28 days of crawl and index data tell you what the pre-launch checklist missed.
Comparing the Before and After Snapshots
Re-crawl the live site on day one and diff against the pre-migration crawl on every field you captured: status codes, redirect targets and hop counts, canonicals, meta robots, titles, <h1>s, hreflang, structured data types, and inlink counts. Screaming Frog 24.0's Auto Crawl Comparison diffs the last two crawls automatically, which is enough for a nightly job through the first month.
In the Page Indexing report, each non-indexed reason has a migration meaning:
- Not Found (404): a missing redirect.
- Excluded by 'noindex' tag: a template shipped with the directive.
- Blocked by robots.txt: staging rules leaked.
- Duplicate, Google chose different canonical than user: the new templates and Google disagree.
- Page with redirect: expected for the old-URL sitemap.
Together, these reasons provide a practical triage order for post-launch indexing problems.
Re-run Core Web Vitals per template. CrUX field data can take up to 28 days to fully reflect the new site, so use lab measurements against the do-not-break list meanwhile.
Tracking Recovery (and When to Escalate)
Plan for fluctuation across four to twelve weeks. Search Engine Journal's practitioner guidance is that "sites begin to see improvements within four to 12 weeks."
Track weekly position deltas for the do-not-break list. Google publishes no four-week escalation trigger, so set your own: if traffic hasn't leveled by week four, audit in order for missed redirects (List Mode against the old-URL export), orphaned pages (Sitemaps tab), and rendering regressions (URL Inspection live test on one URL per template).
Why Headless CMS Architecture De-Risks Future Migrations
Every failure mode above traces back to SEO-critical data living in the presentation layer. In a headless CMS setup that data lives in the content model, and the frontend reads it over the Content API. Swapping Next.js for Astro changes rendering code and nothing in the URL contract.
In Strapi 5, a slug is a uid field defined in the Content-Type Builder, and a new frontend reads that same field. SEO metadata follows the same rule: the shared.seo component holds meta title, description, social tags, and schema fields as content, requested with an explicit populate. Internationalization is core in Strapi 5, so locale-specific metadata derives from the locale parameter.
Headless CMS architecture doesn't remove rendering risk. Server-render metadata and body content, own the sitemap generator as code, and the content-layer advantages hold.
Your Migration Is an Engineering Project. Treat SEO Like a Dependency
The website migration SEO checklist above is a set of build requirements with pass/fail criteria: every indexable URL has a single-hop permanent redirect, every template emits the same canonical, headings, and JSON-LD it did before, robots.txt matches the production spec, and the HTML Googlebot receives contains the content you intend to rank. Each of those can be a test in the pipeline. Keep those tests green before launch. Teams that hand a finished site to someone else to handle the SEO find out three weeks later what they missed.
If you're planning a move to Strapi, plan content-model mapping early; that's where the slug and metadata fields get decided. If you're already on Strapi 4, the v4 to v5 migration docs cover the upgrade mechanics before you touch the redirect map.





