JavaScript 渲染的内容没进 Google 索引

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

你上线了一个 React/Vue/Svelte SPA。URL 在 Google 索引里。但是搜页面正文里一段独特的话——啥都没有。Search Console URL Inspection → “View crawled page” → “Rendered HTML”。<body> 几乎是空的,或者只有一个 loading spinner。Googlebot 拿到了壳,没拿到内容。表现是”已索引但零曝光”、“被爬但没 rich result”,或者文章对正文里实实在在出现的关键词排不上。

Google 确实会渲染 JavaScript,但是用一个延后的第二轮渲染——速度更慢、资源有上限、撞上一些开发者平时不会测的模式就翻车。修复方法很少是”再等等”。

常见原因

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

组件写了 useEffect(() => fetch('/api/article')),但实际只在 requestIdleCallback、scroll、点击之后才触发。Googlebot 不滚动、不点击、也不像浏览器那样进 idle。

怎么判断:DevTools → Network → Throttle 调 “Slow 3G”,啥都别动等 5 秒。如果内容没画出来,Googlebot 大概率看到的也是空的。

2. API 被 robots.txt 或 CORS 挡了

HTML 加载了,JS 调 /api/data。但 /api/*robots.txtDisallow: 里。Googlebot 拿到了 HTML,但拉不了渲染依赖的 API。

怎么判断:检查 robots.txt。数据端点被禁,Googlebot 就跑不了依赖它的 JS。

3. 渲染超时(首次有意义绘制 > 5 秒)

Googlebot 的 Web Rendering Service 有个软预算。重 JS bundle、慢 API、阻塞渲染的第三方脚本超出预算后,Googlebot 索引超时前画出来的——通常啥都没有。

怎么判断:Lighthouse 跑分。LCP > 4 秒 或 TBT > 600ms,渲染就有风险。

4. JS 错误把渲染搞挂

一个 Cannot read properties of undefined 之类的 console error 让 hydration 中断。壳渲染了,数据层没画出来。

怎么判断:Search Console URL Inspection → “More info” → “Page resources / JavaScript console messages”,看里面有没有 runtime error。或者用 DevTools 打开生产页,看有没有 throw。

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

用户通过 SPA 路由访问 /article/foo。title 和 meta 还停留在首页的值,因为 SPA 没更新。Googlebot 索引第二个 URL 时拿的是首页 metadata。

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

6. Service worker 给 Googlebot 发了旧内容

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

怎么判断:把 User-Agent 设成 “Googlebot/2.1”,对比浏览器普通访问。如果不一样,service worker(或 CDN)在分流。

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

纯客户端渲染:HTML 就是 <div id="root"></div> + script。Googlebot 的渲染轮是看到内容的唯一机会。1-4 任一出问题就没救。

怎么判断curl https://yoursite.com/article/foo,无论 URL 是啥都返回差不多的壳。没有预渲染 HTML、没有 SSR。

最短修复路径

第 1 步:先确认 Googlebot 到底看到了啥

Search Console → URL Inspection → “Test live URL” → “View tested page” → “More info” → “HTML”。那是 Googlebot 渲染出来的。不是 curl 拿到的,也不是浏览器看到的。

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

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

  • SSR:Next.js getServerSideProps、Nuxt asyncData、SvelteKit +page.server.ts
  • SSG:Astro、Next.js getStaticProps、Hugo。内容站最合适。
  • Prerender:Prerender.io、Rendertron——给爬虫发静态快照。
// Next.js 例:文章页 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、intersection observer 里挪出来。关键内容(标题、正文、schema)必须在首屏服务端响应里、或者无门槛的 useEffect 里 hydrate。

第 4 步:robots.txt 放行渲染需要的 API

实在没法避免客户端渲染,至少别禁掉渲染依赖的 API:

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

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

继续做客户端 SPA,用 React Helmet、Vue Meta,或者路由切换时直接更新 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 inspection。用能渲染 JS 的爬虫——Screaming Frog 开 JS 渲染、Sitebulb、或者基于 puppeteer 的脚本——爬 100+ 页,对比 rendered HTML 和 source HTML。差异大的就是有风险的。

第 7 步:观察 Search Console

修完之后给 2-4 周,“Crawled but not indexed” 数量回落、正文 query 的曝光开始出现。

哪些情况可能不是你操作错了

有些 JS 框架对 Googlebot 渲染本来就有已知坑(老版本 Angular Universal、老 Ember)。真正的修法是升级,不是打补丁。

容易误判的情况

误以为是内容质量问题。文章看起来按 URL 被索引了,但 Google 完全不知道它讲啥,因为正文从没进索引。所以”被索引”了但任何关键词都排不上。

预防建议

  • 任何想拿排名的内容,默认 SSR 或 SSG。
  • 永远别在 robots.txt 里禁掉渲染需要的 API。
  • 每月跑一次抽样的 rendered-vs-source 对比。
  • 给 JS bundle 大小和渲染时间定预算,CI 超阈值就 fail。
  • SPA 路由切换一定要更新 document.title 和 meta。

FAQ

  • Google 现在不是能完整渲染 JS 了吗? 能,但第二轮渲染延后、资源有上限、撞上渲染错误就跳过。“会渲染 JS”不等于”任何情况都稳定渲染”。
  • 预渲染会被当 cloaking 惩罚吗? 不会——只要预渲染 HTML 和实际渲染 HTML 给用户和爬虫看的内容一致。

相关阅读

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