拓十年匠心定制 · 商业建站与技术教学双线并行 咨询热线:400-886-1026 service@lmnt.cn
ARTICLE DETAIL

资讯详情

深耕网站建设与运营推广的一线实战洞察。

外贸站收录延迟排查记:客户端渲染让 Google 等了三周,三种渲染方案的对照结论

外贸站收录延迟排查记:客户端渲染让 Google 等了三周,三种渲染方案的对照结论

外贸站收录延迟排查记:客户端渲染让 Google 等了三周,三种渲染方案的对照结论

适用读者:负责外贸独立站的前端工程师、管自然流量的独立站运营、正在选型渲染架构的技术负责人。你需要对 React 或 Vue 的工程化有基本概念,不需要深入搜索引擎 internals。

改版上线整整三周,运营拿着新品页链接来问:为什么在 Google 里搜型号加品牌词,翻到第五页都没有我们的页面?后台明明显示 Googlebot 天天来抓。我打开 view-source 看了一眼,心里就凉了半截——返回给 Googlebot 的原始 HTML 里,body 只有一行<div id="root"></div>。这篇就把这次做搜索引擎优化(Search Engine Optimization, SEO)排查的全过程、数据对照和三种渲染方案的取舍写清楚,给同样在做网站优化的人一个可以直接照抄的复盘。

改版留下的债:一个纯前端渲染的 SPA

先交代背景。这个外贸独立站原来是一套 PHP 模板站,速度慢但收录一直稳定。去年 Q4 改版,选了 React 单页应用(Single Page Application, SPA)加后端 API 的架构:前端全部客户端渲染(Client-Side Rendering, CSR),页面内容由浏览器拉取 JS、再请求接口拼出来。开发体验确实好,上线两个月内迭代了三十多个需求,没人觉得有什么问题。

问题出在收录上。改版前 Google 大约 48 小时内就能收录新上的产品页;改版后,9 月 2 日上线的一批 40 个新品页,到 9 月 23 日还有 27 个没进索引。中间我们试过在 Search Console 里手动「请求编入索引」,队列排了四天才有响应,响应结果还是「已抓取 - 尚未编入索引」。

关键结论先放这里:CSR 不是不能被收录,而是把收录从一个「抓取即完成」的动作,变成了一个依赖二次渲染队列的异步动作。队列有多长,你的收录时延就有多长。

排查第一步:view-source 和检查元素是两份 HTML

这次排查里最直观的一步,也是后来我跟团队反复讲的一课:view-source: 看到的和「检查元素」看到的,可能完全是两个页面。

view-source: 拉到的是服务器原始响应,等价于 Googlebot 第一阶段拿到的东西;「检查元素」看到的是浏览器执行完 JS 之后的 DOM,等价于 Googlebot 第二阶段渲染后的结果。我们两个一对比,差异大到离谱:

  • view-source 里的 body:一个空的挂载点,加 6 个<script>标签,正文文本约 0 字;
  • 检查元素里的 body:完整产品描述、规格参数表、FAQ,正文文本约 4200 字。

也就是说,Googlebot 第一眼看到的是一个没有任何内容的空壳。用 Node 写个小脚本把这件事量化,方便每次发版后跑一遍回归:

// fetch-raw.mjs —— 拉取服务器原始 HTML,量化空壳程度// 运行方式:node fetch-raw.mjs <目标URL>,Node 18+ 自带 fetch,无需装依赖consturl=process.argv[2];constres=awaitfetch(url,{// 关键:不要跟随后端给爬虫的特殊跳转,只看普通用户拿到的响应redirect:"follow",headers:{"User-Agent":"Mozilla/5.0 (Windows NT 10.0; Win64; x64)"},});consthtml=awaitres.text();// 提取 body 内部内容,统计纯文本长度(去掉标签后的字符数)constbody=html.match(/<body[^>]*>([\s\S]*?)<\/body>/i)?.[1]??"";consttextLen=body.replace(/<[^>]+>/g,"").trim().length;constscripts=(html.match(/<script/g)??[]).length;// 判定标准:原始正文 < 200 字且 script 占比高,基本可判为 CSR 空壳console.log(`原始正文文本长度:${textLen}`);console.log(`script 标签数量:${scripts}`);// 期望值参考:我们站改造前这个数字是 12,改造后(SSG)是 3800+

这个脚本现在是我们 CI 里的固定检查项,正文文本长度低于阈值就直接报警。当时第一次跑出来的数字是 8——8 个字符,基本就是转义后的换行和空格。

原理与机制:Googlebot 的两阶段抓取和渲染队列

要理解为什么「三周才进索引」,得把 Googlebot 的工作机制拆开看。它对 JS 页面的处理是两个阶段:

第一阶段是抓取(Crawl),只拿原始 HTML、解析链接、入队。第二阶段是渲染(Rendering),把页面丢进一个基于 Chromium 的无头浏览器里跑完 JS,再从渲染后的 DOM 里提取内容和链接。这两阶段之间隔着一个全局共享的渲染队列。

有

没有/极少

调度器从待抓取队列取 URL

第一阶段: 抓取原始 HTML

HTML 里是否有实质内容?

直接解析提取, 进入索引流程

页面进入渲染队列等待

第二阶段: 无头 Chromium 执行 JS

提取渲染后 DOM 与新发现的链接

进入索引评估流程

这个渲染队列的优先级逻辑没有官方文档明说,但官方在 JavaScript SEO 的帮助文档里确认了渲染是「延后且按需」的,队列资源有限。站点权重越高、被抓取频率越高,排得越靠前;反过来,一个收录历史浅的新站,排几个星期不算稀奇。我们的 40 个新品页就是这么在队列里排队等了三周。

更麻烦的是抓取预算(Crawl Budget)被 JS 拖耗的问题。我们站每个页面平均带 14 个 JS 资源请求,Googlebot 渲染时要挨个下载执行。9 月中旬日志里能看到,Googlebot 每天固定消耗大约 6000 次请求,其中大量是静态资源,真正的新产品页抓取量反而被挤占。对中小站点来说,这是 CSR 在网站优化里最隐蔽的代价:你交的抓取预算,一大半花在了「让 Googlebot 看懂页面」这个本可以省掉的环节上。

用 URL Inspection 做渲染后 HTML 对照

Search Console 的网址检查(URL Inspection)工具提供了「查看已抓取的网页」和「测试实际网址」两个入口,后者会现场跑一遍渲染,把渲染后的 HTML 给你看。这是排查 CSR 问题时最有用的官方工具。

我们当时对照了三个页面,发现的典型问题记录如下:

  1. 渲染后 HTML 里产品标题正常,但结构化数据没有——因为结构化数据是我们用 JS 注入的,渲染阶段有时超时没跑完;
  2. 「测试实际网址」的加载状态里出现过Failed to load resource两次,对应一个挂在内网 CDN 预热前地址的图片;
  3. 页面可交互时间(Time To Interactive)在模拟环境里超过 9 秒,渲染队列里排队时更久。

把渲染前的原始 HTML 和渲染后的 HTML 各存一份 diff,是后面做方案对照的基线。没有这个基线,改造完你说不清到底改善了什么。

三种方案对照:SSR / SSG 预渲染 / 动态渲染

确认问题后我们拉了三个候选方案,逐个评估。先给结论表,再讲取舍过程。

维度服务端渲染(SSR)静态站点生成(SSG)预渲染动态渲染(Dynamic Rendering)过渡
首屏给爬虫的内容每次请求实时生成构建时预先生成爬虫命中预渲染快照
改造工作量大,组件需兼容服务端执行中,加构建期抓取与写盘小,加一层代理分流
新品页收录时延分钟级分钟级(重构建后)短期无效,依赖快照更新
长期维护成本高(服务器、缓存、水位)中(构建时长随页面数增长)高(两套渲染要长期同步)
对 SEO 的意义彻底解决彻底解决止血方案,非终态

几个关键取舍点:

SSR 一步到位但门槛最高。我们的产品页里有一半内容依赖登录态和地区定价,SSR 意味着这些逻辑全部要理出服务端安全的版本,团队只有两个前端,预估六周起步,还可能埋一堆水合(Hydration)不一致的坑。

SSG 预渲染最后胜出的原因:产品页本质是「读多写少」,一天上新二三十个 SKU,完全可以在新品上架的发布流程里触发一次增量预渲染,把页面变成纯静态 HTML 直接吃 CDN。这样 Googlebot 第一阶段抓到的就是全量内容,两阶段合并成一个阶段,收录时延直接砍掉渲染排队的时间。

动态渲染我们只当止血带。它的思路是按 User-Agent 识别 Googlebot、Bingbot 这类爬虫,把请求转发给一个预渲染服务,返回已经执行完 JS 的 HTML 快照。改造量最小,理论上两天能上,但它有三个硬伤:UA 判断要长期维护、快照服务本身要保可用性、快照内容和真实页面永远有时间差。官方文档也明确说了这是过渡方案,不推荐长期使用。

给爬虫分流的核心逻辑不复杂,当时止血版本的代码骨架长这样:

// server.js —— 动态渲染止血版:给爬虫吐预渲染快照// 依赖:express、prerender-node,Node 16+,PRERENDER_TOKEN 配在环境变量里constexpress=require("express");constprerender=require("prerender-node");constapp=express();// 只对声明过自己是爬虫的 UA 生效,正常用户照旧走 SPA,不影响体验app.use(prerender.set("prerenderToken",process.env.PRERENDER_TOKEN).set("protocol","https"));// 静态资源与 SPA 入口app.use(express.static("dist"));// 兜底:非爬虫请求统一回落到 index.html,交给前端路由app.get("*",(req,res)=>{res.sendFile(require("path").join(__dirname,"dist","index.html"));});// 健康检查给监控用,快照服务挂了要能第一时间知道app.get("/healthz",(req,res)=>res.send("ok"));app.listen(3000,()=>console.log("render proxy on :3000"));

上线前在预发环境用curl -A "Googlebot"验证分流是否命中,再用「测试实际网址」确认渲染结果,这一步不能省——我们第一次配的时候把 UA 白名单写反了,差点把正常用户也导去快照服务。

改造落地:发布流程挂上增量预渲染

最终落地的是 SSG 预渲染为主、动态渲染为辅的过渡架构。方案分铺开的时序大概是这样:

确认 CSR 空壳问题

止血: 动态渲染代理上线

第 2-4 周: 预渲染管线开发

新品发布流程接入增量预渲染

存量 800 页全量预渲染一次

下线动态渲染, 纯静态 + CDN

SSR 评估推迟到多语言站点重构时再做

发布流程的接入比想象中顺:运营上架新品后,发布系统调一个内部接口,预渲染 worker 抓取该页渲染结果写进静态目录,CDN 缓存刷新,前后大概 40 秒。真正费劲的是存量 800 个产品页的历史数据清洗——不少老页面的规格表是图片,预渲染出来内容还是薄,这些页面单独排了优先级重做。

改造前后的数据对照是我们跟管理层汇报的核心,也是这篇里最值得留给后来人的一张表:

指标改造前(CSR 空壳)止血期(动态渲染)改造后(SSG 预渲染)
新品页进索引时延(中位数)19 天6 天26 小时
原始 HTML 正文文本长度8 字符约 3900 字符约 4100 字符
Googlebot 日均请求中浪费在 JS 上的占比约 62%约 41%约 9%
收录产品页数(30 天窗口)143 / 240189 / 240226 / 240
渲染队列相关「已抓取未编入索引」堆积27 个9 个2 个

数据来源是我们自己的 Search Console 和 Nginx 访问日志统计,时间窗是 2026 年 7 月到 9 月,别拿去和别的行业横比,但趋势是可信的:收录时延从 19 天压到 26 小时,靠的不是什么高级技巧,就是让 Googlebot 第一阶段就能拿到全部内容。

结构化数据这块我们踩过同样的坑:早期用 JS 注入 JSON-LD,渲染超时就直接丢。改造后统一在预渲染 HTML 里直接输出,Googlebot 第一阶段就能读到。产品页的 Product + Offer 标记写法大致如下:

{"@context":"https://schema.org","@type":"Product","name":"Industrial Gear Motor 3HP","image":"https://example.com/products/gear-motor-3hp.jpg","description":"Three-phase 3HP gear motor for conveyor systems, IP55 rated.","sku":"GM-3HP-220V","brand":{"@type":"Brand","name":"Example Motors"},"offers":{"@type":"Offer","url":"https://example.com/products/gear-motor-3hp","priceCurrency":"USD","price":"289.00","availability":"https://schema.org/InStock"}}

关键字段说明:name和description要和页面正文一致,避免结构化数据与可见内容不一致被判定为作弊;offers.price用字符串数字并配priceCurrency,方便 Google 直接展示价格富媒体;availability用 schema.org 的枚举值而不是自由文本。这段 JSON-LD 由预渲染 worker 在写静态 HTML 时一并拼进<head>,而不是等浏览器跑 JS 再注入——这样即使渲染队列超时,结构化数据也已经在原始 HTML 里,不会像之前那样丢。

Bing 和百度:别把 Google 的经验直接照搬

排查期间顺手看了另外两家,差异值得单独记一笔。

Bing 的爬虫对 CSR 有一定执行能力,但实测渲染触发比 Google 更保守,我们的新品页在 Bing 上同样延迟,只是程度轻一些。百度的情况更严格:它的抓取体系对 JavaScript 渲染的支持长期有限,纯 CSR 站点在百度侧的收录问题比 Google 严重得多,很多外贸站团队只盯 Google,等哪天想接百度流量才发现坑更深。所以如果你的网站优化目标包含多引擎,CSR 的债务是按引擎数翻倍计算的。这条经验后来被我们写进了前端选型 checklist。

三家引擎对 CSR 页面的处理机制差异,可以汇总成下面这张对照表:

维度GoogleBing百度
渲染触发策略两阶段:先抓取原始 HTML,再进全局渲染队列,延后且按需对 CSR 有一定执行能力,但渲染触发比 Google 更保守对 JavaScript 渲染的支持长期有限,触发条件更严格
典型收录时延依赖渲染队列长度,权重高排前,新站可能排数周同样有延迟,但程度比 Google 轻纯 CSR 站点收录问题比 Google 严重得多
对 JS 注入内容的支持程度支持,但渲染超时会导致结构化数据等 JS 注入内容丢失支持有限,实测触发更保守支持长期有限,JS 注入内容收录风险最高
适合的渲染方案建议SSR / SSG 预渲染彻底解决,动态渲染可作过渡同样建议 SSR / SSG 预渲染,动态渲染可作过渡强烈建议 SSR / SSG 预渲染,纯 CSR 基本不可行

误区澄清

最后澄清两个这次复盘里反复被问到的误区。

一个误区是「Google 不是能渲染 JS 吗,等就行」。能渲染,但渲染是延迟的、排队的、消耗抓取预算的。对收录时延敏感的电商新品、活动页,等的成本就是实打实的流量损失,这个等不起。

另一个误区是「上了 SSR/SSG 就等于做了网站优化」。渲染只是技术 SEO 的地基,地基之外还有结构化数据、内链、页面速度这些活,地基不打后面全是空中楼阁。生成式搜索(GEO)兴起之后,爬虫对「拿得到完整 HTML」的要求只会更高,这次改造打底的架构,也算提前给下一步铺了路。

渲染方案没有标准答案,只有和团队规模、业务节奏匹配的选择。你在外贸站收录上踩过什么坑,欢迎评论区交流。

参考与延伸

  • Google 搜索中心:了解 JavaScript SEO 基础知识 — https://developers.google.com/search/docs/crawling-indexing/javascript/javascript-seo-basics
  • Google 搜索中心:Google 抓取工具概览 — https://developers.google.com/search/docs/crawling-indexing/overview-google-crawlers
  • Google 搜索中心:网址检查工具使用说明 — https://developers.google.com/search/docs/monitoring-debugging/inspect-urls
  • Bing Webmaster Tools 帮助文档 — https://www.bing.com/webmasters/help/help-center-661155ed

收录延迟 · 客户端渲染 · SSR · SSG 预渲染 · 动态渲染 · Google Search Console · 外贸独立站 · 抓取预算

返回列表