JavaScript 渲染的内容没进 Google 索引

SPA 在用户看来一切正常,但 Search Console 显示页面被索引时 body 是空的。Googlebot 渲染的坑在哪、最快怎么修。

你上线了一个 React/Vue/Svelte SPA。URL 在 Google 索引里。但搜页面正文里一段独特的话,啥都搜不到。打开 Search Console,跑 URL Inspection,点 Test live URL,再点 View tested page,再切到 HTML 标签页。body 几乎是空的,或者只有一个 loading spinner。Googlebot 拿到了壳,没拿到内容。同样的问题还会表现成”已索引但零曝光”、页面什么词都排不上、或者明明页面上看得见的内容却过不了 rich result 校验。

最快的修法:只要内容对搜索重要,就在服务端渲染(SSR)或预先构建(SSG),让它出现在原始 HTML 里,而不是事后靠 JavaScript 画出来。下面的内容是在你没法立刻上 SSR/SSG 时,用来诊断渲染轮为什么失败的。

Googlebot 到底怎么渲染 JavaScript(截至 2026 年 6 月)

Google 用两轮来索引 JS 页面:

  1. 抓取(Crawl)——Googlebot 取回原始 HTML,把这份响应里已经有的文字、链接、metadata 直接索引。
  2. 渲染(Render)——页面被排进 Web Rendering Service(WRS)的队列,那是一个常青版(evergreen)的无头 Chromium。它执行你的 JavaScript,等网络静下来,再重新读一遍生成出来的 DOM。

有两件事决定你的内容能不能在这一轮里活下来:

  • 不存在硬性的”5 秒渲染上限”。 这个数字是个误传,来自一个旧的中位数统计。Google 的 Martin Splitt 说过,约 99% 的页面在几分钟内就渲染完,而那个常被引用的 5 秒是抓取到渲染的中位间隔,不是每个页面的截止时间。所以”再多等等”几乎从来都不是真正的解法。内容缺失意味着渲染失败了或者根本没触发,不是”超时了”。
  • WRS 不会跟页面交互。 它不点击、不滚动、不悬停、不切换标签页,也不会闲到足以触发懒加载逻辑。按 Google 自己的 JavaScript SEO 文档,只在用户操作后才出现的内容它看不到。这是下面最常见的一个原因。

常见原因

1. 内容只在用户操作之后才拉

组件看起来是挂载时拉数据 useEffect(() => fetch('/api/article')),但实际上是在 requestIdleCallback、scroll handler、IntersectionObserver 或点击之后才触发。Googlebot 从不触发这些。

怎么判断:在 Chrome DevTools 里打开页面,在 Console 里快速确认没有 scroll 或 idle 回调在卡住你的 fetch。或者把 Network throttling 调到 “Slow 4G”,啥都别动等 5 秒。如果 body 不会自己画出来,Googlebot 看到的也是同样的空白。

2. 渲染依赖的 API 被 robots.txt 挡了

HTML 加载了,JS 调 /api/data。但 /api/*robots.txtDisallow: 下面。Google 文档说得很明确:“Google Search won’t render JavaScript from blocked files or on blocked pages。” 拿不到 API 响应,就没有渲染出来的内容。

怎么判断:打开 https://yoursite.com/robots.txt,确认页面渲染依赖的数据端点没被 disallow。在 URL Inspection 里,View tested page → More info → Page resources 会列出 Googlebot 尝试的每个 fetch,并标出加载失败的那些。

3. JavaScript 报错把渲染搞挂

一个像 Cannot read properties of undefined (reading 'map') 的运行时错误让 hydration 中断。壳画出来了,数据层没有。WRS 不会重试你的应用;一个没接住的 throw,内容就没了。

怎么判断:在 URL Inspection 里跑 live test,然后 View tested page → More info → JavaScript console messages。(这个面板和渲染截图只在 live test 里有,已索引的快照里没有。)那里任何一条 error 就是你的渲染挂掉的地方。

4. 渲染太重或太慢,没跑完

WRS 不是无限耐心的。阻塞渲染的第三方脚本、几兆大的 bundle、或者一串慢 API 调用,都可能让 WRS 抓取 DOM 时内容还没画出来。这是资源问题,不是固定时钟。

怎么判断:跑 Lighthouse。LCP 超过约 4 秒、或 Total Blocking Time 超过约 600ms,说明关键内容出现得太慢、有风险。把 URL Inspection 里的渲染截图和你的线上页面对比一下。

5. 客户端路由从不更新 title 和 meta

用户通过 SPA 路由访问 /article/footitlemeta 还停在首页的值,因为路由从没更新它们。Googlebot 索引第二个 URL 时拿的是首页 metadata,于是这个页面只会”排”在首页的主题上。

怎么判断:连点 3 篇文章,每次切换后看 DevTools Elements 面板里的 title。如果不变,说明路由没更新 document.title

6. Service worker 给 Googlebot 发了旧内容

PWA service worker 缓存了一个跟当前 API 不兼容的旧 bundle。登录用户走 cache-bust 拿到新副本,Googlebot 走普通 fetch 撞上坏掉的缓存版本。

怎么判断:在 DevTools 里打开 Application → Service Workers,点 “Unregister”,然后硬刷新,看没缓存的渲染结果。再把 User-Agent 设成 Googlebot/2.1 的请求和普通浏览器请求对比;HTML 不一样就说明 service worker 或 CDN 在分流。

7. 纯 CSR,没有给爬虫的 fallback

HTML 就是 <div id="root"></div> 加一个 script 标签。渲染轮是看到内容的唯一机会,所以上面 1 到 4 任一出问题,索引里就是永久空白。

怎么判断:跑 curl https://yoursite.com/article/foo。如果不管 URL 是啥都返回同一个壳,就说明没有预渲染 HTML、没有 SSR——每个页面都全押在一次零失误的渲染上。

你属于哪一类?

URL Inspection 里的症状最可能的原因看哪一步
Rendered HTML 有壳但没有正文fetch 卡在交互上(1)或 JS 报错(3)第 1、3 步
Page resources 显示某个 /api/... fetch 被挡或失败robots.txt 或 CORS 挡了 API(2)第 4 步
JavaScript console messages 有运行时错误渲染崩了(3)第 1、3 步
渲染截图是空白/spinner,Lighthouse LCP 高渲染太慢/太重(4)第 3 步
正文索引正常,但每个 URL 的 title/meta 是错的路由没更新 head(5)第 5 步
curl 对每个 URL 返回同一个壳纯 CSR,没有 fallback(7)第 2 步

最短修复路径

第 1 步:先确认 Googlebot 到底渲染出了什么

Search Console → URL Inspection → Test live URLView tested pageHTML。这个标签页就是 Googlebot 跑完你的 JavaScript 之后的 DOM——不是 curl 拿到的,也不是浏览器看到的。同一个 live test 里再打开 More info → JavaScript console messagesPage resources;它们只在 live test 里有数据,会立刻告诉你失败到底是崩溃、是资源被挡、还是内容根本没触发。

第 2 步:上服务端渲染或预渲染

最稳的修法是别再让需要被索引的内容靠客户端渲染:

  • SSR——Next.js getServerSideProps 或 React Server Components、Nuxt useAsyncData、SvelteKit +page.server.ts
  • SSG——Astro、Next.js generateStaticParams / 静态导出、Hugo。适合不随每次请求变化的内容。
  • Prerender——Prerender.io,或自己托管一个 Rendertron 快照发给爬虫。注意 Google 现在把 dynamic rendering 称为 “a workaround and not a long-term solution”,所以把它当成通往 SSR/SSG 的过渡,而不是终点。
// Next.js(Pages Router):文章页 SSR
export async function getServerSideProps({ params }) {
  const res = await fetch(`https://api.example.com/articles/${params.slug}`);
  const article = await res.json();
  return { props: { article } };
}

第 3 步:关键内容不靠交互就能渲染

把数据拉取从 requestIdleCallback、scroll handler、IntersectionObserver 里挪出来。标题、正文、结构化数据必须在服务端响应里,或者在加载时就跑的、无门槛的 useEffect/onMounted 里拉。给渲染加好兜底,让 fetch 失败时显示真实文字而不是永久 spinner;用可选链(optional chaining)包住数据访问,这样一个缺失字段不会 throw 把整个渲染搞挂。

第 4 步:放行渲染依赖的 API 端点

如果客户端渲染没法避免,就别 Disallow: 页面读取数据的那些端点:

User-agent: Googlebot
Allow: /api/articles/
Allow: /api/categories/
Disallow: /api/admin/

同源 API 最省事;如果 API 在另一个域名上,确认它对这次 fetch 返回了宽松的 CORS 头,因为 CORS 失败在渲染结果里看起来跟资源被挡一模一样。

第 5 步:每次客户端路由切换都更新 title 和 meta

继续做纯客户端 SPA 的话,用 React Helmet / Next Head、Vue 的 useHead@unhead/vue),或者在每次路由切换时直接更新 document.title 和 meta:

router.afterEach((to) => {
  document.title = to.meta.title || 'Site Name';
  document
    .querySelector('meta[name="description"]')
    ?.setAttribute('content', to.meta.description || '');
});

第 6 步:批量测渲染,别一个 URL 一个 URL 地看

只看一次 URL Inspection,发现不了在 30% 页面上才出问题的模式。用能渲染 JS 的爬虫——Screaming Frog 开 JavaScript 渲染、Sitebulb、或者一个 Puppeteer 脚本——爬 100+ 个 URL,把 rendered HTML 和原始 HTML 做 diff。差异大的(大量内容只在渲染后才出现的)就是有风险的页面。

第 7 步:确认修好了,再盯 Search Console

确认方法:重跑 URL Inspection 的 live test,确认 HTML 标签页现在含有你的正文和正确的 title。如果上了 SSR/SSG,再 curl 一下这个 URL,确认内容在原始响应里。然后 request indexing。修完之后给 1 到 3 周,“Crawled - currently not indexed” 的数量回落、正文 query 的曝光开始出现,具体多久取决于 Google 多久重爬一次这个版块。

容易误判的情况

误以为是内容质量问题。页面看起来按 URL 被索引了,于是很容易想去重写文章。但如果正文从没进索引,Google 根本不知道这页讲啥,那么文笔再好它也什么词都排不上。动内容之前,永远先看 rendered HTML。

哪些情况不是你代码的锅

有些老的框架配置对 Googlebot 渲染本来就有已知坑(老版本 Angular Universal、老 Ember)。真正的修法是升级框架,而不是绕着补。另外,按 Martin Splitt 2026 年的说法,Google 的 AI 界面(Gemini 和 AI Overviews)现在用同一套 WRS 流水线渲染 JavaScript,所以一个对经典 Search 隐形的 CSR 页面,对这些界面同样隐形——把渲染修好,两边一起好。

预防建议

  • 任何想拿排名的内容,默认 SSR 或 SSG。
  • 永远别 Disallow: 渲染依赖的 API。
  • 每月对抽样 URL 跑一次 rendered-vs-原始 HTML 的 diff。
  • 给 JS bundle 大小和渲染时间定预算,CI 超阈值就 fail。
  • SPA 路由切换一定要更新 document.title 和 meta。

FAQ

  • Google 现在不是能完整渲染 JS 了吗? 它能渲染大多数 JavaScript、大多数时候、最终能渲染——但渲染轮是延后的、跑在预算上、不跟页面交互、还会跳过崩溃的内容。“会渲染 JS”不等于”任何情况都稳定渲染”。
  • 真的没有 5 秒超时? 没有固定的每页截止时间。被反复引用的”5 秒”是抓取到渲染的中位间隔;Google 说过大多数页面在几分钟内渲染完。内容缺失意味着渲染失败或根本没触发,不是”时间用完了”。
  • 预渲染会因为 cloaking 被惩罚吗? 不会,只要你发给爬虫的 HTML 和用户看到的一致。cloaking 是给爬虫看不同的内容;用不同的技术路径给出相同的内容是没问题的。
  • 为什么正文索引了但 title 是错的? 你的 SPA 路由切换时更新了内容,却没更新 document.title/meta。看第 5 步。
  • Googlebot 抓了我的页面,但 rendered HTML 还是空的,怎么办? 跑 live test,读 JavaScript console messagesPage resources。有 console error 说明渲染崩了(第 3 步);某个 /api/... fetch 被挡或失败说明是 robots.txt 或 CORS(第 4 步)。

相关阅读

标签: #SEO #排查 #收录 #Search Console #javascript #spa #rendering