---
title: "How to run an SEO-safe site migration"
slug: "seo-migration-playbook"
category: "howto"
tags: ["howto", "seo", "migration", "redirects", "domain-change", "https", "tutorial"]
status: "stable"
last_updated: 2026-05-15
summary: "Inventory URLs, build a 1:1 redirect map, stage on noindex, flip DNS at low traffic, and monitor coverage for four weeks."
related: ["[[seo/redirects]]", "[[seo/technical]]", "[[seo/discoverability-files]]", "[[howto/static-site-seo]]", "[[howto/set-up-quartz-with-cloudflare-pages]]", "[[seo/crawl-budget]]", "[[howto/master-google-search-console]]", "[[seo/glossary]]", "[[seo/audit-checklist]]"]
---

## Overview

A site migration is a domain change, an HTTPS upgrade, a URL restructure, or a platform swap. Each one rewrites the URL set Google has indexed. Done well, the migration costs a week of fluctuation and then settles. Done badly, it costs months of recovery or never recovers. This playbook lands the change with the redirect map as the single source of truth.

## Prerequisites

- Crawl access to the current site (Screaming Frog, Sitebulb, or `wget --spider`).
- A staging host that can serve the new site with `X-Robots-Tag: noindex` on every response.
- Owner access to Google Search Console and Bing Webmaster Tools for both the old and new properties.
- DNS control with TTL lowered to 300 seconds at least 48 hours before cutover.
- An analytics baseline: 30 days of clicks, impressions, and average position per top URL.

## Steps

### 1. Crawl the current site for a full URL inventory

Run a full-site crawl and export every URL that returns 200. Include canonical, status code, title, H1, indexability, and inbound link count. This is the source-of-truth list for the redirect map.

```bash
# Screaming Frog headless example
screamingfrogseospider \
  --crawl https://oldsite.example.com \
  --headless \
  --save-crawl \
  --export-tabs "Internal:HTML" \
  --output-folder ./crawl/
```

Also pull the top 1,000 URLs from Google Search Console (Performance > Pages, 16-month window) and the top 500 referring URLs from Ahrefs or your backlink tool. Union all three sets. URLs in GSC or backlink data that did not appear in the crawl are orphan pages or off-site assets; redirect them too.

### 2. Build the 1:1 redirect map old to new

For every old URL, write the new URL. One row per source. No chains, no loops, no many-to-one collapses unless the merge is deliberate.

```csv
old_url,new_url,reason
https://oldsite.example.com/blog/post-1,https://newsite.example.com/articles/post-1,renamed-folder
https://oldsite.example.com/images/hero.png,https://newsite.example.com/static/hero.png,asset-move
https://oldsite.example.com/llms.txt,https://newsite.example.com/llms.txt,passthrough
```

Include `robots.txt`, `sitemap.xml`, `llms.txt`, `ai.txt`, and all OG image URLs. Missing those is the single most common migration mistake; see [[seo/discoverability-files]] for the full list.

### 3. Stage on a noindex preview

Deploy the new site to a staging host with these response headers on every URL:

```
X-Robots-Tag: noindex, nofollow
```

Confirm with `curl -I https://staging.example.com/` before serving real content. Do not rely on `<meta name="robots">` alone; staging crawlers honor the header faster.

Wire the redirect map into the new platform's redirect engine (Cloudflare Bulk Redirects, Vercel `_redirects`, Cloudflare Pages `_redirects`, or Nginx `return 301`). See [[howto/set-up-quartz-with-cloudflare-pages]] for the Cloudflare Pages syntax.

### 4. Verify with a parallel crawl on staging

Crawl staging with the same tool. Compare three columns to the production crawl: URL count, indexable count, title match. Resolve every diff before cutover.

```bash
diff <(sort prod-urls.txt) <(sort staging-urls.txt)
```

Run the redirect map through a checker. Every old URL must return 301 (not 302, not 200, not 404) and resolve to the mapped new URL in one hop. Chains break.

### 5. Flip DNS at the low-traffic window

Pick the hour with the lowest GSC click volume across the past 30 days. Change the A or CNAME record to point at the new host. Because TTL is 300 seconds, propagation completes in under 10 minutes for most resolvers.

Remove the `X-Robots-Tag: noindex` header on the new host the moment DNS flips. Confirm with:

```bash
curl -I https://example.com/ | grep -i x-robots-tag
# (no output is correct)
```

### 6. Submit the new sitemap to GSC and Bing

In GSC for the new property: Sitemaps > Add a new sitemap > `https://example.com/sitemap.xml`. In Bing Webmaster Tools: Sitemaps > Submit. In GSC for the old property: use the Change of Address tool only if the domain itself changed.

Ping [[seo/indexnow]] with the new sitemap URL to accelerate Bing and Yandex discovery.

### 7. Monitor coverage and 404s daily for four weeks

Watch four signals every day for four weeks, then weekly thereafter:

- GSC Coverage report: Errors and Excluded counts must trend down.
- GSC Performance: total clicks and impressions stabilize within 14 days.
- Server logs: any URL returning 404 with referrer traffic is a missing redirect; add it.
- 301 chain detector: re-run weekly to catch new chains added by content changes.

See [[howto/master-google-search-console]] for the GSC report layout.

## Verify it worked

```bash
# 1. Top 50 URLs all return 301 -> 200 in one hop.
while read url; do
  curl -s -o /dev/null -w "%{http_code} %{redirect_url}\n" "$url"
done < top-50-old-urls.txt

# 2. Sitemap is fetched (GSC Sitemaps report shows "Success").

# 3. Aggregate clicks recover to within 90% of baseline by week 4.
```

## Common errors

- Redirect chains. `/old` 301 to `/intermediate` 301 to `/new` halves the equity Google passes. Flatten every chain to one hop.
- Missing redirects for image URLs. Pinterest pins, embedded blog images, and OG card URLs all die silently. Include every asset in the map.
- Forgetting to redirect `llms.txt`, `robots.txt`, `ai.txt`, `humans.txt`, and `sitemap.xml`. Agents and crawlers hit these first; a 404 here is a trust signal.
- Cutting over with the staging `noindex` header still set. The new site is invisible to Google for as long as the header lives. Always grep for it post-cutover.
- Using 302 instead of 301. Temporary redirects do not pass full equity; Google may keep the old URL indexed indefinitely. Use 301; see [[seo/redirects]] for the status-code rules.
- Changing more than one variable at once. A simultaneous domain change, redesign, and CMS swap makes regression analysis impossible. Sequence the changes.

## Related

- [[seo/redirects]]
- [[seo/technical]]
- [[seo/discoverability-files]]
- [[howto/static-site-seo]]
- [[howto/set-up-quartz-with-cloudflare-pages]]
- [[seo/crawl-budget]]
- [[howto/master-google-search-console]]
- [[seo/glossary]]
- [[seo/audit-checklist]]
