本文约 3,011 字,预计阅读 8 分钟。文中命令与配置均经过实测,可直接参考执行。
引言:页面明明有内容,Google 却说「未检测到内容」
用前端框架做的站点常遇到这种情况:URL 检查工具里「已抓取的 HTML」是空的,或者「已编入索引」但搜索结果摘要里空空如也。浏览器里打开一切正常,Google 却看不到内容。原因是 Googlebot 处理页面的方式与我们打开浏览器的方式不同——它分两步走,第二步可能要等很久,甚至可能不做。
原理分析:两阶段抓取到底发生了什么
Googlebot 的处理流程可以拆成三段:
| 阶段 | 动作 | 产出 | 时间特征 |
|---|---|---|---|
| 抓取 | 下载 HTML、CSS、JS 等资源 | 原始 HTML 存入抓取队列 | 秒级 |
| 渲染 | 用渲染服务执行 JS,生成 DOM | 渲染后的 HTML | 可能延迟数秒到数天 |
| 索引 | 基于渲染结果抽取内容与链接 | 加入索引 | 再一次排队 |
关键点在于:Googlebot 首次抓取时只拿 HTML,不执行 JS。JS 生成的 DOM 要等渲染队列排到才处理。而渲染队列是有成本的,需要真实的浏览器环境执行脚本,因此 Google 会把它作为第二优先级处理。对于大型 JS 站点,这个延迟可以是数天。
由此产生两个必须理解的现实:
第一,原始 HTML 里没有的内容,在渲染完成前不可见。这包括内容,也包括链接——用 JS 动态生成的 <a> 标签,在渲染前不会被发现,内部链接结构因此失效。
第二,渲染不是无限次重试的。脚本报错、接口超时、资源被 robots.txt 屏蔽(导致渲染时拿不到 CSS/JS),都会让渲染结果不完整,最终索引的是残缺内容。
Google 官方文档对 JS SEO 给出的核心建议是:确保服务器返回的 HTML 已包含主要内容(服务端渲染或预渲染),并把 JS 作为增强而非内容载体。同时,robots.txt 不应屏蔽 CSS 与 JS 资源,否则渲染会失败。
保姆级实操
步骤一:用 URL 检查工具对比原始与渲染结果
在 Search Console 的网址检查里点「查看已抓取的页面」,切到「HTML」与「屏幕截图」两个标签,对比原始 HTML 与渲染结果。如果原始 HTML 里没有正文而渲染结果里有,说明是纯客户端渲染。
步骤二:用 curl 模拟无 JS 环境
最直接的自查方式:
# 不带 JS 执行,看原始 HTML 里有没有正文
curl -s https://mimangfei.com/sample-post/ | grep -c "<article"
curl -s https://mimangfei.com/sample-post/ | sed -n 's/.*<title>\(.*\)<\/title>.*/\1/p'
步骤三:确认 robots.txt 没有屏蔽渲染资源
这是最容易被忽略的渲染失败原因:
User-agent: Googlebot
Allow: /
Allow: /wp-includes/
Allow: /wp-content/themes/
Allow: /wp-content/plugins/
Disallow: /wp-admin/
步骤四:把关键内容放到服务端输出
WordPress 主题侧要确保正文与链接在 PHP 阶段就输出,而不是靠 JS 补。检查主题的 content.php 是否直接输出 the_content():
<article id="post-<?php the_ID(); ?>" <?php post_class(); ?>>
<h1><?php the_title(); ?></h1>
<div class="entry-content">
<?php the_content(); ?>
</div>
<?php wp_link_pages(); ?>
</article>
步骤五:链接必须是真实 <a href>
用 onclick 或 router.push 跳转的链接,Googlebot 在渲染前发现不了。确保导航与相关文章输出的是标准锚点:
$related = get_posts( array(
'category__in' => wp_get_post_categories( get_the_ID() ),
'posts_per_page' => 6,
'exclude' => array( get_the_ID() ),
) );
foreach ( $related as $r ) {
printf( '<a href="%s">%s</a><br>', esc_url( get_permalink( $r ) ), esc_html( get_the_title( $r ) ) );
}
步骤六:用 Search Console 的 URL 检查看渲染状态
网址检查工具会给出「已抓取的页面」的截图与 HTML,也能看到资源是否被屏蔽。若看到「页面资源无法加载」,优先排查 robots.txt 与资源 URL 的协议(HTTP 与 HTTPS 混用会被拦截)。
步骤七:必要时做预渲染
对必须依赖 JS 的模块,可用预渲染服务或构建期静态化。判断标准是:这一块内容是否参与排名。不参与排名的部分(评论区、实时数据)用 JS 完全没问题。
常见踩坑
- 正文完全由前端框架渲染,原始 HTML 只有一个空的
<div id="app">。 - robots.txt 屏蔽了
/static/或/assets/,导致渲染拿不到 CSS/JS,渲染结果残缺。 - 内链用
onclick跳转,渲染前无法被发现,内部链接结构失效。 - 依赖客户端调用接口取数据,接口超时导致渲染结果为空。
- 认为「URL 检查工具的截图正常」就等于没问题,忽略了渲染延迟对新页面的影响。
- 案例:某站点用 Vue 重写了文章页,上线两周内新文章的收录率从 92% 掉到 31%。排查发现正文由接口异步加载,渲染队列平均延迟 4 天,且部分请求因接口限流返回空。改为服务端输出正文后,收录率一周内回到 88%。
- 用 JS 做分页加载,第 2 页之后的内容没有任何静态链接入口。
总结
理解两阶段机制的判断标准是:把 JS 全部禁用后,页面还剩多少可索引内容。剩下的部分就是 Googlebot 首次抓取能拿到的全部。落地做法是先用 curl 看原始 HTML,再确认 robots.txt 未屏蔽渲染资源,最后把参与排名的正文与内链改为服务端输出。JS 可以作为增强,但不要作为内容的唯一载体。渲染延迟带来的收录滞后可以通过 抓取预算优化 与 服务器日志分析 来观测和验证;如果站点同时面向多地区,还要注意 国际站点URL结构 带来的版本数量放大效应——每个语言版本都要单独排队渲染。
