换域名后 canonical 还是老域名

改了主域名,但 canonical 还指老的——通常是配置 + 缓存。

主域名从 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: HITx-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 -Icf-cache-statusMISSEXPIRED,再 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 提交”地址变更”

  1. 在 GSC 新增 https://newsite.com 属性并验证。
  2. 在老属性 https://oldsite.com 的 设置 → 地址变更 里选择新属性,按向导提交。
  3. 对前 20 个核心 URL 用”URL 检查 → 请求编入索引”手动触发抓取。
  4. 提交新域名的 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 充足迁移时间

相关阅读

标签: #部署 / 托管 #排查 #排查 #SEO