JavaScript SEO 踩坑实录:搜索引擎如何处理 JS 渲染的内容?(实测)

2024 年 5 月 4 日|技术SEO|预计阅读时间 ≈ 14 分钟

本文通过一个简单的实验,测试了主流搜索引擎(特别是谷歌,也捎带看了下 Bing 和 Yandex)在抓取和索引纯靠 JavaScript 渲染的网页时的真实表现。我们还会探讨这对 SEO 专家和前端开发者意味着什么,尤其是在国内复杂的搜索引擎环境下。

如今的网页开发,JavaScript (JS) 扮演着举足轻重的角色。就像谷歌的 John Mueller 说的,"它不会轻易消失。" 各种炫酷的交互、动态加载的内容,很多都离不开 JS。

然而,JS 对于 SEO 来说,一直是个有点"头疼"的话题。不少 SEO 同学谈 JS 色变,觉得它对搜索引擎不友好。这背后,一方面可能是因为 JS 开发和网页渲染机制本身比较复杂,另一方面也源于 SEO 人员和开发团队之间沟通的鸿沟

这篇文章,咱们不光聊理论,更要做个实验,看看搜索引擎到底是怎么跟 JS "打交道"的。

(如果你想深入了解 JS SEO 的基础知识,可以先看看谷歌官方的一些资源,比如 Martin Splitt 的分享,了解两阶段索引等概念。)

简单来说,谷歌处理 JS 内容大致分两步:

  1. 第一阶段(抓取和索引 HTML): 谷歌蜘蛛先抓取页面的初始 HTML 源代码。如果你的关键内容(比如标题、核心文本)直接写在 HTML 里(通常是服务器端渲染 SSR 或静态生成 SSG 的结果),那么在这个阶段就能被快速索引。
  2. 第二阶段(渲染和索引 JS 内容): 抓取完 HTML 后,谷歌会把页面丢进一个叫 WRS (Web Rendering Service) 的地方,用类似 Chrome 浏览器的环境去执行 JS,渲染出最终的页面效果 (DOM)。然后再对渲染后的内容进行索引。

谷歌抓取、索引、渲染流程示意图

问题在于,第二阶段需要排队等待渲染资源,有明显的延迟。而且,更关键的是,并非所有搜索引擎都像谷歌那样有强大的 JS 渲染能力。尤其是国内的百度等搜索引擎,在处理复杂的、完全依赖客户端 JS 渲染 (CSR) 的内容时,能力相对有限,很容易抓不到或抓不全。

"如果你希望内容尽快被收录,确保最重要的东西在 HTML 源代码里(服务器端渲染)。" —— Martin Splitt

一个简单的实验:搜索引擎处理 JS 的真实表现

为了直观感受搜索引擎如何处理纯 JS 渲染的内容,我做了个小测试。这个测试比较"极端",纯粹是为了观察行为,不代表最佳实践。

1)测试页面设置

我创建了一个非常简单的网页,特点如下:

  • 初始 HTML 极简: 只有基本的 HTML 结构标签,<title> 标签是空的,页面主体里只有一个 <h4> 副标题和页脚的一小段文字。这点文字是为了让搜索引擎在第一阶段就能抓到点东西。
  • 核心内容完全由 JS 渲染: 页面的标题 (<title>), 主标题 (<h1>), 描述 (<meta name="description">), 大段的正文内容(用 GPT-3 生成的),以及图片等,全部通过一个 JS 函数在客户端动态生成并插入到页面中。这个 JS 函数会在 DOM 加载完成后 ($(document).ready()) 执行。
  • 内容通过 AJAX 获取: 为了增加复杂度,JS 函数渲染所需的内容(文字、JSON 数据)并不是直接写在 JS 代码里,而是通过远程 AJAX 请求从服务器获取的。
  • 结构化数据也由 JS 注入: Article 类型的 Schema.org 结构化数据也是通过 JS 异步获取 JSON 后注入页面的。
  • 图片预加载测试: 有一张图片通过 JS 渲染,同时在 HTML 的 <head> 里用 <link rel="preload"> 进行了预加载,想看看这是否能加速图片的索引。
1// 简化示例:页面加载完成后执行 JS 渲染 2$(document).ready( function () { 3 // 通过 AJAX 获取文章数据 4 $.ajax({ 5 url: '/api/get-article-data', 6 success: function(data) { 7 // 使用获取的数据渲染页面元素 8 $('title').text(data.title); // 动态设置页面标题 9 $('h1#main-title').text(data.h1); // 动态设置 H1 10 $('meta[name="description"]').attr('content', data.description); // 动态设置描述 11 $('#article-content').html(data.body); // 动态插入文章主体 12 $('#article-image').attr('src', data.imageUrl); // 动态设置图片 13 14 // 动态注入 Schema.org 结构化数据 15 var script = document.createElement('script'); 16 script.type = 'application/ld+json'; 17 script.text = JSON.stringify(data.schemaData); 18 document.head.appendChild(script); 19 } 20 }); 21});

设计思路: 我故意让 JS 修改 <title>, <h1>, <meta description>,因为如果搜索引擎真的渲染并采纳了 JS 生成的内容,那么 SERP(搜索结果页面)上的摘要信息会完全改变,这样就能直观判断 JS 是否被处理了。

2)提交页面并初步测试

页面发布后,我分别通过 Google Search Console, Bing Webmaster Tool 和 Yandex Webmaster Tool 提交了 URL。

同时用了一些工具进行测试:

  • Merkle 的 Fetch & Render 工具: 这个工具模拟 Googlebot 渲染页面,结果显示能正确渲染出 JS 加载后的完整内容。 注意:Merkle 还有个预渲染测试器,可以检查服务器针对不同 User-Agent 返回的 HTML 是否不同,这对于检测动态渲染很有用。

Merkle Fetch & Render 工具正确渲染了 JS 内容

  • 谷歌富媒体搜索结果测试 (Rich Results Test): 这个官方工具也成功生成了渲染后的 DOM,并正确识别出了通过 JS 注入的 Article 结构化数据

谷歌富媒体测试工具生成的 DOM

  • Google Search Console 的 URL 检查工具: 有时候会显示渲染不完整,推测可能是因为工具有执行时间限制。

这些初步测试表明,至少谷歌系的工具具备渲染这个纯 JS 页面的能力

3)第一阶段索引结果

提交后几小时,我在 Google, Bing, Yandex 的搜索结果中都找到了这个页面。

三大搜索引擎第一阶段的 SERP 片段

重点来了: 这时候三个搜索引擎显示的标题和描述,都来自于页面初始 HTML 源代码中那点可怜的内容,而不是 JS 渲染后的内容。

页面上的图片也都没有被索引。

这完美印证了两阶段索引:搜索引擎先快速索引了初始 HTML。

4)尝试触发第二阶段处理

第二天,结果依然没变。为了"催促"搜索引擎进行第二阶段的 JS 渲染和处理,我做了几件事:

  • 修改了页面的初始 HTML(加了个 favicon、作者名、发布日期),看看搜索引擎会不会在处理 JS 之前先更新这个片段。
  • 更新了 JS 注入的结构化数据中的 dateModified 字段。
  • 提交了包含该 URL 的 Sitemap。
  • 再次通过各自站长工具(包括 Google Indexing API)通知搜索引擎页面有更新。

实验结果:谷歌能行,但有延迟;其他引擎……

又过了几天,结果出来了:

1) 谷歌:

  • 成功渲染并更新了 SERP 片段! 搜索结果显示的标题和描述,变成了由 JS 动态生成的内容。

谷歌最终更新了 SERP 片段,采纳了 JS 内容

  • 使用 site: 命令查看,发现谷歌确实索引了通过 JS 渲染出来的正文内容

谷歌 site: 命令显示 JS 渲染的内容已被索引

  • 通过 JS 渲染的第一张图片也被索引,出现在 Google Images 中。

通过 JS 渲染的图片出现在 Google Images 中

结论:谷歌确实能处理纯客户端 JS 渲染的内容(包括 AJAX 加载、动态注入),但需要经过第二阶段处理,存在几天的时间延迟。

2) Bing 和 Yandex:

  • 在整个测试期间(发布后数天),它们在 SERP 上显示的片段始终没有改变,依然是初始 HTML 的那点内容。
  • 结论:Bing 和 Yandex(至少在这个测试中)未能有效处理和采纳通过客户端 JS 渲染的核心内容。

3) 图片预加载:

  • <head> 中使用 <link rel="preload"> 预加载的那张图片,并没有比另一张未预加载的图片更快被索引。这个小实验说明预加载对图片索引速度可能没啥帮助。

针对非谷歌搜索引擎的"补救":动态渲染

既然 Bing 和 Yandex 处理 JS 不给力,我又尝试了动态渲染 (Dynamic Rendering)

动态渲染的原理是:服务器判断来访问的是普通用户浏览器还是搜索引擎爬虫。

  • 如果是用户浏览器,返回正常的、需要执行 JS 来渲染的页面。
  • 如果是搜索引擎爬虫(如 Bingbot, YandexBot, 也包括 Baiduspider),则在服务器端预先渲染好完整的 HTML 内容,直接返回一个静态版本给爬虫。

动态渲染工作原理示意图

配置好动态渲染后,我再次请求 Bing 和 Yandex 索引。

  • Yandex: 几小时后,SERP 片段更新了!显示的是预渲染后的完整内容。

配置动态渲染后,Yandex 更新了 SERP 片段

  • Bing: 在测试结束时仍未更新,但理论上应该也能处理预渲染的 HTML。

动态渲染的启示: 对于那些 JS 处理能力较弱的搜索引擎(尤其是百度!),动态渲染是一种能让它们看到完整内容的技术方案。但要注意,动态渲染配置复杂,且如果处理不当,可能被视为"伪装"(Cloaking),有一定风险。

结论与实战建议 (尤其针对国内环境)

这个简单的实验清晰地展示了:

  1. 谷歌确实能处理客户端 JS 渲染的内容,但存在延迟。 它会先索引初始 HTML,几天后再渲染 JS 并更新索引。对于需要快速收录和排名的内容(如新闻、时效性强的活动),纯 CSR 风险很大。
  2. Bing、Yandex 以及国内的百度等搜索引擎,处理复杂客户端 JS 渲染的能力依然有限。 完全依赖 CSR 可能导致这些搜索引擎无法抓取或理解你的核心内容,严重影响收录和排名。

基于以上结论,给国内开发者和 SEOer 的建议:

  • 核心内容和 SEO 关键元素,优先使用 SSR 或 SSG! 对于页面标题 (<title>), 描述 (<meta description>), H1, 核心文本内容,重要的导航链接等,务必确保它们出现在初始 HTML 源代码中。这是保证内容能被所有搜索引擎(特别是百度)快速、准确理解的最稳妥方式。
    • 现在流行的前端框架如 Vue (配合 Nuxt.js) 和 React (配合 Next.js) 都提供了成熟的 SSR 和 SSG 解决方案,利用好它们。
  • 谨慎使用纯客户端渲染 (CSR) 加载核心内容。 如果你的主要目标市场包含百度等对 JS 处理能力有限的搜索引擎,纯 CSR 基本不可取。即使只考虑谷歌,也要接受收录延迟的现实。
  • 动态渲染可作为备选,但需谨慎。 如果现有架构难以改造成 SSR/SSG,动态渲染可以作为一种折中方案,确保搜索引擎能看到内容。但配置复杂,且要注意避免 Cloaking 风险。对于大部分新项目,优先考虑 SSR/SSG。
  • 权衡用户体验与技术复杂度。 JS 能带来更好的用户体验,但也会增加技术复杂度和 SEO 风险。是否值得为了极致交互而牺牲部分搜索引擎友好性?需要根据业务目标和目标用户来权衡。
    • 现代 JS 框架提供的 SSR/SSG 能力,可以在很大程度上兼顾两者。

"问题是…在项目中引入 SSR/SSG 的'复杂性'是否值得?答案很简单:是的,如果你想在保证 SEO 效果的同时,提供出色的用户体验!"

最后提醒: 分析谷歌缓存的页面快照对于判断 JS 是否被处理没有意义,因为你看缓存时,浏览器会重新执行 JS。要看真实效果,还是得依赖 site: 命令、URL 检查工具的渲染截图以及 SERP 实际展现。

拥抱现代前端技术,但也别忘了给搜索引擎留条"明路"!