单页应用 SEO:为什么 SPA 难以获得排名,SSR、静态生成和预渲染如何解决这个问题,以及如何让 AI 爬虫读懂你的应用。

简而言之
单页应用(SPA)只加载一个 HTML 外壳,并在浏览器中用 JavaScript 构建每个视图。Google 能够渲染 JavaScript,但渲染会延迟且不稳定,而 ChatGPT、Claude 和 Perplexity 背后的爬虫根本不执行 JavaScript。要获得排名并被引用,SPA 的每个视图都需要自己的 URL、带 href 的真实链接、独特的标题和描述、正确的状态码,最重要的是,通过服务端渲染、静态生成或预渲染,让主要内容出现在服务器发送的 HTML 中。
想深入了解?请询问
单页应用 SEO 是一套技术,让用 React、Vue、Angular 或 Svelte 构建的 JavaScript 应用像传统网站一样容易被抓取。这个问题早有充分记录:Google 在 JavaScript SEO 基础知识 中解释,它分三个阶段处理 JavaScript 页面(抓取、渲染、编入索引),而 SPA 的每个弱点都位于这条流程的某个环节。本指南专注于 SPA;关于一般的 JavaScript 网站,请阅读我们的 JavaScript SEO 指南。
传统网站为每个 URL 返回完整的 HTML 文档。SPA 为每个 URL 返回几乎相同的文档:一个空的 <div id="root">、几个 meta 标签和一个庞大的 JavaScript 包。浏览器下载这个包,调用 API,然后绘制页面。访客点击链接时,JavaScript 会替换内容而不重新加载页面。
对于使用高速手机的用户来说,结果几乎是即时的。对于爬虫来说,第一次响应只是一个空壳:没有标题,没有文本,没有链接。一切都取决于机器人是否执行 JavaScript,以及何时执行。仅这一点就解释了大部分 SPA SEO 问题:
/#/pricing),而 # 之后的部分永远不会发送到服务器,因此所有视图都合并成一个 URL。<title>,如果路由器不更新它,所有页面都会共用。href 的链接:按钮和 onClick 处理程序能为用户导航,却不给爬虫留下任何可以跟踪的东西。Googlebot 首先获取原始 HTML,并读取其中的链接和指令。返回 200 状态码的页面随后进入 Web Rendering Service(一个无头 Chromium)的队列。Google 表示,页面“可能会在这个队列中停留几秒钟,但也可能需要更长时间”。渲染完成后,生成的 HTML 会再次被解析以提取内容和链接,然后编入索引。
Google 文档中有四个细节对 SPA 尤为重要:
href 的 <a> 元素才是可靠的链接。由 JavaScript 注入的链接只要符合这种格式就没有问题。pushState)而不是 # 路由。它已于 2015 年 10 月弃用旧的 AJAX 抓取方案(#! 网址)。noindex 优先。Google 看到 noindex 时可能会跳过渲染,因此之后再用 JavaScript 移除它是无效的。实际后果是:Google 可以为客户端渲染的 SPA 编入索引,但你为每个页面增加了第二个步骤、一段延迟和一个故障点。渲染过程中的超时、被阻止的 API 调用或 JavaScript 错误,都会让编入索引的版本变成空白。请用 Search Console 中的网址检查工具查看 Google 看到的内容;如果页面一直未被编入索引,请阅读我们关于 Google 索引如何运作 的指南。
大多数情况下不会。Vercel 和 MERJ 在 2024 年 12 月发布的一项基于 Vercel 网络流量的研究中发现,“目前没有任何主要 AI 爬虫会渲染 JavaScript”:GPTBot、OAI-SearchBot 和 ChatGPT-User(OpenAI)、ClaudeBot(Anthropic)、PerplexityBot、Meta-ExternalAgent 和 Bytespider 都只读取原始 HTML。它们确实会下载 JavaScript 文件(约占 ChatGPT 请求的 11.5%、Claude 请求的 23.8%),但不会运行这些文件。例外是依靠 Google 渲染基础设施的 Gemini,以及使用基于浏览器的爬虫的 AppleBot。
对于客户端渲染的 SPA,这意味着为 ChatGPT、Claude 和 Perplexity 提供数据的 AI 爬虫 看到的是一个空白页面。你的产品可能在 Google 上排名靠前,却在 AI 回答中完全隐形,因为助手从未读到其中的一个字。社交网络和即时通讯应用中的大多数链接预览机器人也是如此,它们读取原始 HTML 中的 Open Graph 标签。内容进入 HTML 之后,还要检查你的 robots.txt 是否允许你需要的 AI 爬虫。
解决办法是在第一次响应中就发送真实的 HTML。Google 在其 动态渲染文档 中称动态渲染(只向机器人提供预渲染版本)是“一种权宜之计,而不是推荐的解决方案”,并建议改用服务端渲染、静态渲染或 hydration。下面的定义参考了 web.dev 上的 Rendering on the Web 一文。
| 策略 | 工作方式 | 首个 HTML 中是否有内容 | 最适合 | 局限 |
|---|---|---|---|---|
| 客户端渲染(CSR) | 浏览器通过 JavaScript 和 API 构建页面 | 否 | 仪表盘和需要登录的页面 | Google 收录延迟,大多数 AI 爬虫看不到 |
| 服务端渲染(SSR) | 服务器在每次请求时渲染 HTML,然后由 JavaScript 进行 hydration | 是 | 产品目录、列表页、经常变化或个性化的内容 | 服务器成本,未缓存时响应较慢 |
| 静态生成(SSG) | 在构建时为每个 URL 生成 HTML,并通过 CDN 提供 | 是 | 营销页面、博客、文档、落地页 | 内容变化时需要重新构建(有增量方案) |
| 构建时预渲染 | 无头浏览器把每个路由的初始状态捕获为静态 HTML | 是 | 公开路由数量有限的现有 SPA | 数据在构建时固定,必须列出所有路由 |
| 动态渲染 | 机器人收到预渲染快照,用户收到 SPA | 是,仅限列出的机器人 | 旧应用的临时修复 | 官方定位为权宜之计,有维护成本,存在向机器人提供不同内容的风险 |
大多数现代框架允许按路由混合使用策略:营销页面用静态生成,产品目录用服务端渲染,登录后的应用用客户端渲染。这种混合架构在搜索流量所在之处为爬虫提供完整的 HTML,并在 SPA 体验真正有价值的地方保留它。
| 库 | 首次加载即输出 HTML 的框架或功能 |
|---|---|
| React | Next.js(服务器组件、SSR 和静态生成)、框架模式下的 React Router、Gatsby |
| Vue | Nuxt(默认通用渲染,也支持静态生成) |
| Angular | Angular SSR(服务端渲染和构建时预渲染) |
| Svelte | SvelteKit(默认服务端渲染,支持按路由预渲染) |
| 以上任意一个 | Astro(默认输出静态 HTML,交互岛只在需要的地方加载) |
AI 建站工具生成的网站值得特别一提:其中许多导出的是客户端渲染的 React 应用。如果你的网站是用这类工具搭建的,在假定 Google 和 ChatGPT 能读取它之前,请先看看我们关于 Lovable 的 SEO 和 GEO 的说明。
使用 History API(/pricing,绝不要用 /#/pricing),每项内容对应一个 URL,并且无论访客是直接访问还是通过应用内导航到达,URL 都保持一致。每个 URL 在新标签页中打开时都必须加载正确的内容,这正是爬虫所做的测试。
每个指向可编入索引页面的导航元素都应该是 <a href="/path">。框架的链接组件(Link、NuxtLink、routerLink)会输出这种标记;而在 div 或 button 元素上自定义的点击处理程序则不会。好的链接还承载着你的 网站架构:如果一个页面没有被链接,爬虫很少能找到它。
每个路由都需要自己的标题、meta 描述、canonical 和 Open Graph 标签,并尽可能在服务器端生成。我们关于 meta 标签 的页面列出了需要设置的内容。只保留一个 canonical 标签:Google 建议在 HTML 中设置它,如果 JavaScript 修改了它,结果不得与原始值相矛盾。
不存在的商品和已删除的文章不能返回 200 加一条错误信息。使用 SSR 时,从服务器返回真正的 404 或 410。在纯 SPA 中,Google 接受两种备选方案:用 JavaScript 重定向到返回 404 的 URL,或者在错误视图中添加带 noindex 的 robots meta 标签。
标题、正文、价格、常见问题和 JSON-LD 结构化数据都应该出现在服务器响应中,而不是在点击或滚动后才加载。藏在按需渲染的标签页后面,或藏在“加载更多”按钮后面的内容,常常会被遗漏。
按路由拆分代码包,延迟加载首屏不需要的内容,并避免在 hydration 期间重新渲染整个页面。请注意,Chrome 的实际用户数据(CrUX)会把应用内导航的指标归到访客最初加载的 URL 上,因此要单独测试每个关键模板。
对每个关键模板比较三个版本:原始 HTML(查看源代码或用命令行工具获取页面)、Google 渲染后的 HTML(网址检查,然后点击“查看已抓取的网页”),以及禁用 JavaScript 后的页面。如果主要文本只出现在第二个版本中,AI 爬虫就看不到它。我们的免费 AI 可抓取性审核 和 可索引性检查工具 可以对任意 URL 运行这些检查。
| 症状 | 可能原因 | 修复方法 |
|---|---|---|
| Search Console 显示大量页面为“已抓取 - 尚未编入索引” | 渲染前的 HTML 为空或几乎相同 | 对公开路由使用 SSR、SSG 或预渲染 |
site: 搜索的每条结果都显示相同的标题 | 标题未按路由更新 | 在服务器端按路由管理 head |
| 只有首页被编入索引 | 哈希路由或没有 href 的链接 | History API 和真实的 <a> 链接 |
| 错误页面出现在索引中 | 软 404(所有地方都返回 200) | 服务器返回 404,或用 JavaScript 重定向到 404 URL,或使用 noindex |
| 在 Google 上有排名,但从未被 ChatGPT 或 Perplexity 引用 | 内容只有在执行 JavaScript 后才可见 | 让内容出现在第一个 HTML 响应中 |
| Google 的渲染视图中显示旧内容 | JavaScript 或 CSS 文件被缓存 | 带指纹的文件名 |
多年来,客户端渲染是一种可控的风险,因为 Google 最终会渲染 JavaScript。如今,一部分发现过程发生在 ChatGPT、Claude、Gemini 和 Perplexity 中,代价随之改变:只有在执行 JavaScript 后才存在的页面,不在大多数助手能够读取的来源之列。原始 HTML 中的 可抓取性 已成为生成式引擎优化的首要条件。
HTML 完整之后,其余部分遵循常规规则:清楚回答一个问题、带有来源事实和结构化数据的页面,才是助手会引用的页面。Sorank 的 GEO 优化 功能帮助你撰写和发布这类内容,而 AI 提及追踪 会显示在修复渲染之后,ChatGPT、Claude 和 Gemini 是否引用了你的品牌。
单页应用 SEO 的核心规则可以用一句话概括:凡是你希望 Google 排名、AI 引用的内容,都必须出现在服务器发送的 HTML 中。把 SPA 留给登录后的体验,在服务器或构建时渲染公开页面,然后在每次发布前后测试机器人收到的内容。
Google 不会惩罚单页应用,但它的默认设置,即只有一个 HTML 外壳的客户端渲染,会让编入索引更慢、更不可靠。大多数 AI 爬虫不执行 JavaScript,因此看到的是一个空白页面。在公开路由上使用服务端渲染、静态生成或预渲染后,SPA 的排名可以和任何传统网站一样好。
React 本身是中立的:关键在于 HTML 在哪里生成。只在浏览器中渲染的 React 应用会遇到 SPA 的常见问题,而同样的组件如果用 Next.js 或框架模式下的 React Router 在服务器上渲染,就能向爬虫提供完整的 HTML。请为希望编入索引的页面选择支持服务端渲染或静态渲染的框架。
只能作为临时措施。Google 将动态渲染描述为一种权宜之计,而不是推荐的解决方案,因为为机器人维护一个单独的版本会增加成本和风险。服务端渲染、静态生成或构建时预渲染会向每位访客提供相同的 HTML,才是长期的解决办法。
他们已先于您体验了 SORANK

