企业官网改版GEO实战:把 Blazor SPA 改造成 AI 爬虫可读的预渲染方案
适用读者:负责企业官网改版的后端/全栈工程师、接手老官网改造的技术负责人
去年接手一个老客户的企业官网改版需求,技术栈定的是 .NET 8 + Blazor WebAssembly。做完之后 Google 收录正常,但客户在 DeepSeek 和豆包里搜自家公司名,AI 的回答里完全没有他家的信息,甚至连"这家公司是做什么的"都答不对。排查下来,根因很典型:WASM 首屏空白,AI 爬虫根本执行不了 JS,抓到的 HTML 是一个空的 <app> 标签。
这篇文章把整个改造过程拆解出来,包括问题定位、预渲染方案选型、结构化数据注入的代码实现,以及改造前后 6 周的观测数据。
一、先搞清楚:AI 爬虫和传统爬虫差在哪
传统搜索引擎爬虫(Googlebot、Bingbot)大多具备 JS 渲染能力,SPA 页面只是收录慢,不至于抓不到。但生成式引擎的爬虫策略完全不同:
| 能力维度 | 传统搜索爬虫 | 生成式引擎爬虫(GPTBot/ClaudeBot 等) |
|---|---|---|
| JS 执行 | 多数支持(两段式渲染) | 基本不支持,抓到什么算什么 |
| 抓取预算 | 大,会重试渲染 | 小,单页通常只抓一次 |
| 内容利用 | 进索引,按关键词排序 | 切片后进语料库,供 RAG 引用 |
| 偏好格式 | HTML 全文 | 语义清晰、结构化的静态 HTML |
关键结论:面向 AI 引擎的内容,必须在服务端第一次响应就给出完整可读的 HTML。这不是 SEO 的加分项,而是及格线。
二、问题定位:用 curl 模拟 AI 爬虫
不要靠猜,直接模拟一个不带 JS 执行能力的抓取请求:
bash
curl -s -A "GPTBot/1.0" https://www.example.com/products \
| grep -o "<body[^>]*>.*" | head -c 500
改造前我们的输出是这样的:
html
<body>
<div id="app">
<!--Blazor: 组件树尚未渲染,需要加载 blazor.webassembly.js-->
</div>
<script src="_framework/blazor.webassembly.js"></script>
</body>
AI 爬虫看到的就是这个空壳。DeepSeek 的回答里没有我们,完全合理。
三、方案选型:预渲染的三条路
| 方案 | 改造成本 | 适用场景 | 我们的取舍 |
|---|---|---|---|
| Blazor SSR(Interactive Server) | 中 | 内容页为主、有交互 | 内容页采用 |
| 静态站点生成(SSG / 拷贝产物) | 低 | 产品介绍、案例展示 | 公司介绍、产品列表页采用 |
| 无头浏览器动态回源 | 高 | 万不得已的兜底 | 不推荐,维护成本高 |
最终架构是混合模式:营销内容页走 SSR/SSG,查报价这类交互组件用 Blazor Server 交互模式挂在页面局部(.NET 8 的 per-page interactivity 正好支持)。核心原则是内容与交互分离。
四、实施:预渲染 + JSON-LD 注入
4.1 开启 SSR 预渲染
在 .NET 8 的 Blazor Web App 模板下,内容页直接用 SSR 模式,InteractiveServerRenderMode 只挂到需要的组件:
csharp
// Program.cs
builder.Services.AddRazorComponents()
.AddInteractiveServerComponents();
app.MapRazorComponents<App>()
.AddInteractiveServerRenderMode();
内容页顶部不声明 @rendermode,默认就是静态 SSR------服务端输出完整 HTML。
4.2 结构化数据组件
GEO 的另一半是让 AI 理解页面实体的类型。我们做了一个可复用的 JsonLd 组件,页面级注入 Organization、Product、BreadcrumbList:
csharp
// JsonLd.razor ------ 用 @key 防止流式渲染时重复输出
<script type="application/ld+json" @key="ld-@Id">@Payload</script>
@code {
[Parameter, EditorRequired] public string Id { get; set; } = "";
[Parameter, EditorRequired] public object Payload { get; set; } = new();
protected override void OnInitialized()
{
// 序列化时忽略 null,避免 Schema 校验器报 invalid value
}
}
产品详情页的用法:
csharp
<JsonLd Id="product" Payload="@ProductLd.Build(Product)" />
@code {
[Parameter] public string Sku { get; set; } = "";
private Product Product { get; set; } = new();
// 从数据库加载产品后构造 Product Schema
}
4.3 sitemap 与 llms.txt 一并补齐
预渲染解决"能不能读",llms.txt 解决"愿不愿意读"。站点根目录放一份机器可读的内容索引,指向每个内容页的 Markdown 纯文本版本(/content/xxx.md),AI 引擎抓取成本大幅下降。这部分用 Minimal API 十几行就能实现:
csharp
app.MapGet("/content/{slug}.md", async (string slug, ArticleService svc) =>
await svc.GetMarkdownAsync(slug) is { } md
? Results.Text(md, "text/markdown; charset=utf-8")
: Results.NotFound());
五、改造前后 6 周数据对比
用统一口径统计:每周模拟 20 个行业长尾问题向 DeepSeek、豆包提问,记录官网域名是否出现在回答引用中。
| 指标 | 改造前 | 改造后第 6 周 | 说明 |
|---|---|---|---|
| 首屏 HTML 字符数(产品页) | 1,042 | 18,500+ | 空壳 → 完整内容 |
| AI 引用命中数(40 问/周) | 1 | 9 | 约 5 倍以上提升 |
| AI 爬虫抓取次数/周(日志统计) | 6 | 41 | 内容可读后爬虫愿意回访 |
| Schema 校验通过页面占比 | 0% | 96% | 剩余 4% 是历史遗留页面 |
要坦率说明的是:样本只有我们自己的站点,数据只代表趋势方向,不构成任何普遍结论。
六、踩过的坑
- 流式渲染与
<script>的冲突 :SSR 默认流式输出时,@key不加会导致 JSON-LD 重复注入,Schema 校验直接报错。 - prerender 环境下不能访问 HttpContext:预渲染阶段数据库上下文要能脱离请求工作,我们的做法是内容页数据全部走缓存服务,首帧不查库。
- WASM 交互组件包进 SSR 页面后水合报错 :确保
InteractiveServerRenderMode的 JS 在<script src="_framework/blazor.web.js">位置正确加载,不要手动搬脚本。
七、趋势预判与收尾
生成式引擎对内容的消费方式正在从"整页抓取"走向"实体级抓取"------AI 更关心你的页面里"这个公司是谁、这个产品有什么参数",而不是关键词密度。官网改版如果只做视觉升级不做结构化改造,等于把流量入口让给了同行。
一句话收尾:预渲染保证 AI 爬虫读得到,JSON-LD 保证它读得懂,llms.txt 保证它读得省力。三者补齐,官网才算具备了被生成式引擎引用的最基本资格。如果你也在做 Blazor 官网的 GEO 改造,欢迎在评论区交流具体组件的实现细节。
关键词:GEO优化、AI优化AIO、Blazor SSR 预渲染、JSON-LD 结构化数据、llms.txt、AI 爬虫抓取、.NET 8 官网改版、DeepSeek 引用