本文用 Yukiss(一个虚构的手帐文具独立站)来还原一次真实的排查过程。URL 和场景做了替换,技术问题是真实的。
前言
最近在做一个微前端项目的 SEO 排查,第一步打开 Google Search Console,看到的是这个:
URL is not on Google。
不是排名低,不是收录延迟——是这个页面,Google 完全不知道它存在。
我的第一反应是渲染问题。微前端内容依赖客户端 JS 异步加载,Googlebot 应该看不到——这个猜测太合理了,合理到我已经开始盘算 SSR 改造方案了。
但打开 GSC 准备看渲染结果的那一刻,我意识到假设方向就是错的。
这篇文章记录了那次排查的完整过程,以及从中整理出的一个四层诊断框架。如果你也遇到过"页面明明能访问,但 Google 搜不到"这个问题,这个框架可能对你有用。
Google 收录一个页面,是四层顺序执行的链路
在开始排查之前,先把认知基础对齐。
Google 把一个页面收录到搜索结果里,不是一个动作,而是四层顺序依赖的链路:
第一层:链接发现
Google 能知道这个 URL 存在吗?
↓ 通过才进入下一层
第二层:可访问性
Google 能成功拿到这个页面吗?
↓ 通过才进入下一层
第三层:内容渲染
Google 能看到页面里的内容吗?
↓ 通过才进入下一层
第四层:语义理解
Google 知道这个页面在说什么吗?
第一层断了,后面三层根本没有机会执行。
大多数讨论微前端 SEO 的文章,都集中在第三层——渲染延迟、SSR 改造、两波索引机制。这不是没有原因的:渲染问题有大量实验数据支撑(Onely 的实验显示 Googlebot 跟踪 JS 链接比 HTML 链接慢 9 倍),有 Google 官方文档背书,讨论起来有料可说。
但渲染是链路的中间,不是起点。如果从第三层开始排查,很容易在错误的层上消耗精力。
三个断点:从上游到下游逐层排查
第一层:Google 能发现这个 URL 吗?
先找商品详情页的入口在哪里。
Yukiss 的商品目录板块 URL:
https://yukiss.com/shop#products
#products 后面是微前端注入的商品卡片列表,用户能正常看到,点击可以跳转到详情页。看起来一切正常。
但 # 之后的内容,Google 会直接忽略。
这不是 Google 的特殊处理,而是 URL 规范本身的定义:# 后面的 fragment identifier 只在浏览器本地处理,不会发送给服务器,爬虫遵循同样的规则。Google 在 2015 年就正式废弃了曾经支持 hash-based 导航的 hashbang(#!)方案。
所以 Google 实际抓取的是:
https://yukiss.com/shop
商品卡片列表——不存在。详情页的链接——从来没有被发现过。
快速自查:
# 检查入口链接的形式
# 可爬取:标准 <a href> 标签
<a href="/products/ts-B6-grid-2024">查看商品</a>
# 不可爬取:JS click handler
<div onclick="navigate('/products/ts-B6-grid-2024')">查看商品</div>
# 不可爬取:链接藏在 #hash 后面
https://yukiss.com/shop#products → Google 只看到 https://yukiss.com/shop
除了直接检查代码,还可以用 Screaming Frog 爬取站内链接,看爬虫视角下能发现哪些 URL。
第一层,断了。
第二层:Google 能成功访问这个 URL 吗?
假设 sitemap 里收录了详情页的完整 URL:
https://yukiss.com/product-detail?id=ts-B6-grid-2024&type=notebook
尝试去掉参数,直接访问 shell 页面:
curl -o /dev/null -s -w "%{http_code}" https://yukiss.com/product-detail
# 返回:404
404。
这个结果一开始让我有点困惑——用户访问的时候明明是正常的。
后来想明白了:/product-detail 这个路由,在服务端根本不存在。它是微前端在客户端动态处理的路径,服务端没有对应的逻辑。有 ?id= 参数,客户端 JS 能发请求、渲染内容;没有参数,服务端直接返回 404,客户端 JS 根本没有机会运行。
Google 看到 404,会直接跳过,不进行索引。Google 官方文档明确说明:非 200 状态码的页面,渲染可能直接被跳过。
换句话说:即使 sitemap 里收录了带参数的完整 URL,只要 Google 在任何环节拿到了不带参数的 shell URL,就会直接碰壁。
快速自查:
# 去掉所有参数,直接访问 shell URL
curl -o /dev/null -s -w "%{http_code}" https://your-domain.com/page-path
| 返回结果 | 含义 |
|---|---|
| 200,有实质内容 | 这层没问题 |
| 404 | 服务端不认识这个路由,依赖客户端 JS 动态处理 |
| 200,但内容是错误页或空白 | 软 404,Google 可能判定为无效页面不予索引 |
| 需要登录 | Google 无法通过验证,内容不可见 |
这个断点在日常开发中很难被感知到——因为开发者总是带着参数访问页面,从来不会直接访问 shell URL。
第二层,也断了。
第三层:Google 能看到内容吗?
到这里,"URL is not on Google"已经有了足够的解释。但继续往下看,第三层同样有问题。
操作步骤:
- 打开 Google Search Console
- 左侧导航 → 网址检查(URL Inspection)
- 输入要检查的完整 URL(带参数)
- 点击 "查看已抓取的网页(View Crawled Page)"
- 切换到 HTML tab
Ctrl+F搜索页面核心关键词(商品名、标题等)
结果:什么都没有。
商品标题、描述、价格,全部由 JS 在客户端异步拉取。Googlebot 第一波抓到的,是一个几乎空白的 HTML shell:
<!DOCTYPE html>
<html>
<head>
<title>Yukiss</title> <!-- 所有详情页共用同一个 title -->
</head>
<body>
<div id="app"></div>
<script src="/remoteEntry.js"></script> <!-- 微前端入口 -->
</body>
</html>
这和两波索引机制直接相关:Google 第一波拿到空壳,判断需要渲染,放进渲染队列。但渲染队列不是即时的。Onely 的实验数据显示,某些页面的渲染延迟长达数周。即使进入第二波渲染,内容能否完整取回,还取决于 JS 执行成功率、API 响应速度、Google 给该页面分配的渲染资源。
快速自查:
| HTML tab 搜索结果 | 含义 |
|---|---|
| 能找到核心关键词 | 内容在渲染结果里,这层没问题 |
| 找不到 | 内容依赖 JS 异步加载,Googlebot 第一波没有等到 |
| 截图是空白或 loading 状态 | JS 执行失败,或关键资源被 robots.txt 拦截 |
值得注意的是:Google 在 2026 年 3 月更新了 JavaScript SEO 官方文档,移除了之前"JS 对 SEO 不友好"的笼统警告,表明 Googlebot 渲染能力已经成熟。这不是说渲染问题消失了,而是说问题已经从"Google 能不能渲染 JS"转移到了"渲染链路完不完整"——前两层如果没问题,渲染层本身的障碍已经小了很多。
第三层,同样断了。
三层全部断,而且是从最上游开始断的:
断点一:#hash 截断 → Google 从未发现详情页链接
↓
断点二:去掉参数 → 404 → URL 无法独立存在
↓
断点三:纯 CSR → 初始 HTML 是空壳
↓
结果:URL is not on Google
四层诊断框架:可复用的排查顺序
把这次排查过程整理成可复用的形式。这不是解法清单,而是一个排查顺序——先找到断在哪一层,再决定从哪里下手。
第一层:链接发现
核心问题: Google 是通过什么路径知道这个 URL 存在的?
诊断工具:
- GSC → 链接报告,查外部链接和内部链接来源
- Screaming Frog 爬取站内链接,模拟爬虫视角
- 检查 sitemap.xml 是否收录了完整 URL
常见断点:
| 现象 | 原因 |
|---|---|
入口链接在 #hash 后面 | fragment 不传给服务器,Google 看不到 |
| 链接用 JS click handler 生成 | 没有 href 属性,不被识别为可爬取链接 |
| sitemap 只收录了 shell URL | 带参数的详情页 URL 从未被主动提交 |
| 分页内容通过 JS 加载 | 第二页、第三页的链接 Google 可能发现不了 |
第二层:可访问性
核心问题: 不带任何参数直接访问这个 URL,返回什么?
诊断工具:
# 检查 shell URL 状态码
curl -o /dev/null -s -w "%{http_code}" https://your-domain.com/page-path
# 检查 robots.txt 是否拦截了关键路径
curl https://your-domain.com/robots.txt
常见断点:
| 返回结果 | 含义 | 处理方向 |
|---|---|---|
| 404 | 服务端不认识这个路由 | 配置 catch-all,返回 200 + 骨架 HTML |
| 软 404(200 但内容是错误页) | Google 可能判定为无效页面 | 确保 shell 返回有意义的内容 |
| 被 robots.txt 拦截 | 关键资源或路径被误封 | 检查 Disallow 规则 |
第三层:内容渲染
核心问题: Googlebot 渲染完页面后,HTML 里有没有实际内容?
诊断工具:
GSC → 网址检查 → 查看已抓取的网页 → HTML tab → 搜索核心关键词
也可以用 Google 的 Rich Results Test(适用于公开可访问的页面):
https://search.google.com/test/rich-results
常见断点:
| 现象 | 原因 | 处理方向 |
|---|---|---|
| 搜不到核心关键词 | 内容由 JS 异步拉取,第一波没等到 | 考虑 SSR 或服务端预渲染关键字段 |
| 截图是空白 | JS 执行失败 | 检查 JS 报错、资源加载情况 |
| title 所有页面一样 | title 由 JS 渲染,Google 只拿到了默认值 | 服务端输出动态 title |
第四层:语义理解
核心问题: Google 知道这个页面在说什么吗?
诊断工具:
# Rich Results Test
https://search.google.com/test/rich-results
# Schema Markup Validator
https://validator.schema.org/
常见断点:
| 现象 | 原因 | 处理方向 |
|---|---|---|
| 所有详情页 title 相同 | JS 渲染的 title,Google 只读到了 shell 默认值 | 服务端输出 <title> |
| 没有结构化数据 | 缺少 JSON-LD | 添加 Product / Course 类型的 JSON-LD |
| canonical 指向 shell URL | JS 动态设置 canonical,Google 可能只读到初始值 | 服务端输出正确的 canonical |
这一层是四层里修复成本最低的:不涉及架构改动,只需要改 HTML 模板或加 JSON-LD 脚本。即使前三层暂时改不动,第四层也可以立刻做。
一个最小可行的改动:
<!-- 服务端根据 URL 参数注入,不依赖 JS -->
<head>
<title>Indigo A5 Grid Notebook 2024 - Yukiss</title>
<meta name="description" content="手工装订,80g 道林纸,适合日常手帐记录。">
<link rel="canonical" href="https://yukiss.com/product-detail?id=ts-B6-grid-2024&type=notebook">
<script type="application/ld+json">
{
"@context": "https://schema.org",
"@type": "Product",
"name": "Indigo A5 Grid Notebook 2024",
"description": "手工装订,80g 道林纸,适合日常手帐记录。",
"url": "https://yukiss.com/product-detail?id=ts-B6-grid-2024&type=notebook"
}
</script>
</head>
找到断点之后
诊断清楚了,但修不修得动是另一回事。
不同层的修复成本反向于链路顺序:
第四层(语义) → 改 HTML 模板、加 JSON-LD,成本最低,可以立刻做
第三层(渲染) → 引入 SSR 或边缘预渲染,工程量较大
第二层(可访问)→ 改服务端路由配置,需要跨团队协作
第一层(发现) → 改路由架构或 CMS 结构,成本最高
一个实用的思路:从最下游开始修,能改的先改。 哪怕上游的断点暂时动不了,至少把下游的层做干净。
另外有一件值得单独说的事:微前端架构里,这四层往往分属不同团队——路由在 CMS 侧,渲染在前端团队,服务端配置在运维,meta 和结构化数据在内容团队。每个团队都在做自己那一层,但没有人在 track 整条 SEO 链路的健康状态。
这不是哪个团队的问题,而是一个值得提前想清楚的问题:这个页面的 Google 可见性,应该由谁来 track,用什么指标衡量,出了问题谁先知道?
如果这个问题没有答案,断点可能存在很久,才会有人注意到。
总结
遇到"URL is not on Google",先不要假设断点在渲染层。按这个顺序从上游开始查:
能发现吗 → 能访问吗 → 能看到内容吗 → 能理解语义吗
找到断在哪一层,再决定从哪里下手。
这个排查顺序是从这次经历里提炼的,理论上适用于更广的场景,但我还没有在其他项目上完整跑过——如果你用这个框架查出了不一样的断点,欢迎在评论区交流。
参考资料
- Understand JavaScript SEO Basics — Google Search Central — Google 官方文档,说明 Googlebot 两波索引机制及 2026 年 3 月的最新更新
- URL Structure Best Practices — Google Search Central — fragment identifier 和 query parameter 的官方处理说明
- Dynamic Rendering — Google Search Central — Google 对动态渲染的官方立场:workaround,推荐 SSR / 静态渲染
- Google Needs 9X More Time To Crawl JS Than HTML — Onely — JS 链接爬取速度比 HTML 慢 9 倍的实验数据