读者点你文章底部的”相关:GPT Tips”——落到 404。你 6 个月前把那个 slug 改成了 chatgpt-tips,但没回头更新 47 篇链到旧 gpt-tips 的文章。Search Console 默默记着 404——Google 开始把”链向破坏邻居”的页面从索引里丢。你的内部 PageRank 图从里头烂掉了。
内链腐烂是无声的:build 不挂、页面正常渲染、坏链躺在文章底部多数读者根本不滚到的位置。但每一条悬空链都是泄漏的权威信号 + 更差的用户体验。修法:CI 跑链检查(linkinator 或 lychee)、维护 redirects.json 配重命名 slug、prebuild 在任何内链指向不存在 slug 时 fail。
常见原因
1. 改了 slug 没更新引用方
你把 gpt-tips.mdx 改成 chatgpt-tips.mdx——所有指向 /en/articles/gpt-tips/ 的文章现在 404。无 build error、无重定向、无 warning。
如何判断:grep 旧 slug。
grep -r "/articles/gpt-tips/" src/content/articles/ | wc -l
2. 删了一篇文章没查入链
你把一篇薄文废了。43 篇还链着它——这些链现在 404。
如何判断:删前扫入链。
grep -r "/articles/SLUG-TO-DELETE/" src/content/articles/
非零——必须更新或重定向。
3. Markdown 链接目标拼错
你写了 /en/articles/chatgpt-tipss/(多了一个 s)——页面照常渲染、build 不校验、用户点完 404。
如何判断:只有真链接检查器抓得到。用 frontmatter urlSlug 做正则比对得到合法 slug 全集。
4. 外链腐烂(第三方网站搬了页)
SEO 影响小,但 UX 仍然糟。你链到 https://example.com/great-article/,对方重构——现在 302 到首页或 404。
如何判断:linkinator 或 lychee 加 --check-external。
5. 重命名章节的 anchor-only 链接
你链到 /en/articles/foo/#step-3。文章被重写——## Step 3 现在叫 ## Step 3: Verify,anchor slug 变成 step-3-verify——anchor 404 但悄无声息:页面加载但只跳顶。
如何判断:anchor 检查器更少;lychee 加 --include-fragments 能抓。
最短修复路径
Step 1:在构建产物上跑 lychee 或 linkinator
装并跑 lychee:
# https://github.com/lycheeverse/lychee
brew install lychee
npm run build
lychee --offline --include-fragments dist/**/*.html > link-report.txt
或 JS 原生选 linkinator:
npx linkinator dist --recurse --silent --skip "^https://(facebook|twitter)" > link-report.txt
输出每条悬空 URL——按入链数排序优先处理。
Step 2:为改名 slug 建 redirects 文件
把 gpt-tips 改成 chatgpt-tips——对的答案是 permanent 301,不是重写每个 linker。维护 public/_redirects(Netlify/Cloudflare)或 astro.config.mjs 的 redirects:
// astro.config.mjs
export default defineConfig({
redirects: {
'/en/articles/gpt-tips/': '/en/articles/chatgpt-tips/',
'/zh/articles/gpt-tips/': '/zh/articles/chatgpt-tips/',
},
});
301 保留权威。redirect 上线后悬空链还能用、Google 转发 link equity——可以慢慢修文本(也可以不修)。
Step 3:prebuild 在内链断链直接 fail
重定向救改名;prebuild 检查抓拼写和删除。用 frontmatter 建 slug 索引,校验正文每条内链:
# scripts/check-internal-links.mjs
import fs from "node:fs";
import path from "node:path";
import matter from "gray-matter";
const validSlugs = new Set();
const articleDirs = [
"src/content/articles/en/troubleshooting",
"src/content/articles/zh/troubleshooting",
];
for (const dir of articleDirs) {
for (const f of fs.readdirSync(dir)) {
const { data } = matter(fs.readFileSync(path.join(dir, f), "utf8"));
if (data.urlSlug) validSlugs.add(`/${data.lang}/articles/${data.urlSlug}/`);
}
}
let broken = 0;
for (const dir of articleDirs) {
for (const f of fs.readdirSync(dir)) {
const txt = fs.readFileSync(path.join(dir, f), "utf8");
const links = [...txt.matchAll(/\(\/(?:en|zh)\/articles\/([^)#]+)\/?\)/g)];
for (const m of links) {
const url = `/${m[0].split("/")[1]}/articles/${m[1]}/`;
if (!validSlugs.has(url)) {
console.error(`BROKEN in ${f}: ${url}`);
broken++;
}
}
}
}
process.exit(broken > 0 ? 1 : 0);
接到 prebuild——PR 落不了悬空内链。
Step 4:按批修复存量
每个断链目标:
- 改名:加 redirect,正文不动
- 已删但内容仍有价值:从 git 恢复
- 主动删除:grep-and-sed 批量更新 linker——指向他处或剥链
不要留 404——要么 redirect 要么重写。
Step 5:重提 sitemap + 请求重抓
修完后在 Search Console 重提 sitemap、对最坏的几页请求 indexing——让 Google 重抓看到干净邻居。
预防
- 每个 PR 在 CI 跑 lychee 或 linkinator——内链断链 fail
- prebuild 内链校验器对 frontmatter slug 索引
- 改 slug 必须同 PR 加
redirects条目——lint 规则强制 - 删除清单:先扫入链、redirect 或重写再 merge
- 外链检查每周跑一次(非每 PR——太抖)允失败
- 季度回顾 redirects 表:折叠链路(A -> B -> C 改成 A -> C)
相关
- Content Site Sitemap Not Resubmitted After Big Changes
- Content Site Translation Pages Mismatched
- Internal Links Uneven Across Site
- Orphan Content Pages
- Search Console Low Value URLs
- Content Site Tag Orphan Page Zero Articles
标签: #内容运营 #站点质量 #站点审计 #排查 #broken-link