你上线了一个 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.txt 的 Disallow: 里。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、NuxtasyncData、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 给用户和爬虫看的内容一致。
相关阅读
- JavaScript 动态设置的标题未被 Google 索引
- 无限滚动页面不被索引
- Crawled, currently not indexed 怎么解
- 页面被索引但零曝光
- Discovered, currently not indexed 怎么解
- robots.txt 挡了 CSS/JS 拖垮索引
- Google 没发现你的站内链接
- 页面被索引但排不上
- Sitemap 提交了但 URL 还是没被索引
- 内容深度不足问题
标签: #SEO #排查 #收录 #Search Console #javascript #spa #rendering