canonical 写错会怎样:3 个原因 + 修复路径

canonical 写错会让真页进不了索引、或排名 URL 是参数版甚至别人的站,且浏览一切正常只有 Google 知道。本文给三类真实出错写法 + Search Console 验证步骤。

<link rel="canonical"> 是你告诉 Google “如果这页有多个版本,把权重和收录算在哪个 URL 上”。一旦写错,会出现两种致命后果:你的真正想收录的页根本进不了索引,或者搜出来的 URL 是个错的版本(参数版、test 域名、备用语言、甚至别人的网站)。

最难发现的是:网页打开一切正常,只有 Google 知道你在自残。下面三种是真实见过的写法。

常见原因

1. canonical 指向不存在 / 404 / 被 noindex 的 URL

典型现象:

<!-- 模板用了固定路径,但目标 URL 已经被删 -->
<link rel="canonical" href="https://yourdomain.com/legacy/post" />

<!-- 域名换了但模板没更新 -->
<link rel="canonical" href="https://staging.yourdomain.com/article" />

<!-- canonical 指向自己,但自己 noindex -->
<meta name="robots" content="noindex" />
<link rel="canonical" href="https://yourdomain.com/this-very-page" />

Google 拿到 canonical → 抓 canonical URL → 404 或 noindex → 整个组(原页 + canonical)都不进索引。

如何判断:Search Console → 网址检查 → 看”Google 选择的规范网址”是否返回 200。或在终端:

curl -sI "https://yourdomain.com/legacy/post" | head -1
# 期望:HTTP/2 200

2. 跨域 canonical 但没权限或目标不互相引用

跨站 canonical(指到别人的域名或 CDN 域)只有在你确实希望权重转给那个 URL 时才有效,例如 syndication 内容。常见错误:

  • 复制了别站的 HTML 模板,忘了改 canonical 还指向原网站
  • 用了 CDN 子域(cdn.example.com)做镜像,canonical 没指回主域
  • 自己有 www. 和裸域,canonical 半边指 www.、半边指裸域
<!-- 你的页面在 www.yourdomain.com -->
<!-- 错误:自己拆自己 -->
<link rel="canonical" href="https://yourdomain.com/article" />

如何判断:Search Console → “页面”报告里搜 “Duplicate, Google chose different canonical”——这条专门捕这类错误。

3. canonical 与 hreflang / robots / sitemap 互相打架

hreflang 要求每个语言版本互相引用,但 canonical 必须指自己语言版本,否则两套信号互相抵消:

<!-- /zh/article 页面 -->
<link rel="canonical" href="https://yourdomain.com/zh/article/" />
<link rel="alternate" hreflang="zh" href="https://yourdomain.com/zh/article/" />
<link rel="alternate" hreflang="en" href="https://yourdomain.com/en/article/" />
<link rel="alternate" hreflang="x-default" href="https://yourdomain.com/en/article/" />

错误版(canonical 错指英文版)会导致中文版整组从索引消失,只剩英文版排名,且对中文 query 排得很差。

另一类冲突:sitemap 里写 URL A,但页面 A 的 canonical 指 URL B。Google 会优先信 canonical,sitemap 提交浪费。

4. canonical 大小写 / trailing slash / 协议不一致

Google 把以下视为不同 URL:

  • HTTPS://yourdomain.com/Article vs https://yourdomain.com/article
  • https://yourdomain.com/article vs https://yourdomain.com/article/
  • https://yourdomain.com/article vs http://yourdomain.com/article

如果 sitemap、内链、canonical 大小写 / 斜杠不统一,Google 抓 canonical 时跳一次 301 是可以接受的,跳两次就视为弱信号,可能选另一版做主版本。

最短修复路径

Step 1:用爬虫批量抽 canonical,对比正常域名

下面这段脚本可以扫整站 sitemap,把每个 URL 的 canonical 抓出来:

// scripts/audit-canonicals.mjs
import { XMLParser } from "fast-xml-parser";

const sitemapUrl = "https://yourdomain.com/sitemap.xml";
const expectedHost = "yourdomain.com";

const xml = await fetch(sitemapUrl).then((r) => r.text());
const { urlset } = new XMLParser().parse(xml);
const urls = urlset.url.map((u) => u.loc);

for (const url of urls) {
  const html = await fetch(url).then((r) => r.text());
  const m = html.match(/<link\s+rel=["']canonical["']\s+href=["']([^"']+)["']/i);
  const canonical = m?.[1] ?? "(missing)";
  const issues = [];
  if (canonical === "(missing)") issues.push("MISSING");
  else {
    const c = new URL(canonical);
    if (c.host !== expectedHost) issues.push(`CROSS-HOST: ${c.host}`);
    if (c.protocol !== "https:") issues.push("NON-HTTPS");
    if (c.pathname !== new URL(url).pathname) issues.push("PATH-DIFFERS");
  }
  console.log(`${url}\t→ ${canonical}\t${issues.join(",")}`);
}

运行:node scripts/audit-canonicals.mjs > canonicals.tsv。打开看,标红的就是要修的。

Step 2:默认全部 self-canonical,只在必要时指别处

90% 的页面应该指自己。在模板里强制:

<!-- src/layouts/Article.astro 之类的位置 -->
---
const canonical = Astro.url.href;
---
<link rel="canonical" href={canonical} />

只在以下情况指别处:

情况canonical 指向
分页 /blog?page=2/blog
参数变体 /p?utm=x/p
移动子域 m.example.com/pexample.com/p
内容同步(自己原创发别处转)自己的主版本
内容同步(别人原创你转载)别人的主版本

Step 3:canonical 与 hreflang 同时出时,强制配对生成

// 通用 helper
export function buildHreflangAndCanonical(currentLang, slug, langs) {
  const base = "https://yourdomain.com";
  const canonical = `${base}/${currentLang}/${slug}/`;
  const alternates = langs.map((l) => ({
    hreflang: l,
    href: `${base}/${l}/${slug}/`,
  }));
  alternates.push({ hreflang: "x-default", href: `${base}/en/${slug}/` });
  return { canonical, alternates };
}

每个页面都过这个函数,再渲染。这样 canonical = 当前语言自己,hreflang 覆盖所有语言 + x-default,永远闭环。

Step 4:CI 加一层 build 阶段校验

在 prebuild 加:

// scripts/check-canonical-build.mjs
import fg from "fast-glob";
import fs from "node:fs";

const files = fg.sync("dist/**/*.html");
const issues = [];

for (const f of files) {
  const html = fs.readFileSync(f, "utf8");
  const cm = html.match(/<link\s+rel=["']canonical["']\s+href=["']([^"']+)["']/i);
  const robots = html.match(/<meta\s+name=["']robots["']\s+content=["']([^"']+)["']/i);
  if (!cm) issues.push(`${f}: MISSING canonical`);
  if (robots?.[1]?.includes("noindex") && cm) {
    issues.push(`${f}: noindex + canonical (canonical wasted)`);
  }
}
if (issues.length) {
  console.error(issues.join("\n"));
  process.exit(1);
}

让”模板写错了 canonical”在部署前就被拦住。

Step 5:修完后强制重抓

在 Search Console 对 5-10 个最重要的 URL 用”请求编入索引”。完整重新评估通常 1-4 周。

预防建议

  • 模板里只通过一个 buildCanonical() helper 渲染 canonical,杜绝手写
  • 新模板上线前用 audit-canonicals.mjs 全站扫一次
  • CI prebuild 拦截:缺失 canonical、noindex + canonical 共存、跨域 canonical 都报错
  • canonical / sitemap / 内链三处大小写和斜杠保持完全一致,URL 统一走一个 urlFor() 函数

相关阅读

标签: #SEO #Google #Search Console #收录