作为一名天天和代码打交道的程序员,最近我发现网站的流量生态发生了一些微妙的变化。 以前我们做个人博
从 SEO 到 GEO:我的博客系统 AI 检索优化与落地实践
发布时间: 2026-06-19 (a month ago)
GEO

作为一名天天和代码打交道的程序员,最近我发现网站的流量生态发生了一些微妙的变化。

以前我们做个人博客或技术网站,每天盯着的是 Google 或百度的收录量,研究怎么通过关键词匹配和外链提高 PageRank(网页排名)。

但现在,越来越多的朋友在遇到技术难题时,不再去搜索引擎一页页翻答案,而是直接打开 ChatGPT、Kimi、Perplexity 或者使用 Google 的 AI Overviews(AI 搜索)提问:“Go 语言里的 GMP 模型是怎么回事?”

这时候,AI 会在几秒钟内给你整理出一篇通俗易懂的回答,并且在旁边贴心地标上几个彩色的小气泡(引用来源链接),点开一看,正是某个开发者的技术博客。

这种让 AI 抓取你的内容,并在回答中把你的网站作为“参考书目”引用出来的技术,就是最近火起来的 GEO (Generative Engine Optimization,生成式引擎优化)

前段时间,我花精力将自己的博客系统做了一次彻底的现代化重构。这篇文章就以我的实际项目(Nuxt 4 + Go/Gin)为例,用大白话加代码的方式,手把手带你看看我是如何落地 SEO 和 GEO 优化的。


1. 为什么大模型能搜到你的网页?通俗聊聊 RAG 机制

在动手改代码之前,我们先花一分钟理解一下 AI 搜索的原理。为什么 AI 能知道你的网站写了什么?

现在的 AI 搜索(如 Perplexity)后面普遍挂载了一个叫 RAG(检索增强生成) 的技术。简单来说,它的工作流程是这样的:

  1. 用户提问:用户输入“如何用 Docker 部署 Elasticsearch 8”。
  2. 小秘书搜集资料:AI 搜索引擎先假装成普通用户,用传统搜索引擎去网上把排在前面的几十个网页全部抓下来(这需要你的网页非常利于抓取,不能是动态加载的空壳子)。
  3. 大模型提炼回答:AI 读完这几十个网页,把里面的核心干货提炼出来,组合成一段通俗的话发给用户。
  4. 贴上参考链接:为了防止自己胡说八道(幻觉),AI 会把刚刚做出贡献的网页网址贴在旁边,当成“引用源”。

所以,GEO 优化的核心目的,就是降低 AI “搜集和阅读资料”的成本,让它觉得读你的文章最轻松、最靠谱


2. 核心技术落地:我的四步走改造方案

为了让 AI 觉得我们的网站“真香”,我给自己的博客系统做了以下四步升级。


第一步:扔掉 SPA,拥抱 SSR 与语义化 URL (Slug)

如果你的博客还是纯单页应用(SPA,比如用纯 Vue 或 React 打包出来的静态页面),在 AI 时代基本就宣告出局了。因为 SPA 初始只有一个 <div id="app"></div> 的空壳,内容全靠浏览器运行 JS 动态渲染。AI 爬虫可没空等你慢慢加载 JS,它一进来看到是空的,直接就转头走了。

所以我做的第一件事,就是将博客重构为 Nuxt 4 的服务端渲染(SSR)。这样当爬虫访问时,服务器直接就把渲染好的、带文字内容的完整 HTML 塞给它。

同时,我们还要改造网址(URL)的拼写。

  • 以前的网址https://yoursite.com/article/32 (冷冰冰的数字,爬虫和人类都看不懂)
  • 现在的网址https://yoursite.com/article/32-online-platform-personalized-recommendation (带上了英文关键字,语义清晰)

🛠️ 我在项目里的代码实现:

在前端,我写了一个工具类来自动转换文章链接。如果文章有英文标题,就优先转换英文;如果没有,就把中文特殊字符过滤掉并用中划线 - 连接:

typescript 复制代码
// utils/slug.ts

/**
 * 将中英文标题转换为符合 SEO/GEO 标准的扁平化 URL Slug
 * 比如将 "Docker 部署 ES 8.x" 变成 "docker-deployment-es-8x"
 */
export function getSlug(title: string, titleEn?: string): string {
    const baseText = titleEn || title;
    
    return baseText
        .toLowerCase()
        // 过滤掉除了英文字母、数字、中文、空格和中划线以外的杂质符号
        .replace(/[^a-z0-9\u4e00-\u9fa5\s-]/g, '') 
        // 把连在一起的空格全部换成单个中划线
        .replace(/\s+/g, '-')
        // 防止出现连续的多个中划线,比如 --- 变成 -
        .replace(/-+/g, '-')
        .trim();
}

/**
 * 拼装完整的文章链接
 */
export function getArticleLink(article: { id: number; title: string; title_en?: string }): string {
    const slug = getSlug(article.title, article.title_en);
    return `/article/${article.id}-${slug}`;
}

在后端(我用的是 Go 的 Gin 框架),路由直接设计成 /article/:id_slug。在解析参数时,其实非常通俗简单——我们只拿最前面的主键 ID 去查数据库,后面那一长串英文直接忽略掉:

go 复制代码
// 后端 Gin 路由控制器解析参数示例
func GetArticleDetail(c *gin.Context) {
    // 比如接收到的是 "32-online-platform-personalized-recommendation"
    idSlug := c.Param("id_slug")
    
    // 按照 "-" 分割,拿最前面的 "32" 即可
    idStr := strings.Split(idSlug, "-")[0]
    id, err := strconv.Atoi(idStr)
    if err != nil {
        c.JSON(400, gin.H{"error": "无效的文章ID"})
        return
    }
    
    // 依然使用极速的主键 ID 查询数据库,保证性能
    article := queryArticleFromDB(id)
    c.JSON(200, gin.H{"code": 0, "data": article})
}

第二步:递上名片——注入 JSON-LD 结构化元数据

大模型爬虫进到你的网页,如果从 HTML 段落里去猜哪段是正文、哪段是作者、什么时候发布的,不仅费劲而且容易猜错。

我们可以主动给它递一张“名片”——JSON-LD。这是把网页的核心信息打包成一段统一规范的 JSON 代码,藏在 HTML 的头部(用户看不见,但爬虫一扫就懂)。

🛠️ 我在项目里的代码实现:

在 Nuxt 4 的文章详情页中,我们利用 useHead 动态拼装符合 Schema.org 标准的 BlogPosting 元数据:

vue 复制代码
<!-- pages/article/[id].vue -->
<script setup lang="ts">
import { computed } from 'vue'

const { data: article } = await useAsyncData(`article-${id}`, () => fetchArticle(id))

// 动态拼装 AI 最喜欢的 JSON-LD 结构化数据
const jsonLd = computed(() => {
    if (!article.value) return null
    return {
        '@context': 'https://schema.org',
        '@type': 'BlogPosting',
        'headline': article.value.title,
        'description': article.value.abstract,
        'image': article.value.banner_path ? [article.value.banner_path] : [],
        'datePublished': article.value.created_at,
        'author': {
            '@type': 'Person',
            'name': '山姆叔叔'
        },
        'publisher': {
            '@type': 'Organization',
            'name': '山姆的技术博客',
            'logo': {
                '@type': 'ImageObject',
                'url': 'https://yoursite.com/logo.png' // 替换成你真实的 logo 地址
            }
        }
    }
})

// 将其写入 HTML 头部
useHead({
    title: () => `${article.value?.title} - 山姆的博客`,
    script: [
        {
            type: 'application/ld+json',
            // 💡 注意:必须用 innerHTML 写入,不能写在 children 里,否则 SSR 服务端渲染时字符会被转义报错
            innerHTML: () => jsonLd.value ? JSON.stringify(jsonLd.value) : ''
        }
    ]
})
</script>

只要这段代码生效,Google 就会给你的文章在搜索结果里加上缩略图、发布时间和作者头像(Rich Snippet 效果),AI 也能以 100% 的准确率提取到这篇文章的所有属性。


第三步:自动更新网站“户口本”——动态多源 Sitemap

光写好网页还不行,得让爬虫知道你有新文章更新了。这就需要 sitemap.xml(站点地图)。以前很多人是手动写一个 XML 文件,但对于经常更新的博客,这太不现实了。

🛠️ 我在项目里的代码实现:

我集成了 @nuxtjs/sitemap 模块,并写了一个服务端的接口,让它每天或者在有新文章时,自动把最新的 Slug 链接吐出来。

首先在 nuxt.config.ts 里声明 Sitemap 数据源:

typescript 复制代码
// nuxt.config.ts
export default defineNuxtConfig({
  modules: ['@nuxtjs/sitemap'],
  
  sitemap: {
    // 动态文章的 URL 数据源指向我们的服务端路由
    sources: ['/api/__sitemap__/urls'],
    // 排除掉不需要被收录的后台页面
    exclude: ['/login', '/admin/**']
  }
})

接着在服务端创建 /server/api/__sitemap__/urls.ts,这个接口只在后台默默运行,通过向 Go 后端发送请求拉取全量文章列表,生成站点地图专用的 URL 数组:

typescript 复制代码
// server/api/__sitemap__/urls.ts
import { getArticleLink } from '~/utils/slug'

interface ApiResponse {
    code: number
    data: {
        list: Array<{ id: number; title: string; title_en: string; created_at: string }>
    }
}

export default defineSitemapEventHandler(async () => {
    const config = useRuntimeConfig()
    const backendBase = config.serverApiUrl || 'http://127.0.0.1:8000'

    try {
        // 请求 Go 接口获取全站所有文章
        const res: ApiResponse = await $fetch(`${backendBase}/api/articles?page=1&limit=1000`)
        if (res.code !== 0) return []

        // 把文章列表转换为 sitemap 需要的格式
        return res.data.list.map(article => ({
            // 这里调用的依然是第一步里写的 getArticleLink 工具类,保证两边 URL 完全统一
            loc: getArticleLink({
                id: article.id,
                title: article.title,
                title_en: article.title_en,
            }),
            lastmod: article.created_at,
            changefreq: 'weekly' as const, // 告知爬虫该页面大概每周更新一次
            priority: 0.9                  // 权重分:0.0 ~ 1.0
        }))
    } catch (e) {
        console.error('Sitemap 接口获取文章列表失败:', e)
        return []
    }
})

做完这步,当我们访问 http://yoursite.com/sitemap.xml 时,就能看到一个清爽的、实时的全站链接地图了,AI 爬虫能顺着这本“户口本”把我们全站扫得一清二楚。


第四步:重新整理 Robots 规则,欢迎 AI 爬虫

有些传统的 Robots.txt 屏蔽了太多路径。我们要在其中明确写出允许哪些大模型爬虫抓取,这也是 GEO 优化里最简单但最重要的一环。

🛠️ 我在项目里的代码实现:

编辑并优化 public/robots.txt,重点向主流 AI 放行:

ini 复制代码
User-agent: *
Allow: /
Disallow: /login
Disallow: /admin

# ---- OpenAI / ChatGPT Search 专属绿卡 ----
User-agent: GPTBot
Allow: /

# ---- Google Gemini / 谷歌 AI 搜索 ----
User-agent: Google-Extended
Allow: /

# ---- Anthropic Claude 专属爬虫 ----
User-agent: ClaudeBot
Allow: /

# ---- Perplexity AI 搜索爬虫 ----
User-agent: PerplexityBot
Allow: /

# 告诉它们站点地图在哪里
Sitemap: https://yoursite.com/sitemap.xml

3. 如何验证我们做的是否有效?

改造上线后,你可以通过以下两个手段来测试效果:

  1. 测试 JSON-LD 格式:使用 Google 官方的 富媒体结果测试工具 输入你的文章链接,只要显示“未发现错误”并且识别出完整的 BlogPosting 架构,说明你的元数据成功了。
  2. 测试 AI 引用:尝试在 Perplexity 里用自然语言搜索你文章所讲解的技术难点。你会发现,AI 在整理回答时,会将你的文章作为小气泡卡片展示在答案附近,链接直接指向我们拼写优美的 Slug URL。

4. 总结与建议

在人工智能重塑互联网的今天,“内容为王”依然是不变的真理,但“如何让 AI 更好地阅读内容”成为了程序员必备的硬核技能。

GEO 的落地,本质上就是减少信息在传输、抓取、理解过程中的损耗。通过 服务端渲染(SSR)确保内容立即可见,通过 JSON-LD 给大模型吃定心丸,通过 语义化 URL 和动态 Sitemap 方便收录

如果你也在搭建或者升级你的个人技术博客、内容型平台,建议把这几项列入你的重构清单中。技术不应该只是自嗨,能在这个 AI 时代抢占流量入口,让大模型帮我们引流,才是真正的“技术变现”第一步。