Website Migration SEO Validation
Verify SEO elements are preserved during site migrations
Website migrations often damage search rankings when SEO elements are misconfigured. EdgeDNS validates critical SEO infrastructure — hreflang reciprocity for international sites, per-crawler robots rules, sitemap URL sampling, and canonical drift — to catch issues before they impact traffic.
The Challenge
Website migrations and platform changes frequently cause SEO disasters. Redirects break, canonical tags point to wrong URLs, hreflang alternates lose their reciprocal return links, sitemaps reference old pages, and robots.txt drifts between Googlebot and AI crawlers. These issues can take months to recover from and cause significant traffic and revenue loss.
The Solution
Run automated SEO validation against your staging environment before migration. Audit hreflang reciprocity (Developer+) so every international alternate has a return link. Sample-validate sitemap URLs (Pro+) to confirm they return 200 with accurate `lastmod`. Resolve the effective robots rule for every major crawler (Googlebot, GPTBot, ClaudeBot, and 10 more) so you know what each one actually sees. Compare pre- and post-migration scans to catch every regression.
Endpoints Used
Combine these EdgeDNS endpoints to build this solution.
/v1/domain/canonicalTry in PlaygroundCanonical URL: Verify canonical tags and audit hreflang reciprocity — every alternate must have a return link (Developer+)
/v1/domain/metaTry in PlaygroundMeta Tags: Check title, description, OG tags, parsed robots directives, verification tags, and platform-specific meta
/v1/domain/sitemapTry in PlaygroundSitemap: Verify sitemap accessibility, sample-validate listed URLs (Pro+), and check `lastmod` accuracy against actual page changes
/v1/domain/robotsTry in PlaygroundRobots.txt: Resolve effective rules per crawler (13 crawlers including GPTBot, ClaudeBot, CCBot) and HEAD-verify referenced sitemaps
/v1/domain/redirectTry in PlaygroundRedirect Chain: Trace every hop after migration with category labels (protocol-upgrade, canonical, mixed-content, open-redirect-suspect, meta-refresh); pass `canonicalCheck=true` to verify the chain lands on a URL whose `<link rel=canonical>` matches
/v1/domain/response-timeTry in PlaygroundResponse Time: Verify site performance post-migration with p90/p95/p99 TTFB and Server-Timing breakdown
Results You Can Achieve
Pre-flight migration validation in seconds
Catch redirect chains, missing canonicals, hreflang return-link gaps, and broken sitemap URLs before search engines crawl the new domain.
Redirect-chain SEO analysis
Hop-by-hop category labels distinguish desired redirects (HTTP→HTTPS, www↔non-www) from problems (302 used for permanent moves, mixed-content, canonical mismatches). The `canonicalCheck` parameter catches "you redirect to /foo but /foo's canonical is /bar" — a silent ranking leak after migrations.
Hreflang reciprocity audit for international sites
Every hreflang alternate fetched and checked for a return link. Missing return links are the #1 silent SEO disaster on global migrations — caught automatically (Developer+).
Per-crawler robots resolution + sitemap URL sampling
See exactly which rule Googlebot, GPTBot, and ClaudeBot apply post-launch. Sample-validate sitemap URLs to confirm they return 200 with accurate `lastmod` (Pro+).
Before/after diff per migrated URL
Side-by-side scan output documents which signals improved, which regressed, and which need follow-up — replaces ad-hoc spreadsheet tracking.
Migration audit log dated for stakeholders
A scored, timestamped run is shareable with marketing and SEO for go/no-go sign-off.
Code Example
SEO validation for website migration
async function validateMigrationSEO(urls) {
const headers = { 'Authorization': 'Bearer YOUR_API_KEY' };
const issues = [];
for (const url of urls) {
const domain = new URL(url).hostname;
const [canonical, meta, robots, redirect] = await Promise.all([
// validateReciprocity audits hreflang return-links (Developer+ tier)
fetch(`https://api.edgedns.dev/v1/domain/canonical?url=${url}&validateReciprocity=true`, { headers }),
fetch(`https://api.edgedns.dev/v1/domain/meta?url=${url}`, { headers }),
fetch(`https://api.edgedns.dev/v1/domain/robots?domain=${domain}`, { headers }),
// canonicalCheck=true compares the final URL to its <link rel=canonical>
fetch(`https://api.edgedns.dev/v1/domain/redirect?url=${url}&canonicalCheck=true`, { headers }),
].map(p => p.then(r => r.json())));
if (canonical.data.canonical && canonical.data.canonical !== url) {
issues.push({ url, issue: `Canonical mismatch: ${canonical.data.canonical}` });
}
// Hreflang reciprocity: every alternate must link back
const missing = canonical.data.hreflangReciprocity?.missingReturnLinks ?? [];
for (const m of missing) {
issues.push({ url, issue: `Hreflang ${m.lang} (${m.href}) has no return link` });
}
if (!meta.data.title) issues.push({ url, issue: 'Missing title tag' });
if (!meta.data.description) issues.push({ url, issue: 'Missing meta description' });
// Redirect-chain checks: chain length, 302-for-permanent, canonical drift
if (redirect.data.totalRedirects > 2) {
issues.push({ url, issue: `Redirect chain has ${redirect.data.totalRedirects} hops — consolidate to ≤ 1` });
}
for (const issue of redirect.data.structuredIssues ?? []) {
if (issue.code === 'redirect-temporary-for-permanent') {
issues.push({ url, issue: '302/307 used for what looks like a permanent move — switch to 301 for ranking signals' });
}
if (issue.code === 'redirect-canonical-mismatch') {
issues.push({ url, issue: `Redirect lands on ${redirect.data.finalUrl} but its canonical is ${redirect.data.canonicalMatch?.canonicalUrl}` });
}
}
// Verify Googlebot, GPTBot, and ClaudeBot are not accidentally blocked
const rules = robots.data.effectiveRulesByCrawler ?? {};
for (const bot of ['Googlebot', 'GPTBot', 'ClaudeBot']) {
if (rules[bot]?.effective === 'disallow-all') {
issues.push({ url, issue: `${bot} is disallow-all in robots.txt` });
}
}
}
// validateUrls samples and HEAD-checks each listed sitemap URL (Pro+ tier)
const domain = new URL(urls[0]).hostname;
const sitemapCheck = await fetch(
`https://api.edgedns.dev/v1/domain/sitemap?domain=${domain}&validateUrls=true`,
{ headers }
).then(r => r.json());
if (!sitemapCheck.data.accessible) {
issues.push({ url: 'sitemap.xml', issue: 'Sitemap not accessible' });
}
const brokenSample = sitemapCheck.data.sampleValidation?.broken ?? [];
for (const b of brokenSample) {
issues.push({ url: b.url, issue: `Sitemap URL returned ${b.status}` });
}
return { passed: issues.length === 0, issues };
}Learn More
Explore industry standards and best practices related to this use case.
Ready to build Website Migration SEO Validation?
Get started with 200 free API requests per month. No credit card required.