为什么 AI 搜索读不到你的 SPA(GPTBot、ClaudeBot 与 JavaScript 难题)
GPTBot、ClaudeBot、PerplexityBot 这类 AI 爬虫基本不执行 JavaScript,所以纯客户端渲染的 SPA 在它们眼里就是一张空壳。本文讲清楚怎么两分钟自测、为什么服务端渲染或静态 HTML 是正解,以及页面能被读到之后还要做什么才换得来引用。
如果你的站点是纯客户端渲染的单页应用,AI 答案引擎大概率看到的是一张空白页。ChatGPT、Claude、Perplexity 背后的爬虫会抓取你的 HTML,但通常不会运行那段把内容拼出来的 JavaScript。没有 JavaScript,就没有正文,也就没有引用。正解是把真正的文字放进服务器返回的那份 HTML 里——通过服务端渲染或静态生成——在任何脚本运行之前。这篇文章讲清楚问题为什么会发生、怎么在自己站点上两分钟确认、到底怎么修,以及为什么”能被读到”是必要条件,但还不足以真正被引用。
一句话结论:AI 爬虫只抓 HTML,不渲染
普通浏览器做两件事:先下载 HTML,再运行 JavaScript 把页面填满。大多数 AI 检索爬虫只做到第一步就停了。一份基于超过 5 亿次 GPTBot 抓取的分析发现,主流 AI 爬虫——GPTBot、ClaudeBot、PerplexityBot——只请求原始 HTML,基本跳过 JavaScript 执行。它们的行为更像 curl,而不像 Chrome。Google 自己的搜索索引器确实会在第二趟里跑 JS,但那是独立的另一套系统,和喂给实时 AI 答案的那些爬虫不是一回事;你也没法指望 Googlebot 的渲染去补 GPTBot 从不执行的那部分。
所以对一个纯 SPA 来说,爬虫看到的就是首屏 HTML 响应里的东西,而那通常几乎什么都没有。一个 Create React App 或默认的 Vite 构建产物,发出的 index.html 主体基本上就是一个空的 <div id="root"> 加几个 script 标签。你的标题、产品文案、价格表、FAQ,全是浏览器加载之后由爬虫从不执行的那段代码拼出来的。从模型那一侧看,这个页面没有任何值得引用的文字。你的内容不是排名差——文档里压根没有内容可供爬虫去排名。
这跟”渲染慢”或”渲染不稳”是两码事。爬虫不是在等你的应用启动、等不及才放弃,而是读完那个 HTTP 响应的字节就走了。如果那些字节里没有你的文字,那后面的一切——向量化、检索、引用——都无从恢复。
怎么两分钟自测
不需要任何专门工具就能确认。两个检查就能说明全部问题,而且它们之间会互相矛盾——这一点正值得搞明白。
第一,禁用 JavaScript 后重新加载。Chrome 里打开 DevTools,调出命令面板(Ctrl/Cmd+Shift+P),运行 “Disable JavaScript”,然后刷新。如果页面变空白、只剩一个转圈的加载图标、或塌成一个光秃秃的页眉,那张坏掉的页面就大致是 AI 爬虫拿到的东西。像 Playwright 这样的浏览器自动化工具可以把同一个检查脚本化——禁用 JavaScript 加载某个路由,断言某句已知的正文存在——这样你能把它接进 CI,等哪天有人把某个区块挪回客户端组件时及时抓到回归。
第二,看服务器实际发出的原始 HTML,而不是渲染后的 DOM。跑一句 curl -s https://yoursite.com/your-page,在输出里搜一句你确定在正文里的话:
curl -s https://yoursite.com/pricing | grep -o "起价 ¥349"
如果 grep 搜得到,爬虫就读得到。如果只翻到一堆 script 标签和一个空挂载点,说明你的正文只活在 JavaScript 里,AI 爬虫看不到。浏览器的”查看源代码”显示的是同一份东西——没渲染过的响应,跟 Elements 面板不一样——如果你不想用终端,用它也行。
这两个视图之间的差距,就是整个陷阱所在。Elements 面板里的 DOM 永远看着是完整的,因为你打开它的时候浏览器早就替你跑过 JS 了。要信”查看源代码”和 curl,别信审查元素面板,也别信页面对你自己呈现的样子——你开着 JavaScript,爬虫没有。
修法:在服务端渲染,或者直接发静态 HTML
修法就是把真正的正文放进服务器返回的那份 HTML 里,在任何 JavaScript 运行之前。有两条成熟的路子,它们的取舍跟一直以来一样。
服务端渲染(SSR)是在服务器上、或边缘节点上,为每次请求生成页面 HTML。Next.js App Router 默认就把 Server Component 渲染成 HTML——正文在响应体里,客户端只为可交互的部分下发 JavaScript。你可以用 OpenNext on Cloudflare 把它跑在边缘,它把 Next.js 构建产物适配到 Cloudflare Workers,让喂给用户的那次渲染同样喂给爬虫,而且就近发生在请求落地的地方。爬虫拿到的是一份内容完整的页面,你的用户在 hydration 之后照样得到一个可交互的应用。
静态生成则是在部署时把 HTML 构建一次,然后给所有人发同一份文件。Astro 默认零 JavaScript,特别适合内容型站点——营销页、文档、博客文章这种两次部署之间正文很少变的内容。产物是纯 HTML 文件,可以从 Cloudflare Pages 这样的 CDN 直接发,热路径上没有渲染步骤。本站走的就是这条路,所以你现在读的这些字就在首屏 HTML 响应里,不是脚本拼出来的。
你不必把所有东西重写一遍。诚实的范围问题是:哪些页面需要被引用?答案几乎总是那些公开页——营销页、文档、文章、对比页。只把这部分迁到 SSR 或静态 HTML,就覆盖了绝大部分价值。登录墙后面那个应用可以继续是客户端渲染的 SPA,反正 AI 爬虫本来就进不去、也不该进去,因为把私有数据挡在授权后面本来就是对的(OWASP A01:失效的访问控制)。一个常见且合理的架构就是:一个静态或服务端渲染的公开站点,加一个独立的 SPA 后台,各做各擅长的事。
迁完之后,再跑一遍 curl 检查。正文应该就明明白白躺在响应体里。顺手把页面保持得快一点——同一套边缘渲染既帮爬虫,也帮你的 Core Web Vitals;一个能快速返回 HTML 的服务器,爬虫也更可能把它完整抓下来。
能被读到是必要条件,但不充分
把正文放进 HTML 只是让你进了门,并不等于能被引用。模型能读到页面之后,真正换来引用的,是它能干净抽取、能信任的内容:靠前的清晰结论、真实的具体数字,以及告诉引擎这个页面讲的是什么的结构化数据——而不是让它从一段段散文里去猜。
研究给出了具体数字。发表在 KDD 2024 的生成式引擎优化论文,拿真实 AI 答案引擎测试了一批具体的内容改动,发现加入引用过的统计数字、直接引述、以及权威来源,能把页面在生成式答案里的可见度提升最高约 40%,其中标注来源是单项里最强的因素之一。同一项研究还指出,这种提升并不均匀:原本在常规搜索排名里靠后的页面收益最大,而排名靠前的页面变化更小、甚至会下降——这说明 GEO 更像一股拉平的力量,而不是给本就领先的内容再加倍。落到实操上就是:把力气花在你当前排在中游的页面,而不是那些已经领跑的页面。
然后,把页面对引擎也做成可读的结构化数据,而不只是对人可读的散文。用 schema.org 的类型给内容打标记——Article、FAQPage、Organization、Product,看哪个合适——并遵循 Google 的结构化数据指南,这样引擎能解析实体和关系,而不是靠猜。结构化数据是页面被识别成一个可引用、可标注来源的来源、而非一整面没区分度的文字墙的一部分。
提醒一句别走捷径,因为”能读到却引不到”这个落差很容易把人引向坏主意。跳过隐藏指令那套把戏,也别迷信 llms.txt。一项覆盖 30 万个域名的研究没发现 llms.txt 对 AI 引用有任何可测影响,采用率约 10%,也没有任何主流引擎确认自己用这个文件。而且 Google 已经更新了垃圾内容政策,把操纵生成式 AI 回答也算进去,所以伪装内容、或往页面里塞提示词,结果是被过滤,而不是被引用。这背后还有个要分清的区别:被抓取、被索引,跟被引用不是一回事。模型可以收下你的页面,却始终不把它端出来——正是因此,在访问问题解决之后,真正出力的是可抽取性和可信度。
为什么现在就该做
AI 主导的发现正在从新鲜事变成默认入口。Gartner 预测,到 2028 年 90% 的 B2B 采购旅程将受 AI agent 影响,涉及超过 15 万亿美元的采购。如果这些 agent 抓了你的原始 HTML,只找到一个空的 <div>,那不管你的产品多好,你都缺席了那个答案——而且你在分析后台里都看不到这件事表现为排名下滑,因为根本没有可丢的曝光。你只是没进候选集。
这个检查只花两分钟。禁用 JavaScript、curl 一下你的核心页面,看看正文还在不在。如果不在,服务端渲染或静态 HTML 就是答案,而且这是一次性投入——之后越来越多的采购决策经由那些跑不动你脚本的模型,它就一直在帮你赚回来。先把内容弄进 HTML;然后用具体数字、来源和结构去把那次引用挣回来。