内链审计显示 80 页 0 入链。它们确实存在(在 sitemap 里、在磁盘上、技术上可达),但站内没有任何别的页面引用它们。用户只能靠手输 URL 进入。Google 把它们当成低重要性页面:站点自己都不链,自然拿不到内链传递的上下文,也分不到内部 PageRank。
最快的修法: 跑一份真正的孤儿报告(用爬虫或下面的脚本),然后对每个孤儿做一个二选一决定——要么整合它(从真正相关的文章里加 2-5 条正文内链),要么清除它(该消失就 410,该保留但不排名就 noindex)。不要想着救活每一个孤儿,有些就该被清掉。
孤儿不只是「索引债」。内链是 Google 判断「你认为哪些页面重要」最强的站内信号之一。John Mueller 多次说它「super critical」(极其关键),而距首页超过约三次点击的页面,通常被爬得更少、被索引得更慢。一个 0 入链的页面没有内部上下文、没有锚文本信号、也没有链接权重,所以即使内容本身没问题,也常常停在 Google Search Console 的 Crawled - currently not indexed(已抓取但未编入索引)。下面讲:怎么找出真正的孤儿、逐页决定、以及如何不再制造新孤儿。
常见原因
按命中率从高到低排。
1. 只顾「多发」、没有 cluster 策略
你两年来每月排 5 篇。编辑流程里从没人问过「现有哪些文章应该 link 到这篇?」于是每篇都孤儿落地,积压越来越多。
如何判断:孤儿按批量发布日期聚集。如果一大批孤儿集中在同一个发布周,那就是发布管道造出来的。
2. 转载或客座文章从没被链进来
来自旧的同步(syndication)协议或客座作者 import 的文章。它们被「加进来」了,但没人写从现有内容指向它们的内链,所以从没真正整合。
如何判断:孤儿的作者不同,或来自与你当前发布节奏对不上的日期段。Import 了却没整合。
3. 前任站长 / 旧方向留下的页面
站点转了方向(从「通用 tech」转到「AI 提效」)。旧的不相关话题文章还在,但不够相关,现有内容不会去链它们。它们靠这次话题转向把自己孤立了。
如何判断:孤儿都在你站点已经不再覆盖的话题上。属于「残余」内容。
4. 文章落在已经缩水的分类里
你启动了一个分类、发了 5 篇,本想做大却没做。分类页本身很薄、入链很少,于是分类里的文章被这个垂死的父页面孤立了。
如何判断:孤儿集中在某一个分类,而那个分类页自己的入链也很低。一根枯枝。
5. URL 改名但没更新内链
你把 getting-started-with-x 改成了 x-quickstart。新 URL 存在,但没东西链它,因为所有内链还指向旧 slug。301 redirect 对用户有效,但从 Google 视角看,新页面是孤儿。
如何判断:旧的(被重定向的)URL 入链很多,但最终目的地 URL 入链是 0。内链必须直接指向 canonical 目的地。
6. tag 页、归档页或「仅分类」页
只通过 tag / 归档导航出现、正文从不链的页面。这些是自动生成的,所以即使导航能到达,它们在「刻意的正文链接」层面仍然是孤儿。
如何判断:tag / 归档页正文引用为 0。靠自动 nav 存在,但没有任何文章刻意去指它们。
最短修复路径
按收益从高到低排。Step 1 探测,Step 2 逐页决定。
Step 1:爬一遍,找出真正的孤儿
可靠的做法是用一个爬虫,把你的抓取图(crawl graph)和外部的「这个 URL 有流量 / 展示」数据合在一起,这样才能抓到那些「存在、甚至略有排名,但没有内部路径」的页面。
用 Screaming Frog SEO Spider(业界标准工具,截至 2026 年 6 月):
Configuration > Spider > Crawl,打开 XML Sitemaps 选项卡,勾选 Crawl Linked XML Sitemaps 并填入你的 sitemap URL。- 在
Configuration > API Access连接数据源:Google Search Console 和 Google Analytics 4(GSC 最有用,它会补上那些有展示量但没有内链的 URL)。 - 跑抓取(点 Start),等它跑完。
- 跑 Crawl Analysis(
Crawl Analysis > Start)——这一步最容易被忘掉;不跑它,孤儿报告就是空的。 - 打开
Reports > Orphan Pages。导出的就是那些出现在 sitemap、GSC 或 GA4 里、但从起始 URL 没有内链路径的页面。
或者自己写一个(对每篇文章都是一个文件的静态 / Astro 站点很好用):
// scripts/find-orphans.mjs
import fs from "node:fs";
import path from "node:path";
import matter from "gray-matter";
const DIR = "src/content/articles/en";
// 1. 从 frontmatter 收集每篇文章的 slug
const files = fs.readdirSync(DIR, { recursive: true })
.filter((f) => f.endsWith(".mdx"))
.map((f) => path.join(DIR, f));
const slugs = files.map((f) => {
const { data } = matter(fs.readFileSync(f, "utf8"));
return data.urlSlug;
});
// 2. 收集任意正文里的每一条 /en/articles/<slug>/ 内链
const linkedTo = new Set();
for (const f of files) {
const body = fs.readFileSync(f, "utf8");
for (const m of body.matchAll(/\/en\/articles\/([^/\s)"']+)\//g)) {
linkedTo.add(m[1]);
}
}
// 3. 没有入链(且不是只链自己)的 slug 就是孤儿
const orphans = slugs.filter((s) => !linkedTo.has(s));
console.log(`Orphans: ${orphans.length}`);
console.log(orphans.sort().join("\n"));
这种基于文件的检查能抓到正文孤儿,但抓不到「只被自动生成的 widget 链接」的页面。出于质量考虑,应把 widget-only 的链接当成「不算数」的入链:在统计前排除生成的「相关」标记,或只统计文章正文内的链接。然后过滤掉刻意做成独立页的页面(付费广告落地页、法律页等),这些本来就该是孤儿。
Step 2:每个孤儿决定整合还是清除
| 孤儿类型 | 动作 |
|---|---|
| 近 6 个月、对话题、质量过得去 | 整合:加 2-5 条正文内链 |
| 对话题但偏老 / 偏薄 | 先升级内容,再整合 |
| 偏离话题、前方向残余 | 410(想留着就 noindex) |
| 转向残余、话题彻底放弃 | 410 |
| 重复 / 被更好的文章取代 | 301 到后继文章 |
| tag / 归档页,无正文引用 | 真的仅 nav 用就可接受,否则 noindex |
不要想着全部整合。有些孤儿就该被清掉。
Step 3:整合时,每个孤儿加 2-5 条入链
对每个你决定保留的孤儿:
- 找 5-10 篇真正相关话题的已有文章(同 tag 或同分类是不错的起点)。
- 在其中 2-5 篇里,往一个「配得上这条链接」的句子中加一条指向该孤儿的正文链接。
- 用描述性锚文本(孤儿的主题),不要用「点这里」或裸 URL。
- 确认链接在正文内容里,而不只是在生成的 widget 里。
目标大约是来自 3-5 篇不同源文章的 3-5 条入链。这一轮之后,孤儿就有了真正的内部上下文。
Step 4:清除时,刻意选 410、noindex 还是 301
410 Gone -> 页面不该存在(legacy、偏离话题、无后继的重复页)
Google 去除索引比 404 更快,因为 410 是明确的「永久消失」信号。
404 也能去索引,只是更慢。
noindex -> 让 URL 保持上线、可达,但不进搜索
加 <meta name="robots" content="noindex">(或 X-Robots-Tag 响应头)。
关键:该页面绝不能在 robots.txt 里被屏蔽。一旦被屏蔽,Googlebot
就抓不到这个页面,也就看不到 noindex,URL 反而可能继续留在索引里。
要让它能被抓取、让 Google 读到标签。
标签要永久保留——过早移除会让页面重新被索引。
301 redirect -> 有明确的后继文章
重定向到最接近的话题替代页,不要重定向到首页
(批量重定向到 "/" 会被当成软 404 而被忽略)。
关于静态托管的一点说明:删掉文件通常返回 404,而不是 410。Firebase Hosting 的 redirects 块无法返回 410——它的 redirects 只接受 3xx 状态码(截至 2026 年 6 月)。想返回真正的 410,需要 rewrite 到一个 Cloud Function / Cloud Run 处理器来设置状态码,或者接受「更慢但同样有效」的 404。在 Vercel 上,你可以从 serverless / edge function 或 vercel.json 的路由处理器返回 410。
Step 5:让「相关文章」widget 暴露孤儿
只看相关性的 widget 往往会反复 surface 高入链文章(一种让新页面挨饿的网络效应)。给低入链页面加一个权重,让它们在真正相关时也能拿到 widget 位:
function relatedScore(current, candidate) {
return sharedTags(current, candidate) * 3
+ sameCategory(current, candidate) * 5
+ (candidate.inboundCount < 3 ? 4 : 0); // 给孤儿加分
}
记住 Step 1 的结论:widget 链接弱于正文链接。用 widget 加权帮助分布,但整合时仍要补上真正的正文内链。
Step 6:每月重爬,及早抓到新孤儿
加个日历提醒或 CI job:
# 每月重跑孤儿探测,写出带日期的报告
node scripts/find-orphans.mjs > reports/orphans-$(date +%Y-%m).txt
如果数字逐月往上爬,说明你的发布工作流没在整合新文章。加一道整合关卡(见「预防」)。
如何确认已修好
- 重跑 Step 1。该批次的孤儿数应为 0(排除刻意做成独立页的页面)。
- 对每个被重新整合的页面,确认入链在文章正文里,而不只是在 widget 里。
- 在 Google Search Console 里,盯几周受影响 URL 的 Pages > Crawled - currently not indexed 和 Discovered - currently not indexed;连接良好的页面应逐步走向被索引。
- 对被清除的页面,用
curl -I https://yoursite.com/old-url/验证状态码——删除的页面要410(或404),重定向要301且Location合理,绝不能在你想清除的页面上看到200。
预防
- 编辑关卡:每篇新文章在发布前,必须从 3 篇现有文章加链接指向它。
- 每月做一次孤儿审计;任何超过 60 天的孤儿,要么整合、要么清除。
- 相关文章 widget 给低入链页面加权,随时间帮助分布。
- 话题转向时,刻意把偏离话题的残余页
noindex或410,而不是留作孤儿。 - 改 URL 名时,搜索并更新每一处内部引用。不要靠
301去掩盖缺失的内链。 - 280 篇连接良好的文章,胜过 500 篇里有 80 个孤立的。
常见问题
已经在 sitemap 里的页面,还算孤儿吗? 算。Sitemap 有助于发现,但只是个弱信号。Google 表示它把仅出现在 sitemap 里的 URL 当作低置信度,可能会一直留在 Crawled - currently not indexed。页面需要的是内链,而不只是一条 sitemap 记录,才能拿到真正的重要性。
「相关文章」widget 里的链接算入链吗? 对抓取发现来说算,但比带描述性锚文本的编辑正文链接弱得多。出于质量考虑,把「只有 widget 链接」的页面当成实质上的孤儿,再补上真正的正文内链。
刻意删掉的页面,用 410 还是 404?
能用 410 就用 410。Google 把 410 Gone 当成明确的永久移除信号,去索引比 404 更快。404 也能去索引,只是更慢。404 留给意外丢失 / 本就不该存在的 URL。
我加了 noindex,但页面还在 Google 里,为什么?
最常见的原因是这个 URL 同时被 robots.txt 屏蔽了。如果 Googlebot 抓不到页面,它就看不到 noindex 标签,URL 反而可能继续留在索引里。去掉 robots.txt 里的 disallow,让页面能被抓取,保留 noindex 标签,然后等一次重爬。
我能不能干脆把所有孤儿都重定向到首页?
不能。把不相关的页面重定向到 / 会被当成软 404,该重定向在排名上基本被忽略。只在有真正等价后继页时才 301,否则就整合或 410。
一个页面到底需要多少入链? 没有魔法数字,但来自相关文章的 3-5 条上下文正文链接是一个稳妥的下限,能给页面带来锚文本上下文,并把抓取路径拉到距某个 hub 三次点击以内。链接的相关性和位置,比数量更重要。