Google Renders Your SPA, and That's Exactly the Problem

Google Renders Your SPA, and That’s Exactly the Problem

Google 会渲染你的 SPA,而这恰恰是问题的所在

A client shipped a beautiful React SPA, opened view-source, saw an empty <div id="root">, and shrugged. “Google runs JavaScript now,” their agency had told them. Six weeks later organic traffic was down 40% against the old server-rendered site it replaced, and nobody in the room could say why. I got the call because the numbers had gotten bad enough to become somebody’s problem, and by then the somebody was me.

一位客户发布了一个精美的 React 单页应用(SPA),打开网页源代码一看,发现只有一个空的 <div id="root">,但他们只是耸耸肩。他们的代理商曾告诉他们:“Google 现在可以运行 JavaScript 了。”六周后,其自然搜索流量较被取代的旧版服务端渲染网站下降了 40%,而会议室里没人能说出原因。我接到了求助电话,因为数据下滑已经严重到必须有人来解决,而那个人最终成了我。

Here is the uncomfortable thing I keep having to explain in meetings: “Googlebot can execute JavaScript” is technically accurate and practically ruinous. It’s the kind of fact that is true in a benchmark and false in production. Treating it as permission to ship a blank HTML shell is how you quietly bleed traffic for a quarter before anyone connects the dip to the rewrite. I’ve stopped treating HTML-first rendering as an optimization you circle back to. On anything that lives or dies by organic search, it’s a hard requirement, same tier as HTTPS.

以下是我在会议中不得不反复解释的一个令人不安的事实:“Googlebot 可以执行 JavaScript”在技术上是准确的,但在实践中却是灾难性的。这是一种在基准测试中成立,但在生产环境中却行不通的事实。将其视为发布空白 HTML 外壳的“通行证”,只会让你在接下来的一个季度里悄无声息地流失流量,直到有人将流量下滑与网站重构联系起来。我已经不再把“HTML 优先渲染”视为一种可以后续优化的选项。对于任何依赖自然搜索生存的项目来说,这都是一项硬性要求,其重要性与 HTTPS 同等。

The myth of the second wave: how Googlebot really renders JavaScript

“第二波渲染”的神话:Googlebot 究竟是如何渲染 JavaScript 的

The reassuring story goes like this: Googlebot fetches your page, sees an empty shell, notices the JavaScript, runs it, indexes the result. Every step of that is true. What the story quietly drops is when the running happens, and “when” is the entire ballgame.

一个令人宽慰的说法是这样的:Googlebot 获取你的页面,看到一个空壳,注意到 JavaScript,运行它,然后索引结果。这些步骤每一步都是真实的。但这个说法悄悄忽略了“何时运行”的问题,而“何时”才是整个博弈的关键。

Indexing a JavaScript-heavy page is a two-phase job. First, Googlebot fetches the raw HTML and parses whatever is actually sitting in the response body. Then, if the page needs JavaScript to produce its content, the URL goes into a render queue. A separate headless-Chrome service — Google calls it the Web Rendering Service — picks it up later, executes the scripts, and hands the rendered DOM back for indexing. That second phase is neither free nor instant. It can happen minutes after the first fetch; it can happen days after. You don’t control the interval and you can’t see it.

索引一个重度依赖 JavaScript 的页面是一项两阶段的工作。首先,Googlebot 获取原始 HTML 并解析响应体中实际存在的内容。然后,如果页面需要 JavaScript 来生成内容,该 URL 就会进入渲染队列。一个独立的无头 Chrome 服务(Google 称之为 Web 渲染服务)会在稍后获取它,执行脚本,并将渲染后的 DOM 交回进行索引。这第二个阶段既不免费,也不是即时的。它可能在首次抓取后的几分钟内发生,也可能在几天后才发生。你无法控制这个间隔,也无法观测到它。

So every page is really a bet: 所以,每一个页面实际上都是一场赌博:

  • The raw HTML already has your content. Indexed immediately, deterministically, on the first pass. This is the boring path, and boring is what you want.

  • 原始 HTML 中已经包含了你的内容。 在第一轮抓取时即可确定性地被立即索引。这是“无聊”的路径,而“无聊”正是你所需要的。

  • The raw HTML is a shell; content arrives after render. Indexed eventually, at a time Google picks, if the render succeeds, if your JS didn’t throw, if nothing timed out, if the crawler had budget that day.

  • 原始 HTML 只是一个外壳;内容在渲染后才出现。 最终会被索引,但时间由 Google 决定——前提是渲染成功、你的 JS 没有报错、没有超时,且爬虫当天还有抓取配额。

The failure modes on that second path are silent. One unhandled exception in a component, a third-party script that hangs, an API call that needs an auth cookie the crawler never sends — any of these leaves the renderer holding a half-built page, and the indexed version is whatever partial DOM existed at the moment it gave up. Nothing lights up in your dashboard. Your users see a perfect page because their browsers retry, wait, and forgive. The crawler does none of that. It takes one snapshot and moves on.

第二条路径的失败模式是静默的。组件中一个未捕获的异常、一个挂起的第三方脚本、一个需要爬虫永远不会发送的身份验证 Cookie 的 API 调用——任何这些都会导致渲染器只拿到一个半成品页面,而索引版本就是它放弃那一刻所存在的局部 DOM。你的仪表盘上不会有任何警报。你的用户看到的是完美的页面,因为他们的浏览器会重试、等待并容错。但爬虫不会做这些。它只拍一张快照,然后就离开了。

There’s a subtler tax too: the renderer works from a heavily cached view of your resources and does not execute scripts the way a fresh browser session does. It won’t scroll, it won’t click, it typically won’t fire the interactions that lazy-load your content. If your main copy only mounts after an IntersectionObserver trips or a user taps a tab, assume it isn’t in the index at all.

还有一个更隐蔽的代价:渲染器基于高度缓存的资源视图工作,它执行脚本的方式与全新的浏览器会话不同。它不会滚动,不会点击,通常也不会触发那些用于懒加载内容的交互。如果你的主要文案只有在 IntersectionObserver 触发或用户点击标签页后才挂载,那么请默认它根本没有被索引。

This bites hardest on exactly the pages that pay the bills: new content and content that changes often. A news article. A product that just came back in stock. A blog post you published this morning. If it’s parked in the render queue for three days, you’ve already missed the window where ranking it was worth anything.

这对那些“赚钱”的页面打击最大:新内容和频繁更新的内容。比如一篇新闻报道、一个刚补货的产品、或者你今天早上发布的博客文章。如果它在渲染队列中停滞了三天,你就已经错过了它获得排名的黄金窗口期。

Google is not your only crawler, and the others don’t render JavaScript at all

Google 并不是你唯一的爬虫,而其他爬虫根本不渲染 JavaScript

Here’s the part the “Google runs JS” crowd forgets completely: Google is one crawler. On a growing number of sites, it isn’t even the one that matters most for reach. Walk down the list of things that actually fetch your URLs:

这是那些“Google 会运行 JS”的拥护者完全忽略的一点:Google 只是一个爬虫。在越来越多的网站上,它甚至不是对触达率影响最大的那个。看看那些真正抓取你 URL 的列表:

  • Bing does render JavaScript, but more conservatively than Google, and it feeds a growing slice of the market — including the search sitting inside a lot of AI assistants people now use instead of a search bar.

  • Bing 确实会渲染 JavaScript,但比 Google 更保守,而且它占据了市场中越来越大的份额——包括许多人现在用来替代搜索栏的 AI 助手背后的搜索功能。

  • Social unfurlers — the bots behind link previews in Slack, WhatsApp, iMessage, LinkedIn, X, Facebook, Discord — fetch the raw HTML and read the <head>. That’s the whole behavior. They do not run your JavaScript. If your Open Graph tags are injected client-side, every shared link renders as a naked URL: no title, no image, no description.

  • 社交媒体预览机器人——Slack、WhatsApp、iMessage、LinkedIn、X、Facebook、Discord 中链接预览背后的机器人——它们只获取原始 HTML 并读取 <head>。这就是它们的全部行为。它们不会运行你的 JavaScript。如果你的 Open Graph 标签是通过客户端注入的,那么每个分享的链接都会显示为一个裸链接:没有标题、没有图片、没有描述。

  • AI and LLM crawlers — the bots pulling pages for AI search and answer engines — overwhelmingly parse static HTML and move on. If your content isn’t in the response body, you’re not in the answer. As “search” increasingly means “an LLM summarizing the web,” being invisible to non-rendering crawlers is a bigger and bigger hole in your funnel.

  • AI 和 LLM 爬虫——为 AI 搜索和问答引擎抓取页面的机器人——绝大多数只解析静态 HTML 然后就离开了。如果你的内容不在响应体中,你就不会出现在答案里。随着“搜索”日益演变为“由 LLM 总结网络内容”,对非渲染爬虫不可见,正成为你流量漏斗中越来越大的漏洞。

I watched a well-funded product launch where the marketing site was a pure client-rendered SPA. Every link anyone shared — investors, press, the founders’ own launch posts — came up as a bare gray rectangle. Their content was fine. The packaging around it was invisible to everything short of a full browser. That’s not a Google problem. That’s a “the entire modern web reads raw HTML first” problem, and Google is a small and shrinking part of it. The mental model that keeps me honest: assume every consumer of your page except a human’s browser reads only the initial HTML response. Write for that reader. Google’s renderer is a bonus on top, never the foundation underneath.

我曾目睹一个资金雄厚的产品发布,其营销网站是一个纯客户端渲染的 SPA。任何人分享的每一个链接——投资者、媒体、创始人自己的发布帖——都显示为一个空白的灰色矩形。他们的内容本身没问题,但其外围包装对除了完整浏览器之外的所有东西都是不可见的。这不是 Google 的问题,这是“整个现代网络都优先读取原始 HTML”的问题,而 Google 只是其中一小部分,且占比还在不断缩小。 让我保持清醒的思维模型是:假设除了人类的浏览器之外,你的页面的每一个消费者都只读取初始的 HTML 响应。为那个读者编写代码。Google 的渲染器只是锦上添花,绝不能作为基础。

SSR, SSG, ISR, and streaming: choose by content freshness, not by hype

SSR、SSG、ISR 和流式渲染:根据内容新鲜度选择,而不是跟风

Once you accept HTML-first as a requirement, the question stops being “should I render on the server” and becomes “how fresh does this content need to be, and how often does it change.” That single axis picks your strategy for you. No framework’s marketing page will tell you this cleanly, because every framework wants credit for all four modes. Here’s the decision I actually make:

一旦你接受了“HTML 优先”作为一项要求,问题就不再是“我是否应该在服务端渲染”,而是“这些内容需要多新鲜,以及它们更新频率如何”。这单一维度就能为你选定策略。没有任何框架的营销页面会如此清晰地告诉你,因为每个框架都想在所有四种模式上都分一杯羹。以下是我实际做出的决策:

Content typeChangesStrategyWhy
内容类型更新频率策略原因
Marketing pages, docs, blog postsRarelySSG (static site generation)Build once, serve HTML from a CDN, near-zero cost and near-zero latency
营销页面、文档、博客文章极少SSG(静态站点生成)构建一次,通过 CDN 提供 HTML,成本近乎为零,延迟近乎为零