Site migrations lose search rankings when done wrong. Well-executed migrations recover 90-95% of traffic within 30 days. Poor migrations can take 6-12 months to recover or never reach previous levels.
The difference is planning, proper redirects, and post-migration monitoring.
Each migration type affects SEO differently:
Domain change (old.com → new.com): Requires Change of Address tool in Google Search Console and strict 1:1 URL mapping. Keep redirects for at least one year.
Protocol change (HTTP → HTTPS): Update all canonical tags to HTTPS versions. Forgetting this creates redirect loops. redirects send Google to HTTPS, but canonicals point back to HTTP.
URL structure change (/blog/post → /posts/post): Most prone to redirect chains. Map every old URL to exactly one new URL. No intermediates.
Platform/framework change: Moving from WordPress to Vue, or Vue to Nuxt. URL structure usually changes. Full redirect mapping required.
Site redesign with same URLs: Lowest risk if URLs stay identical. Still validate canonical tags and internal links.
Start here before touching production:
For large sites (10,000+ pages), consider migrating in sections. Test with a small section first, verify traffic holds, then migrate the rest.
Mapping is where migrations succeed or fail.
1:1 mapping (preferred): Each old URL redirects to exactly one new URL with identical or similar content.
/blog/vue-seo-guide → /guides/vue-seo
/products/item-123 → /products/item-123 (unchanged)
Pattern-based redirects: For systematic URL changes. Use regex or route patterns to redirect entire sections.
/blog/:slug → /articles/:slug
/category/:cat/page/:num → /c/:cat?page=:num
Deleted pages: Don't 404 pages that had traffic or backlinks. Redirect to the most relevant alternative. If truly no alternative exists, return 410 (Gone) instead of 404. signals permanent removal.
Server-side redirects are required for SEO. Client-side redirects (JavaScript) don't pass PageRank.
import express from 'express'
const app = express()
// Single redirect
app.get('/old-url', (req, res) => {
res.redirect(301, '/new-url')
})
// Pattern redirect
app.get('/blog/:slug', (req, res) => {
res.redirect(301, `/posts/${req.params.slug}`)
})
// Redirect map for bulk redirects
const redirects = {
'/old-page-1': '/new-page-1',
'/old-page-2': '/new-page-2',
'/company/about': '/about'
}
app.use((req, res, next) => {
const redirect = redirects[req.path]
if (redirect) {
return res.redirect(301, redirect)
}
next()
})
// vite.config.ts
import { defineConfig } from 'vite'
export default defineConfig({
server: {
middleware: [
(req, res, next) => {
const redirects = {
'/old-path': '/new-path',
'/blog': '/posts'
}
const redirect = redirects[req.url]
if (redirect) {
res.writeHead(301, { Location: redirect })
res.end()
return
}
next()
}
]
}
})
// server/middleware/redirects.ts
import { defineEventHandler, sendRedirect } from 'h3'
const redirects = {
'/old-url': '/new-url',
'/blog': '/posts'
}
export default defineEventHandler((event) => {
const redirect = redirects[event.path]
if (redirect) {
return sendRedirect(event, redirect, 301)
}
})
For large redirect maps (1000+ URLs), load from JSON file:
import redirectData from './redirects.json'
export default defineEventHandler((event) => {
const redirect = redirectData[event.path]
if (redirect) {
return sendRedirect(event, redirect, 301)
}
})
Redirect chains happen when URL A redirects to B, which redirects to C. This dilutes PageRank and slows crawling.
Common scenario: You have old redirects (A → B) and add new migration redirects (B → C). Result: chain A → B → C.
Fix: Update all redirects to point directly to final destination. Consolidate old and new redirects into single-hop redirects.
// Bad: creates chain
// Old redirect: /page-v1 → /page-v2
// New redirect: /page-v2 → /page-v3
// Result: /page-v1 → /page-v2 → /page-v3
// Good: consolidate
const redirects = {
'/page-v1': '/page-v3', // direct to final
'/page-v2': '/page-v3'
}
Test redirects before launch: Each should return 301 status and resolve in one hop to 200 (OK).
When URLs change, canonical tags must point to new URLs. Canonical pointing to redirect is a common migration mistake.
After migration, canonical tags should reference new URL structure:
<!-- Bad: canonical points to old URL -->
<link rel="canonical" href="https://example.com/old-url">
<!-- Good: canonical points to new URL -->
<link rel="canonical" href="https://example.com/new-url">
In Vue with Unhead:
useHead({
link: [
{ rel: 'canonical', href: 'https://example.com/new-url' }
]
})
Scan staging site before launch to verify all canonicals point to new URLs, not old ones.
The hour after migration is critical.
Expect these phases:
Week 1-2: Traffic dip of 10-25% is normal. Google discovers redirects and starts reindexing.
Week 3-4: Small sites recover 90%+ of traffic if migration was clean. Large sites still reindexing.
Month 2-3: Most sites reach full recovery. Rankings stabilize. Typical range is 30-60 days.
Month 4-6: Large sites (100,000+ pages) continue recovery. Average across 892 migrations was 523 days. but median is much lower.
Keep redirects for 1+ year: Google recommends at least 12 months. Longer if you still see traffic to old URLs. Never remove redirects that still get hits.
If traffic hasn't recovered after 3 months, audit for:
Redirect chains: As mentioned, these dilute authority. Consolidate old and new redirects into single-hop redirects.
Forgetting internal links: Your site's internal links still point to old URLs, triggering unnecessary redirects. Update internal links to point directly to new URLs.
Not updating canonical URLs: Canonical tags create loops when they point to redirected URLs. Update canonicals alongside redirects.
Removing redirects too early: Traffic sources outside your control (old backlinks, bookmarks, third-party sites) use old URLs indefinitely. Keep redirects for years, not months.
Client-side redirects: JavaScript redirects (window.location) don't pass PageRank. Google may not follow them. Server-side 301s are required.
No redirect testing: Test redirects on staging before launch. Verify each returns 301 and resolves to 200 in one hop.
Forgetting mobile/AMP URLs: If you had separate mobile URLs (m.example.com) or AMP versions, redirect those too.
Nuxt applications should use server middleware for redirects.
For full Nuxt migration guides, see Launch & Listen section.