主域名从 oldsite.com 切到 newsite.com,DNS 也指过去了,新域名能正常访问,但 view-source 打开线上页面,里面的 <link rel="canonical" href="https://oldsite.com/..."> 还是老域名——Search Console 接下来会继续把老域名当主体索引,搜索流量始终回不到新域名。这种 bug 通常不是一个原因,而是”代码里硬编码 + CDN 缓存 + Google 没重爬”三层叠加。本文按这三层逐个清理。
常见原因
按命中率从高到低排。
1. 站点根 URL 在代码里硬编码
astro.config.mjs / next.config.js / 模板里直接写死了 https://oldsite.com,build 时把这个值嵌进每个页面的 canonical / og:url / sitemap.xml。哪怕宿主域名换了,HTML 还是老的。
典型代码:
// astro.config.mjs
export default defineConfig({
site: 'https://oldsite.com', // ← 这里没改
});
如何判断:grep -r "oldsite.com" src/ astro.config.mjs next.config.js public/ 看还有几处残留。
2. CDN / 宿主缓存了旧 HTML
代码改完重新部署,但 Cloudflare / Vercel Edge / Fastly 还在 serve 上一版本的缓存 HTML。curl -I 看到 cf-cache-status: HIT 或 x-vercel-cache: HIT,说明拿到的是缓存。
如何判断:curl -s https://newsite.com/some-page | grep canonical 看到的还是 oldsite,再 curl -I 看缓存命中头。
3. Sitemap 和 robots.txt 没同步更新
sitemap.xml 还在列 https://oldsite.com/...,提交给 Google 后它继续按老 URL 抓。robots.txt 里如果写了绝对路径 sitemap 引用,老地址也一并漏了。
如何判断:curl https://newsite.com/sitemap.xml | head -50,看 <loc> 字段是不是新域名。
4. 301 重定向只配了一半
把 oldsite.com 全量 301 到 newsite.com 做好了,但 Google 抓到新域名页面里 canonical 又指回 oldsite.com,就形成”301 → canonical → 301”循环,Google 干脆都不收录。
如何判断:curl -I https://oldsite.com/page 看 301 目标,再 curl https://newsite.com/page | grep canonical 看 canonical,两者方向相反就是循环。
5. Search Console 没改主属性
GSC 里的属性还是 https://oldsite.com,没新增 https://newsite.com 属性、也没在”地址变更”工具里告诉 Google。Google 抓得到新域名,但不知道它和老域名的关系,迁移信号传不过去。
如何判断:登 Search Console,看属性列表是否同时有两个域名,并检查”设置 → 地址变更”是否已提交。
最短修复路径
Step 1:全局搜老域名并改为单一 env var
仓库里 grep 一遍:
grep -rIn "oldsite\.com" . \
--exclude-dir=node_modules \
--exclude-dir=.git \
--exclude-dir=dist
把所有硬编码改成读 env var:
// astro.config.mjs
export default defineConfig({
site: process.env.SITE_URL || 'https://newsite.com',
});
// src/lib/seo.ts
export const SITE_URL = import.meta.env.SITE_URL || 'https://newsite.com';
// 模板里
<link rel="canonical" href={`${SITE_URL}${Astro.url.pathname}`} />
宿主(Vercel / Netlify / Cloudflare)设环境变量 SITE_URL=https://newsite.com,preview 和 production 都设上。
Step 2:重新 build + 部署 + 验证产物
# 本地先验
npm run build
grep -rIn "oldsite.com" dist/ || echo "clean"
# 部署到生产后再验
curl -s https://newsite.com/ | grep -E '<link rel="canonical"|og:url'
应该全部是 https://newsite.com/...。
Step 3:清 CDN / 宿主缓存
- Cloudflare:dashboard → Caching → Configuration → Purge Everything
- Vercel:Deployments → 最新 deploy → Redeploy(不勾选 build cache);Edge cache 会随新 deploy 自动失效
- Cloudflare Pages:Settings → Builds & deployments → Purge cache
- Netlify:Deploys → Clear cache and deploy site
清完再 curl -I 看 cf-cache-status 是 MISS 或 EXPIRED,再 curl 看 canonical 是否已经是新域名。
Step 4:更新 sitemap、robots、301 重定向
public/robots.txt:
User-agent: *
Allow: /
Sitemap: https://newsite.com/sitemap.xml
老域名上配全量 301 到新域名(保留路径),Cloudflare Page Rule / Vercel vercel.json / Nginx 任选:
// vercel.json (在 oldsite 项目上)
{
"redirects": [
{ "source": "/(.*)", "destination": "https://newsite.com/$1", "permanent": true }
]
}
验证:
curl -I https://oldsite.com/some-page
# HTTP/2 301
# location: https://newsite.com/some-page
Step 5:在 Search Console 提交”地址变更”
- 在 GSC 新增
https://newsite.com属性并验证。 - 在老属性
https://oldsite.com的 设置 → 地址变更 里选择新属性,按向导提交。 - 对前 20 个核心 URL 用”URL 检查 → 请求编入索引”手动触发抓取。
- 提交新域名的 sitemap:
https://newsite.com/sitemap.xml。
Google 完成迁移信号传递通常要 4-8 周,期间老域名 301 必须保持在线。
预防建议
- 站点根 URL 只从一个 env var 读,杜绝任何文件里写死域名
- CI 加一条规则:
grep -r "oldsite.com" src/命中就 fail - 每次大改动(域名、路由结构、canonical 模板)后跑一遍 canonical 冒烟测试,抽 10 个核心 URL 看 source 里的 canonical 是否正确
- sitemap.xml 用
@astrojs/sitemap等插件按site自动生成,不要手写 - 老域名 301 至少保留 6 个月,给 Google 充足迁移时间