[{"data":1,"prerenderedAt":-1},["ShallowReactive",2],{"article-36":3},{"id":4,"title":5,"title_en":6,"abstract":7,"abstract_en":8,"content":9,"content_en":10,"category":11,"banner_id":12,"banner_path":13,"tags":14,"is_recommend":15,"prev_article":16,"next_article":20,"created_at":24},36,"从 SEO 到 GEO：我的博客系统 AI 检索优化与落地实践","From SEO to GEO: My Blog System AI Search Optimization and Implementation Practice","作为一名天天和代码打交道的程序员，最近我发现网站的流量生态发生了一些微妙的变化。\n\n以前我们做个人博","As a programmer who deals with code daily, I have recently noticed some subtle changes in the web traffic ecosystem.\n","作为一名天天和代码打交道的程序员，最近我发现网站的流量生态发生了一些微妙的变化。\n\n以前我们做个人博客或技术网站，每天盯着的是 Google 或百度的收录量，研究怎么通过关键词匹配和外链提高 **PageRank**（网页排名）。\n\n但现在，越来越多的朋友在遇到技术难题时，不再去搜索引擎一页页翻答案，而是直接打开 ChatGPT、Kimi、Perplexity 或者使用 Google 的 AI Overviews（AI 搜索）提问：*“Go 语言里的 GMP 模型是怎么回事？”*\n\n这时候，AI 会在几秒钟内给你整理出一篇通俗易懂的回答，并且在旁边贴心地标上几个彩色的小气泡（引用来源链接），点开一看，正是某个开发者的技术博客。\n\n这种让 AI 抓取你的内容，并在回答中把你的网站作为“参考书目”引用出来的技术，就是最近火起来的 **GEO (Generative Engine Optimization，生成式引擎优化)**。\n\n前段时间，我花精力将自己的博客系统做了一次彻底的现代化重构。这篇文章就以我的实际项目（**Nuxt 4 + Go\u002FGin**）为例，用大白话加代码的方式，手把手带你看看我是如何落地 SEO 和 GEO 优化的。\n\n---\n\n## 1. 为什么大模型能搜到你的网页？通俗聊聊 RAG 机制\n\n在动手改代码之前，我们先花一分钟理解一下 AI 搜索的原理。为什么 AI 能知道你的网站写了什么？\n\n现在的 AI 搜索（如 Perplexity）后面普遍挂载了一个叫 **RAG（检索增强生成）** 的技术。简单来说，它的工作流程是这样的：\n\n1. **用户提问**：用户输入“如何用 Docker 部署 Elasticsearch 8”。\n2. **小秘书搜集资料**：AI 搜索引擎先假装成普通用户，用传统搜索引擎去网上把排在前面的几十个网页全部抓下来（这需要你的网页非常利于抓取，不能是动态加载的空壳子）。\n3. **大模型提炼回答**：AI 读完这几十个网页，把里面的核心干货提炼出来，组合成一段通俗的话发给用户。\n4. **贴上参考链接**：为了防止自己胡说八道（幻觉），AI 会把刚刚做出贡献的网页网址贴在旁边，当成“引用源”。\n\n所以，**GEO 优化的核心目的，就是降低 AI “搜集和阅读资料”的成本，让它觉得读你的文章最轻松、最靠谱**。\n\n---\n\n## 2. 核心技术落地：我的四步走改造方案\n\n为了让 AI 觉得我们的网站“真香”，我给自己的博客系统做了以下四步升级。\n\n---\n\n### 第一步：扔掉 SPA，拥抱 SSR 与语义化 URL (Slug)\n\n如果你的博客还是纯单页应用（SPA，比如用纯 Vue 或 React 打包出来的静态页面），在 AI 时代基本就宣告出局了。因为 SPA 初始只有一个 `\u003Cdiv id=\"app\">\u003C\u002Fdiv>` 的空壳，内容全靠浏览器运行 JS 动态渲染。AI 爬虫可没空等你慢慢加载 JS，它一进来看到是空的，直接就转头走了。\n\n所以我做的第一件事，就是将博客重构为 **Nuxt 4 的服务端渲染（SSR）**。这样当爬虫访问时，服务器直接就把渲染好的、带文字内容的完整 HTML 塞给它。\n\n同时，我们还要改造网址（URL）的拼写。\n\n*   **以前的网址**：`https:\u002F\u002Fyoursite.com\u002Farticle\u002F32` （冷冰冰的数字，爬虫和人类都看不懂）\n*   **现在的网址**：`https:\u002F\u002Fyoursite.com\u002Farticle\u002F32-online-platform-personalized-recommendation` （带上了英文关键字，语义清晰）\n\n#### 🛠️ 我在项目里的代码实现：\n\n在前端，我写了一个工具类来自动转换文章链接。如果文章有英文标题，就优先转换英文；如果没有，就把中文特殊字符过滤掉并用中划线 `-` 连接：\n\n```typescript\n\u002F\u002F utils\u002Fslug.ts\n\n\u002F**\n * 将中英文标题转换为符合 SEO\u002FGEO 标准的扁平化 URL Slug\n * 比如将 \"Docker 部署 ES 8.x\" 变成 \"docker-deployment-es-8x\"\n *\u002F\nexport function getSlug(title: string, titleEn?: string): string {\n    const baseText = titleEn || title;\n    \n    return baseText\n        .toLowerCase()\n        \u002F\u002F 过滤掉除了英文字母、数字、中文、空格和中划线以外的杂质符号\n        .replace(\u002F[^a-z0-9\\u4e00-\\u9fa5\\s-]\u002Fg, '') \n        \u002F\u002F 把连在一起的空格全部换成单个中划线\n        .replace(\u002F\\s+\u002Fg, '-')\n        \u002F\u002F 防止出现连续的多个中划线，比如 --- 变成 -\n        .replace(\u002F-+\u002Fg, '-')\n        .trim();\n}\n\n\u002F**\n * 拼装完整的文章链接\n *\u002F\nexport function getArticleLink(article: { id: number; title: string; title_en?: string }): string {\n    const slug = getSlug(article.title, article.title_en);\n    return `\u002Farticle\u002F${article.id}-${slug}`;\n}\n```\n\n在后端（我用的是 Go 的 Gin 框架），路由直接设计成 `\u002Farticle\u002F:id_slug`。在解析参数时，其实非常通俗简单——我们只拿最前面的主键 ID 去查数据库，后面那一长串英文直接忽略掉：\n\n```go\n\u002F\u002F 后端 Gin 路由控制器解析参数示例\nfunc GetArticleDetail(c *gin.Context) {\n    \u002F\u002F 比如接收到的是 \"32-online-platform-personalized-recommendation\"\n    idSlug := c.Param(\"id_slug\")\n    \n    \u002F\u002F 按照 \"-\" 分割，拿最前面的 \"32\" 即可\n    idStr := strings.Split(idSlug, \"-\")[0]\n    id, err := strconv.Atoi(idStr)\n    if err != nil {\n        c.JSON(400, gin.H{\"error\": \"无效的文章ID\"})\n        return\n    }\n    \n    \u002F\u002F 依然使用极速的主键 ID 查询数据库，保证性能\n    article := queryArticleFromDB(id)\n    c.JSON(200, gin.H{\"code\": 0, \"data\": article})\n}\n```\n\n---\n\n### 第二步：递上名片——注入 JSON-LD 结构化元数据\n\n大模型爬虫进到你的网页，如果从 HTML 段落里去猜哪段是正文、哪段是作者、什么时候发布的，不仅费劲而且容易猜错。\n\n我们可以主动给它递一张“名片”——**JSON-LD**。这是把网页的核心信息打包成一段统一规范的 JSON 代码，藏在 HTML 的头部（用户看不见，但爬虫一扫就懂）。\n\n#### 🛠️ 我在项目里的代码实现：\n\n在 Nuxt 4 的文章详情页中，我们利用 `useHead` 动态拼装符合 `Schema.org` 标准的 `BlogPosting` 元数据：\n\n```vue\n\u003C!-- pages\u002Farticle\u002F[id].vue -->\n\u003Cscript setup lang=\"ts\">\nimport { computed } from 'vue'\n\nconst { data: article } = await useAsyncData(`article-${id}`, () => fetchArticle(id))\n\n\u002F\u002F 动态拼装 AI 最喜欢的 JSON-LD 结构化数据\nconst jsonLd = computed(() => {\n    if (!article.value) return null\n    return {\n        '@context': 'https:\u002F\u002Fschema.org',\n        '@type': 'BlogPosting',\n        'headline': article.value.title,\n        'description': article.value.abstract,\n        'image': article.value.banner_path ? [article.value.banner_path] : [],\n        'datePublished': article.value.created_at,\n        'author': {\n            '@type': 'Person',\n            'name': '山姆叔叔'\n        },\n        'publisher': {\n            '@type': 'Organization',\n            'name': '山姆的技术博客',\n            'logo': {\n                '@type': 'ImageObject',\n                'url': 'https:\u002F\u002Fyoursite.com\u002Flogo.png' \u002F\u002F 替换成你真实的 logo 地址\n            }\n        }\n    }\n})\n\n\u002F\u002F 将其写入 HTML 头部\nuseHead({\n    title: () => `${article.value?.title} - 山姆的博客`,\n    script: [\n        {\n            type: 'application\u002Fld+json',\n            \u002F\u002F 💡 注意：必须用 innerHTML 写入，不能写在 children 里，否则 SSR 服务端渲染时字符会被转义报错\n            innerHTML: () => jsonLd.value ? JSON.stringify(jsonLd.value) : ''\n        }\n    ]\n})\n\u003C\u002Fscript>\n```\n\n只要这段代码生效，Google 就会给你的文章在搜索结果里加上缩略图、发布时间和作者头像（Rich Snippet 效果），AI 也能以 100% 的准确率提取到这篇文章的所有属性。\n\n---\n\n### 第三步：自动更新网站“户口本”——动态多源 Sitemap\n\n光写好网页还不行，得让爬虫知道你有新文章更新了。这就需要 `sitemap.xml`（站点地图）。以前很多人是手动写一个 XML 文件，但对于经常更新的博客，这太不现实了。\n\n#### 🛠️ 我在项目里的代码实现：\n\n我集成了 `@nuxtjs\u002Fsitemap` 模块，并写了一个服务端的接口，让它每天或者在有新文章时，自动把最新的 Slug 链接吐出来。\n\n首先在 `nuxt.config.ts` 里声明 Sitemap 数据源：\n\n```typescript\n\u002F\u002F nuxt.config.ts\nexport default defineNuxtConfig({\n  modules: ['@nuxtjs\u002Fsitemap'],\n  \n  sitemap: {\n    \u002F\u002F 动态文章的 URL 数据源指向我们的服务端路由\n    sources: ['\u002Fapi\u002F__sitemap__\u002Furls'],\n    \u002F\u002F 排除掉不需要被收录的后台页面\n    exclude: ['\u002Flogin', '\u002Fadmin\u002F**']\n  }\n})\n```\n\n接着在服务端创建 `\u002Fserver\u002Fapi\u002F__sitemap__\u002Furls.ts`，这个接口只在后台默默运行，通过向 Go 后端发送请求拉取全量文章列表，生成站点地图专用的 URL 数组：\n\n```typescript\n\u002F\u002F server\u002Fapi\u002F__sitemap__\u002Furls.ts\nimport { getArticleLink } from '~\u002Futils\u002Fslug'\n\ninterface ApiResponse {\n    code: number\n    data: {\n        list: Array\u003C{ id: number; title: string; title_en: string; created_at: string }>\n    }\n}\n\nexport default defineSitemapEventHandler(async () => {\n    const config = useRuntimeConfig()\n    const backendBase = config.serverApiUrl || 'http:\u002F\u002F127.0.0.1:8000'\n\n    try {\n        \u002F\u002F 请求 Go 接口获取全站所有文章\n        const res: ApiResponse = await $fetch(`${backendBase}\u002Fapi\u002Farticles?page=1&limit=1000`)\n        if (res.code !== 0) return []\n\n        \u002F\u002F 把文章列表转换为 sitemap 需要的格式\n        return res.data.list.map(article => ({\n            \u002F\u002F 这里调用的依然是第一步里写的 getArticleLink 工具类，保证两边 URL 完全统一\n            loc: getArticleLink({\n                id: article.id,\n                title: article.title,\n                title_en: article.title_en,\n            }),\n            lastmod: article.created_at,\n            changefreq: 'weekly' as const, \u002F\u002F 告知爬虫该页面大概每周更新一次\n            priority: 0.9                  \u002F\u002F 权重分：0.0 ~ 1.0\n        }))\n    } catch (e) {\n        console.error('Sitemap 接口获取文章列表失败:', e)\n        return []\n    }\n})\n```\n\n做完这步，当我们访问 `http:\u002F\u002Fyoursite.com\u002Fsitemap.xml` 时，就能看到一个清爽的、实时的全站链接地图了，AI 爬虫能顺着这本“户口本”把我们全站扫得一清二楚。\n\n---\n\n### 第四步：重新整理 Robots 规则，欢迎 AI 爬虫\n\n有些传统的 Robots.txt 屏蔽了太多路径。我们要在其中明确写出允许哪些大模型爬虫抓取，这也是 GEO 优化里最简单但最重要的一环。\n\n#### 🛠️ 我在项目里的代码实现：\n\n编辑并优化 `public\u002Frobots.txt`，重点向主流 AI 放行：\n\n```ini\nUser-agent: *\nAllow: \u002F\nDisallow: \u002Flogin\nDisallow: \u002Fadmin\n\n# ---- OpenAI \u002F ChatGPT Search 专属绿卡 ----\nUser-agent: GPTBot\nAllow: \u002F\n\n# ---- Google Gemini \u002F 谷歌 AI 搜索 ----\nUser-agent: Google-Extended\nAllow: \u002F\n\n# ---- Anthropic Claude 专属爬虫 ----\nUser-agent: ClaudeBot\nAllow: \u002F\n\n# ---- Perplexity AI 搜索爬虫 ----\nUser-agent: PerplexityBot\nAllow: \u002F\n\n# 告诉它们站点地图在哪里\nSitemap: https:\u002F\u002Fyoursite.com\u002Fsitemap.xml\n```\n\n---\n\n## 3. 如何验证我们做的是否有效？\n\n改造上线后，你可以通过以下两个手段来测试效果：\n\n1. **测试 JSON-LD 格式**：使用 Google 官方的 [富媒体结果测试工具](https:\u002F\u002Fsearch.google.com\u002Ftest\u002Frich-results) 输入你的文章链接，只要显示“未发现错误”并且识别出完整的 `BlogPosting` 架构，说明你的元数据成功了。\n2. **测试 AI 引用**：尝试在 Perplexity 里用自然语言搜索你文章所讲解的技术难点。你会发现，AI 在整理回答时，会将你的文章作为小气泡卡片展示在答案附近，链接直接指向我们拼写优美的 Slug URL。\n\n---\n\n## 4. 总结与建议\n\n在人工智能重塑互联网的今天，**“内容为王”依然是不变的真理，但“如何让 AI 更好地阅读内容”成为了程序员必备的硬核技能。**\n\nGEO 的落地，本质上就是减少信息在传输、抓取、理解过程中的损耗。通过 **服务端渲染（SSR）确保内容立即可见**，通过 **JSON-LD 给大模型吃定心丸**，通过 **语义化 URL 和动态 Sitemap 方便收录**。\n\n如果你也在搭建或者升级你的个人技术博客、内容型平台，建议把这几项列入你的重构清单中。技术不应该只是自嗨，能在这个 AI 时代抢占流量入口，让大模型帮我们引流，才是真正的“技术变现”第一步。\n","As a programmer who deals with code daily, I have recently noticed some subtle changes in the web traffic ecosystem.\n\nIn the past, when we built personal blogs or technical websites, we stared at Google or Baidu index counts every day, researching how to improve **PageRank** through keyword matching and external backlinks.\n\nBut now, more and more friends do not flip through search engines page by page when encountering technical difficulties. Instead, they directly open ChatGPT, Kimi, Perplexity, or use Google's AI Overviews (AI Search) to ask: *\"What is Go's GMP model all about?\"*\n\nAt this moment, AI will compile a plain, easy-to-understand answer for you in a few seconds, and thoughtfully tag it with several colorful little bubbles (citation source links) nearby. When clicked, it turns out to be a developer's technical blog.\n\nThis technique of having AI crawl your content and cite your website as a \"bibliography\" in its response is the recently popularized **GEO (Generative Engine Optimization)**.\n\nA while ago, I spent some effort to carry out a thorough modern refactoring of my blog system. Taking my actual project (**Nuxt 4 + Go\u002FGin**) as an example, this article will walk you through, in plain language and code, how I implemented SEO and GEO optimization.\n\n---\n\n## 1. Why Can Large Language Models Find Your Web Pages? A Plain Talk on the RAG Mechanism\n\nBefore writing code, let's spend a minute understanding the principles of AI search. How does AI know what you wrote on your website?\n\nCurrent AI search systems (such as Perplexity) generally rely on a technology called **RAG (Retrieval-Augmented Generation)**. Simply put, its workflow looks like this:\n\n1. **User Question**: The user inputs \"How to deploy Elasticsearch 8 with Docker\".\n2. **Information Gathering**: The AI search engine pretends to be an ordinary user and uses traditional search engines to download the top dozens of web pages (this requires your web page to be crawl-friendly, rather than a dynamically loaded empty shell).\n3. **LLM Synthesis**: The AI reads these dozens of pages, extracts the core value, and synthesizes it into a plain explanation for the user.\n4. **Citing Reference Links**: To prevent making things up (hallucination), the AI attaches the URLs of the contributing web pages next to the response as \"citation sources\".\n\nTherefore, **the core purpose of GEO optimization is to reduce the cost of AI \"gathering and reading information\", making it feel that reading your article is the easiest and most reliable option**.\n\n---\n\n## 2. Core Implementation: My Four-Step Redesign Plan\n\nTo make AI find our website highly attractive, I performed the following four upgrades to my blog system.\n\n---\n\n### Step 1: Dump SPA, Embrace SSR and Semantic URLs (Slug)\n\nIf your blog is still a pure Single Page Application (SPA, such as a static page built with pure Vue or React), it is basically out of the game in the AI era. This is because an SPA initially contains only an empty shell like `\u003Cdiv id=\"app\">\u003C\u002Fdiv>`, and all content relies on the browser executing JS for dynamic rendering. AI crawlers do not have the patience to wait for your JS to load; when they see an empty page, they simply turn away.\n\nTherefore, the first thing I did was refactor the blog to use **Nuxt 4's Server-Side Rendering (SSR)**. This way, when a crawler visits, the server serves the fully rendered HTML with full text content directly.\n\nAdditionally, we need to restructure our URLs.\n\n*   **Old URL**: `https:\u002F\u002Fyoursite.com\u002Farticle\u002F32` (Cold numbers, illegible to both crawlers and humans)\n*   **New URL**: `https:\u002F\u002Fyoursite.com\u002Farticle\u002F32-online-platform-personalized-recommendation` (Contains English keywords with clear semantics)\n\n#### 🛠️ Code Implementation in My Project:\n\nOn the frontend, I wrote a utility class to automatically convert article links. If an article has an English title, it is converted with priority; otherwise, Chinese special characters are filtered out and connected with hyphens `-`:\n\n```typescript\n\u002F\u002F utils\u002Fslug.ts\n\n\u002F**\n * Convert Chinese\u002FEnglish titles to flat URL Slugs complying with SEO\u002FGEO standards\n * For example, converting \"Docker deploy ES 8.x\" into \"docker-deployment-es-8x\"\n *\u002F\nexport function getSlug(title: string, titleEn?: string): string {\n    const baseText = titleEn || title;\n    \n    return baseText\n        .toLowerCase()\n        \u002F\u002F Filter out miscellaneous symbols except English letters, numbers, Chinese characters, spaces, and hyphens\n        .replace(\u002F[^a-z0-9\\u4e00-\\u9fa5\\s-]\u002Fg, '') \n        \u002F\u002F Replace consecutive spaces with a single hyphen\n        .replace(\u002F\\s+\u002Fg, '-')\n        \u002F\u002F Prevent consecutive multiple hyphens, e.g., turning --- into -\n        .replace(\u002F-+\u002Fg, '-')\n        .trim();\n}\n\n\u002F**\n * Assemble a complete article link\n *\u002F\nexport function getArticleLink(article: { id: number; title: string; title_en?: string }): string {\n    const slug = getSlug(article.title, article.title_en);\n    return `\u002Farticle\u002F${article.id}-${slug}`;\n}\n```\n\nOn the backend (I am using Go's Gin framework), the route is designed as `\u002Farticle\u002F:id_slug`. When parsing parameters, it is actually very simple and straightforward —— we only take the primary key ID at the beginning to query the database, ignoring the long trailing string of English keywords:\n\n```go\n\u002F\u002F Example of parameter parsing in backend Gin route controller\nfunc GetArticleDetail(c *gin.Context) {\n    \u002F\u002F E.g., receiving \"32-online-platform-personalized-recommendation\"\n    idSlug := c.Param(\"id_slug\")\n    \n    \u002F\u002F Split by \"-\", and take the first part \"32\"\n    idStr := strings.Split(idSlug, \"-\")[0]\n    id, err := strconv.Atoi(idStr)\n    if err != nil {\n        c.JSON(400, gin.H{\"error\": \"Invalid article ID\"})\n        return\n    }\n    \n    \u002F\u002F Still use the fast primary key ID to query the database, ensuring performance\n    article := queryArticleFromDB(id)\n    c.JSON(200, gin.H{\"code\": 0, \"data\": article})\n}\n```\n\n---\n\n### Step 2: Present Your Business Card —— Inject JSON-LD Structured Metadata\n\nWhen large language model crawlers enter your web page, if they have to guess from the HTML paragraphs which part is the main text, who the author is, and when it was published, it is not only laborious but also prone to errors.\n\nWe can proactively hand them a \"business card\" —— **JSON-LD**. This packages the core information of the web page into a standardized piece of JSON code, hidden inside the HTML header (invisible to users, but instantly understood by crawlers).\n\n#### 🛠️ Code Implementation in My Project:\n\nIn Nuxt 4's article details page, we utilize `useHead` to dynamically construct `BlogPosting` metadata conforming to the `Schema.org` standard:\n\n```vue\n\u003C!-- pages\u002Farticle\u002F[id].vue -->\n\u003Cscript setup lang=\"ts\">\nimport { computed } from 'vue'\n\nconst { data: article } = await useAsyncData(`article-${id}`, () => fetchArticle(id))\n\n\u002F\u002F Dynamically construct AI's favorite JSON-LD structured data\nconst jsonLd = computed(() => {\n    if (!article.value) return null\n    return {\n        '@context': 'https:\u002F\u002Fschema.org',\n        '@type': 'BlogPosting',\n        'headline': article.value.title,\n        'description': article.value.abstract,\n        'image': article.value.banner_path ? [article.value.banner_path] : [],\n        'datePublished': article.value.created_at,\n        'author': {\n            '@type': 'Person',\n            'name': 'Uncle Sam'\n        },\n        'publisher': {\n            '@type': 'Organization',\n            'name': \"Sam's Tech Blog\",\n            'logo': {\n                '@type': 'ImageObject',\n                'url': 'https:\u002F\u002Fyoursite.com\u002Flogo.png' \u002F\u002F Replace with your actual logo URL\n            }\n        }\n    }\n})\n\n\u002F\u002F Write it into the HTML head\nuseHead({\n    title: () => `${article.value?.title} - Sam's Blog`,\n    script: [\n        {\n            type: 'application\u002Fld+json',\n            \u002F\u002F 💡 Note: Must write using innerHTML, not inside children, otherwise characters will be escaped and cause errors during SSR (Server-Side Rendering)\n            innerHTML: () => jsonLd.value ? JSON.stringify(jsonLd.value) : ''\n        }\n    ]\n})\n\u003C\u002Fscript>\n```\n\nOnce this code takes effect, Google will add thumbnails, publication time, and author avatars (Rich Snippets) to your articles in search results, and AI can extract all properties of this article with 100% accuracy.\n\n---\n\n### Step 3: Automatically Update the Website \"Registry\" —— Dynamic Multi-Source Sitemap\n\nSimply writing web pages is not enough; we must let crawlers know when we have new articles updated. This requires `sitemap.xml` (Sitemap). In the past, many people wrote an XML file manually, but for blogs that update frequently, this is too unrealistic.\n\n#### 🛠️ Code Implementation in My Project:\n\nI integrated the `@nuxtjs\u002Fsitemap` module and wrote a server-side API to automatically serve the latest Slug links daily or whenever a new article is published.\n\nFirst, declare the Sitemap data source in `nuxt.config.ts`:\n\n```typescript\n\u002F\u002F nuxt.config.ts\nexport default defineNuxtConfig({\n  modules: ['@nuxtjs\u002Fsitemap'],\n  \n  sitemap: {\n    \u002F\u002F Dynamic article URL data source points to our server-side route\n    sources: ['\u002Fapi\u002F__sitemap__\u002Furls'],\n    \u002F\u002F Exclude admin backend pages that do not need indexing\n    exclude: ['\u002Flogin', '\u002Fadmin\u002F**']\n  }\n})\n```\n\nNext, create `\u002Fserver\u002Fapi\u002F__sitemap__\u002Furls.ts` on the server-side. This API runs quietly in the background, fetching the full article list by sending requests to the Go backend, and generating the URL array dedicated for the sitemap:\n\n```typescript\n\u002F\u002F server\u002Fapi\u002F__sitemap__\u002Furls.ts\nimport { getArticleLink } from '~\u002Futils\u002Fslug'\n\ninterface ApiResponse {\n    code: number\n    data: {\n        list: Array\u003C{ id: number; title: string; title_en: string; created_at: string }>\n    }\n}\n\nexport default defineSitemapEventHandler(async () => {\n    const config = useRuntimeConfig()\n    const backendBase = config.serverApiUrl || 'http:\u002F\u002F127.0.0.1:8000'\n\n    try {\n        \u002F\u002F Request Go API to get all articles on the site\n        const res: ApiResponse = await $fetch(`${backendBase}\u002Fapi\u002Farticles?page=1&limit=1000`)\n        if (res.code !== 0) return []\n\n        \u002F\u002F Convert the article list into the format required by sitemap\n        return res.data.list.map(article => ({\n            \u002F\u002F Call the getArticleLink utility written in Step 1 to ensure URL consistency\n            loc: getArticleLink({\n                id: article.id,\n                title: article.title,\n                title_en: article.title_en,\n            }),\n            lastmod: article.created_at,\n            changefreq: 'weekly' as const, \u002F\u002F Tell the crawler that this page is updated roughly weekly\n            priority: 0.9                  \u002F\u002F Priority weight: 0.0 ~ 1.0\n        }))\n    } catch (e) {\n        console.error('Failed to get article list for Sitemap:', e)\n        return []\n    }\n})\n```\n\nOnce this step is done, when we access `http:\u002F\u002Fyoursite.com\u002Fsitemap.xml`, we can see a clean, real-time map of all links across the site, enabling AI crawlers to crawl our entire site clearly following this \"registry\".\n\n---\n\n### Step 4: Reorganize Robots Rules to Welcome AI Crawlers\n\nSome traditional `robots.txt` files block too many paths. We need to explicitly state which LLM crawlers are allowed to crawl, which is also the simplest but most important part of GEO optimization.\n\n#### 🛠️ Code Implementation in My Project:\n\nEdit and optimize `public\u002Frobots.txt`, giving priority green light to mainstream AI crawlers:\n\n```ini\nUser-agent: *\nAllow: \u002F\nDisallow: \u002Flogin\nDisallow: \u002Fadmin\n\n# ---- OpenAI \u002F ChatGPT Search Dedicated Green Card ----\nUser-agent: GPTBot\nAllow: \u002F\n\n# ---- Google Gemini \u002F Google AI Search ----\nUser-agent: Google-Extended\nAllow: \u002F\n\n# ---- Anthropic Claude Dedicated Crawler ----\nUser-agent: ClaudeBot\nAllow: \u002F\n\n# ---- Perplexity AI Search Crawler ----\nUser-agent: PerplexityBot\nAllow: \u002F\n\n# Tell them where the sitemap is\nSitemap: https:\u002F\u002Fyoursite.com\u002Fsitemap.xml\n```\n\n---\n\n## 3. How to Verify If What We Did Is Effective?\n\nAfter the redesign goes live, you can test the results using the following two methods:\n\n1. **Test JSON-LD Format**: Use Google's official [Rich Results Test Tool](https:\u002F\u002Fsearch.google.com\u002Ftest\u002Frich-results) and input your article link. If it shows \"No errors found\" and recognizes the complete `BlogPosting` schema, your metadata is successful.\n2. **Test AI Citations**: Try searching for technical difficulties explained in your article using natural language on Perplexity. You will find that when AI organizes the answers, it will display your article as a small bubble card near the answer, linking directly to our beautifully structured Slug URL.\n\n---\n\n## 4. Summary and Recommendations\n\nToday, as artificial intelligence reshapes the Internet, **\"content is king\" remains an immutable truth, but \"how to make AI read content better\" has become a core skill for programmers.**\n\nThe implementation of GEO is essentially about reducing the loss of information during transmission, crawling, and understanding. By using **Server-Side Rendering (SSR) to ensure content is immediately visible**, using **JSON-LD to reassure the LLMs**, and using **semantic URLs and dynamic Sitemaps for easy indexing**.\n\nIf you are also building or upgrading your personal tech blog or content-based platform, it is recommended to include these items in your refactoring checklist. Technology should not just be a self-entertainment; capturing the traffic entry point in this AI era and letting LLMs drive traffic for us is the true first step towards \"monetizing technology\".\n","GEO",30,"https:\u002F\u002Fblog4-1316398321.cos.ap-nanjing.myqcloud.com\u002Fblog5\u002F20260621005241__创意-抽象-极简.png",[11],true,{"id":17,"title":18,"title_en":19},35,"2026 终极指南：手把手带你玩转 Google DeepMind 的 Antigravity 智能编程助手","The Ultimate Guide to 2026: Help you play Google DeepMind's Antigravity Intelligent Programming Assistant",{"id":21,"title":22,"title_en":23},37,"GopherGraph v1.1.0 升级与安全修复实战","GopherGraph v1.1.0 Upgrade and Security Fix Practice","2026-06-19T23:47:52.564487+08:00"]