前段时间我花了半天时间,给一个内容型网站做了一次完整的 GEO(Generative Engine Optimization,生成式引擎优化)改造。改完之后用 GEO 检测工具复查,大部分指标从红转绿。
这篇文章把整个流程整理出来:怎么发现问题、每个问题怎么修、以及一份可以直接套用的检查清单。案例站点是一个典型的 Next.js 内容站,但问题几乎都是通用的——你的站大概率也有同款。

一、先搞清楚:GEO 和 SEO 有什么区别
SEO 优化的对象是搜索引擎的爬虫和排序算法;GEO 优化的对象是 AI 搜索引擎(ChatGPT、Perplexity、豆包、Kimi 等)的抓取和引用逻辑。
两者最大的区别有一个非常具体的工程含义:
AI 爬虫基本不执行 JavaScript。
Google 至少还有二段渲染机制,会执行 JS 再索引;百度对 JS 渲染支持很差;而 GPTBot、ClaudeBot 这类 AI 爬虫,抓的就是原始 HTML。如果你的内容靠客户端 JS 渲染,对 AI 引擎来说,这个页面就是空壳。
第二个区别是内容偏好:AI 引擎引用内容时,偏爱结构清晰、结论明确、可直接摘录的段落——标题层级、列表、表格、结构化数据(JSON-LD)的权重,比传统 SEO 时代更高。
二、体检方法:用爬虫的视角看自己的站
改造之前先体检。方法很简单:伪装成 AI 爬虫,抓取自己的页面,看返回的 HTML 里到底有什么:
curl -s https://example.com/blog/some-article \
-A "Mozilla/5.0 (compatible; GPTBot/1.2)" \
-o page.html
然后检查两件事:
- 正文文字在不在 HTML 里(直接在 HTML 源码里搜索正文中的一句话)
- 有没有
<h2>、<table>这类语义化标签
这一步的结果直接决定了后面所有工作的优先级。
三、发现的问题与修复(按严重程度排序)
1. 致命问题:正文全靠客户端渲染
案例站的文章正文组件是一个 Markdown 预览组件,为了减小首屏体积做了动态加载。这导致 SSR 输出的 HTML 里,正文位置只有一个「加载内容中...」的占位符,正文文本只存在于 JS 数据载荷里。
实测:用 GPTBot 的 UA 抓取文章页,HTML 中 <h2> 数量为 0——文章的所有小节标题都不在初始 HTML 里。
这意味着:百度基本读不到正文,AI 引擎完全读不到正文。几千字的文章,对这些爬虫来说等于不存在。
修复:把正文渲染从客户端挪回服务端。具体做法是用 react-markdown + remark-gfm + rehype-highlight 在 Server Component 里把 Markdown 同步渲染成语义化 HTML,客户端动态加载的预览组件直接删除。改完再测,同样的抓取方式下 <h2> 从 0 变成 8,表格、引用块、代码高亮全部直出。
这是我这次改造中收益最大的一步,没有之一。
2. 缺少 llms.txt —— AI 时代的 robots.txt
llms.txt 是一个正在形成的约定:在站点根目录放一个 Markdown 文件,告诉 AI 引擎这个站是干什么的、核心内容在哪。类似于给 AI 引擎准备的站点地图。
修复时我加了两个文件:
/llms.txt:站点简介 + 页面结构 + 全部文章的标题、链接、日期、摘要/llms-full.txt:所有文章的 Markdown 全文,供 AI 引擎一次性摄取
两个文件都由服务端动态生成、每小时刷新,发新文章自动进入索引。然后在 robots.txt 里声明这两个入口。
3. 结构化数据(JSON-LD)的细节错误
案例站本来就有 BlogPosting 结构化数据,但仔细检查发现了好几个问题:
publisher用了Person类型却带了logo属性——logo是Organization的属性,校验器会报警dateModified一直等于发布日期,因为代码里映射的是创建时间而不是更新时间——文章更新后搜索引擎感知不到- 没有面包屑(BreadcrumbList)
- 首页只有 Person 实体,没有 Organization——AI 引擎无法识别「这个站属于谁」
修复:publisher 改为 Organization,dateModified 改用真实的 updated_at 字段,文章页补上 BreadcrumbList(首页 › 博客 › 文章),首页补齐 WebSite + Person + Organization 三个实体声明。
4. RSS 只输出摘要
案例站的 RSS 每个条目只有百来字的摘要。RSS 是内容聚合器和部分 AI 引擎的重要抓取源,只给摘要等于自断一路。
修复:每个条目增加 <content:encoded> 输出全文 HTML,feed 体积从 15KB 涨到 195KB——这才是 RSS 该有的样子。顺手把频道描述改成与站点定位一致的文案。
5. sitemap 的 lastmod 被冻结在构建时刻
这是个很隐蔽的问题:sitemap 里的 lastmod 字段用的是 new Date(),听起来没问题对吧?但路由在构建时被静态化了,于是所有页面的 lastmod 全部定格在构建那一刻,之后再也不变。搜索引擎看 lastmod 半年不动,会降低抓取频率。
修复:sitemap 路由改成 ISR 每小时重新生成;文章的 lastmod 用真实更新时间;首页和列表页的 lastmod 跟随最新文章的更新时间。
6. 分享图和 favicon 全是 webp
案例站的 OG 分享图、favicon、apple-touch-icon 都是同一张 webp 图。webp 对现代浏览器没问题,但微信、iMessage 和不少平台的分享卡片对 webp 支持很差——链接分享出去没有缩略图,白白损失传播。
修复:生成一张 1200×630 的 PNG 品牌分享图 + PNG 版 favicon,全部自托管。另外还发现一个坑:Next.js 的页面级 openGraph 不会继承 layout 里的分享图,首页之前根本没有 og:image,五个页面的 openGraph 都得单独补上。
7. www 和裸域并存,重复内容
检测发现 www.example.com 和 example.com 都能访问且不跳转,而且 www 的 HTTPS 证书根本没配。两个域名同内容并存会分散权重。
修复:证书加上 www 域名(acme.sh 签发时记得给 nginx 的 80 端口配置 ACME 挑战文件的绿色通道,否则验证 404),nginx 层把裸域 301 到 www。关键收尾:代码里所有 canonical、sitemap、结构化数据里的域名要和 301 的目标域统一,否则每个对外 URL 都多跳一次,信号矛盾。
8. GEO 工具检测出的「软性问题」
改完上面这些硬问题后,我用 GEO 检测工具扫了一遍首页,工具又报了几条:
- og:description 只有 31 字符(建议 120-200)
- 首页正文只有 552 词(建议 600+)
- 全文 0 个
<ul>、0 个<table>,缺少可扫描的结构化内容 - 没有 Organization schema(前面已修)
修复:首页描述扩写到 131 字符;加了一个「关于本专栏」区块(博主身份 + 内容主线 + 更新频率,顺便把词数补足);文章列表从 <div> 堆叠改成语义化的 <ul><li>。
工具还建议加 FAQPage 和 HowTo schema——这两条适合问答型和步骤型文章,属于内容写作层面的优化,不适合在站点层面硬套,留作后续写文章时用。
四、沉淀一份 GEO 检查清单
整个流程走完,我总结了一份可以复用的清单。给任何内容站做 GEO 体检,按这个顺序查:
可抓取性(生死项)
- 用 GPTBot UA 抓页面,正文在不在初始 HTML 里?
- 有没有 llms.txt?robots.txt 有没有误伤 AI 爬虫?
结构化数据(信任项)
- JSON-LD 的 publisher 类型对不对?dateModified 是真实更新时间吗?
- 首页有没有 Organization / Person 实体声明?
- 文章页有没有面包屑?
内容分发(传播项)
- RSS 有没有全文输出?
- sitemap 的 lastmod 是不是真实的?
- OG 图是不是 PNG/JPG?每个页面都有 og:image 吗?
域名与内容(信号项)
- www 和裸域统一了吗?canonical 指向的域名和 301 目标一致吗?
- 描述文字够长吗?页面有足够的实质内容和列表/表格结构吗?
总结
这次改造的核心认知就一句话:
把你的站点当成一个「不执行 JavaScript 的读者」来设计。
传统前端优化追求把 JS 拆小、延后加载,这没错;但正文这种核心内容必须服务端直出。SEO 时代这么做是「更快被收录」,GEO 时代这么做是「能不能被 AI 看见」的分水岭。
工具链层面,llms.txt、全文 RSS、准确的结构化数据,本质上都是同一件事:降低 AI 引擎理解你内容的成本。你让它越容易读,它就越愿意引用你。