博文已索引但不展示:一次 Bing SEO 问题排查记录
📝本文首发于 栏轩·阁
欢迎访问阅读原文,获取更好的阅读体验。
不久前在 Bing Webmaster Tools 中发现一个奇怪的现象:站内文章页面明明已经被索引,却从未出现在任何搜索结果中。
site 查询只返回了首页,所有具体文章仿佛在搜索引擎里"隐身"了。而浏览器直接访问页面,一切正常渲染——标题、正文、图片样样不缺。
这就像一间灯火通明的房间,从外面看却一片漆黑。
一点前置知识:Next.js App Router 的渲染机制
要理解这个问题,需要先了解 Next.js 的两种组件模式。
服务端组件(Server Component)在服务器上运行,生成的 HTML 直接返回给浏览器和爬虫。它可以读取文件、查询数据库、执行任何服务端操作。在 App Router 中,page.tsx默认就是服务端组件。
客户端组件(Client Component)在浏览器中运行,行为和传统的 React SPA 一致。声明了"use client"的组件,其内部的useEffect等在服务端渲染时不会执行——这意味着爬虫拿不到它们产生的内容。
背景:已索引却不可见
在 Bing Webmaster Tools 中,我看到这样一个状态:
site:lxpavilion.top能查到首页site:lxpavilion.top/article/42/没有任何结果- 但站长工具提示该 URL“已成功编制索引”
一个被索引了的页面,为什么在搜索结果中找不到?
如果内容有问题,索引阶段就会失败。如果索引成功了,理论上就应该能被搜到。这两个状态之间的矛盾,说明问题出在索引的内容本身——Bing 确实来爬过,但爬到的内容可能是不完整的。
排查过程
第一步:用爬虫的视角看页面
人眼看到的不算数,需要看服务器返回的原始 HTML。
直接 curl 查看文章页面的响应,结果让人意外:返回的 HTML 中没有<h1>,没有<p>,没有任何正文标签。只有孤零零的<title>标签和一堆 CSS/JS 引用。
页面在爬虫眼里,就是一个有标题的空壳。Bing 索引了这个页面,但索引的内容几乎为空,自然也就不会在搜索结果中展示。
第二步:定位根因
查看代码后发现,博客的三个详情页(文章、项目、文学)都是同一个模式:
服务端渲染时,page.tsx读取了 JSON 文件,从中提取了标题、摘要、封面等信息来生成<title>、<meta>和 JSON-LD 结构化数据——这部分爬虫能正常看到。但页面正文的渲染交给了客户端组件,而客户端组件在加载阶段显示的是空白或 loading 状态。
真正的数据要等useEffect触发 API 请求、数据返回后才渲染。爬虫不会等——它拿到服务端返回的 HTML 就离开了,看不到任何正文内容。
问题就在这里:服务端组件明明已经从 JSON 文件中读到了数据,却没有把内容传递到渲染层。REST API 在浏览器中返回了完整的数据,但爬虫压根不会走到那一步。
修复方案
修复的核心思路很直接:服务端已经读过数据了,为什么不把它传给客户端?
加载中时,服务端把从 JSON 中读到的标题作为 prop 传给客户端,客户端直接渲染,不再等 API 返回。同样的方式处理正文内容——服务端多读一个content字段传下去,客户端在加载阶段直接用这份数据渲染正文文字,爬虫就能抓取到页面内容了。
关键的设计考虑是:客户端保留 API 请求作为后备。如果 JSON 文件不存在(例如本地开发环境中),初始内容为空,组件照常走 API 获取,对开发体验零影响。
用一句话总结修复策略:服务端倾其所有地把数据传给客户端,客户端用它能拿到的最好数据直接渲染,API 数据到了再做更新。
效果对比
修复后的变化很清晰:
- 爬虫能否看到
<h1>:❌ → ✅ - 爬虫能否看到正文文字:❌ → ✅
- 页面功能和交互:完全不变
- Docker 部署:不受影响
改动本身很小,但效果是质变的——页面从"有标题的空壳"变成了"有内容的页面"。
一些思考
为什么初期会这样设计?
回头看,这个模式是合理演变的结果。
项目需要同时支持静态部署(GitHub Pages)和 Docker 部署两种模式。客户端组件需要兼容两种数据源,统一走useEffect请求是最干净的写法。服务端page.tsx的 metadata 功能是逐步叠加的——每次加一点,每次都是"够用就行",没人会回头去审视整个数据流。
在"页面正常显示"这个目标下,这套逻辑工作得很好。直到 SEO 需求出现,才发现服务端打开过同一个 JSON 文件,却只用了其中不到 10% 的数据。
不是设计错了,是需求变了
这个案例让我重新审视了一个惯性思维。
SPA 时代"客户端渲染一切"的理念深入人心——服务端只负责提供一个空的 HTML 壳子和一堆 JS 脚本,所有内容由浏览器执行 JavaScript 后渲染。但在 Next.js App Router 的体系下,服务端组件能做比你想象的更多。每次读取数据的时候,都多问自己一句:这些数据能不能也传到渲染层?
这也引出了一个更深的问题:对于内容型站点(博客、文档、新闻),SSR 不仅仅是性能优化手段,更是 SEO 的基石。Next.js 相比于纯 SPA 框架的优势,不在于"客户端体验更好",而在于"你可以选择在服务端渲染什么,在客户端渲染什么"。如果所有内容都扔给客户端,那和直接用 React SPA 没有本质区别。
两个可以立刻检查的点
如果你也在用类似的架构模式(服务端组件生成 metadata、客户端组件通过 API 获取内容),可以回去检查两件事:
page.tsx中generateMetadata读到的数据,是否也传给了客户端组件?数据已经在服务端手上了,只是没有多传一步。- 加载状态是否把正文内容一起隐藏了?Loading 状态不应该完全替代内容渲染——至少标题骨架和内容占位应该让爬虫看得到。
这两行代码的改动成本几乎为零,却可能是你的页面在搜索引擎中"存在"与"隐身"的分界线。
写在最后
SEO 优化很多时候不是做了什么复杂的事情,而是不让已经做的工作白费。
数据明明已经在服务端读到了,只是没有传到渲染层——这个发现比任何优化技巧都更有价值。它提醒我们,在架构设计时要跳出"正常用户看起来没问题"的视角,思考爬虫和非 JavaScript 环境会看到什么。
对于独立博主而言,搜索引擎是重要的流量来源。让爬虫看到内容,是比"让页面更好看"优先级更高的事情。
