544 lines
28 KiB
Plaintext
544 lines
28 KiB
Plaintext
================================================================================
|
||
EasyGoAdmin 文档站 SEO / GEO 更新标准
|
||
================================================================================
|
||
版本:1.0(2026-10-25)
|
||
适用:EasyGoAdmin_{Beego,Gin,GoFrame,Iris}_{AntdVue,EleVue,Layui,NaiveVue}
|
||
等 16 个 VitePress 文档子项目(结构高度同构,可直接照搬)
|
||
基线项目:EasyGoAdmin_GoFrame_EleVue
|
||
本文档定位:后续为其他项目做相同更新时的执行标准(审计清单 → 实施步骤 →
|
||
验收标准 → 移植注意事项)。
|
||
|
||
--------------------------------------------------------------------------------
|
||
第一部分:概念与目标
|
||
--------------------------------------------------------------------------------
|
||
|
||
1.1 SEO(Search Engine Optimization,搜索引擎优化)
|
||
面向传统搜索引擎(百度、Google、Bing)的爬虫与排序:
|
||
- 让爬虫能发现、抓取、理解每一个页面(技术 SEO)
|
||
- 让每个页面有独立、准确的标题与摘要(页面元数据)
|
||
- 让页面之间有清晰的权重传递结构(内链、面包屑、sitemap)
|
||
- 用结构化数据告诉搜索引擎"这是什么类型的内容"(JSON-LD)
|
||
|
||
1.2 GEO(Generative Engine Optimization,生成式引擎优化)
|
||
面向 AI 引擎(ChatGPT、Claude、Perplexity、豆包、Kimi、AI Overviews)
|
||
的爬虫与答案引用:
|
||
- 让 AI 爬虫明确被允许抓取(robots.txt 显式放行)
|
||
- 提供"给模型看的站点摘要"(llms.txt,llmstxt.org 标准)
|
||
- 页面内容"答案前置",首段即可被直接引用(:::tip 概述 首句)
|
||
- 结构化 Q/A(FAQPage JSON-LD)便于引擎抽取
|
||
- 时效信号(dateModified / sitemap lastmod)影响引用优先级
|
||
|
||
1.3 本方案的设计原则
|
||
a) 单一真相源:URL、站点描述、提取逻辑只写一处,多处复用。
|
||
b) 零运行时开销:全部在构建期(build)完成,不进客户端 bundle。
|
||
c) 零新依赖:只用 Node 内置模块 + VitePress 内置能力,
|
||
不新增 npm 包(llms.txt 脚本在 node_modules 缺失时也能跑)。
|
||
d) 内容不改版:不改动 143 篇正文的结构,SEO 增强全部在配置层注入。
|
||
|
||
--------------------------------------------------------------------------------
|
||
第二部分:审计清单(先查后改,逐项打勾)
|
||
--------------------------------------------------------------------------------
|
||
|
||
对目标项目执行以下检查(grep/ls 即可,全部只读):
|
||
|
||
[ ] 1. public/robots.txt 是否存在?
|
||
[ ] 2. public/sitemap.xml 或 vitepress sitemap 配置是否存在?
|
||
(查 .vitepress/config 中 grep "sitemap")
|
||
[ ] 3. public/llms.txt 是否存在?
|
||
[ ] 4. canonical 标签是否存在?(grep 'rel="canonical"' .vitepress/)
|
||
[ ] 5. og:image / og:title / JSON-LD 是否存在?
|
||
(grep 'og:image\|application/ld+json' .vitepress/)
|
||
[ ] 6. 页面级 frontmatter description 覆盖率?
|
||
(grep -rl "^description:" zh --include="*.md" | wc -l)
|
||
—— 若为 0,说明所有页面共用同一条 meta description(重复内容信号)
|
||
[ ] 7. <title> 是否带章节编号前缀?(看任意页 H1 形如 "5.10 字典模块")
|
||
[ ] 8. og:url 是否全站固定为首页地址?
|
||
[ ] 9. 是否有 404 页面?(ls 404.md)
|
||
[ ] 10. HTTPS 是否可用?
|
||
curl -sI https://<域名>/ -o /dev/null -w "%{http_code}"
|
||
curl 输出 000 或证书告警 → 说明证书不匹配,暂用 http
|
||
[ ] 11. 是否有站长平台验证 meta?(baidu-site-verification / google-site-verification)
|
||
[ ] 12. :::tip 概述 首句是否适合作为摘要?(抽查 10 页,看是否"答案前置")
|
||
[ ] 13. FAQ 类页面的 Q/A 格式是什么?(决定 extractFaqs 是否要加格式分支)
|
||
[ ] 14. 静态 twitter:image / og:image 指向的文件是否真实存在?
|
||
(Gin 项目 META_IMAGE="/social.png" 但 public/ 下无此文件 →
|
||
twitter:image 404,改为实际存在的 /logo.png)
|
||
[ ] 15. zh.ts 的 META_TITLE / META_DESCRIPTION 是否与项目技术栈一致?
|
||
(Gin 项目误写 "GoFrame v2",属元数据事实错误,一并修正)
|
||
|
||
审计结论示例(GoFrame_EleVue 基线,2026-10-25 实测):
|
||
robots/sitemap/llms/canonical/og:image/JSON-LD 全部缺失;
|
||
0/143 页有 description;143 页 title 全带编号;无 404;
|
||
HTTPS 证书为 CDN 默认证书(*.cdn.myqcloud.com)校验失败;
|
||
内容面强:141/143 页有 :::tip 概述、118 页有表格、139 页有代码块。
|
||
|
||
--------------------------------------------------------------------------------
|
||
第三部分:实施步骤(按序执行)
|
||
--------------------------------------------------------------------------------
|
||
|
||
步骤总览(7 步):
|
||
① 抽取共享层 extract.mjs → ② SEO 钩子 seo.ts → ③ 挂载到 config/index.ts
|
||
→ ④ titleTemplate → ⑤ robots.txt + 404.md → ⑥ llms.txt 脚本
|
||
→ ⑦ package.json 接线 + 生成
|
||
|
||
================================================================================
|
||
步骤 ①:共享提取层 .vitepress/config/extract.mjs(+ extract.d.mts)
|
||
================================================================================
|
||
|
||
新建纯 ESM、零依赖文件,被两类调用方复用:
|
||
- VitePress 配置(.ts,esbuild 打包时内联它)
|
||
- scripts/generate-llms-txt.mjs(Node 直接运行)
|
||
|
||
必须导出的五个符号:
|
||
|
||
1) SITE_URL —— 全站唯一 URL 真相源
|
||
canonical、og:url、twitter:url、JSON-LD、sitemap hostname、
|
||
llms.txt 链接、robots.txt 的 Sitemap 行全部由它派生。
|
||
格式:不带尾斜杠,如 "http://docs.goframe.elevue.easygoadmin.vip"
|
||
⚠ 若项目 HTTPS 可用必须用 https;证书有问题才暂用 http,
|
||
并在注释里写明"证书修复后只改这一处"。
|
||
|
||
2) SITE_DESCRIPTION —— 站点级描述(llms.txt 头部引用)
|
||
与 zh.ts 的 META_DESCRIPTION 保持一致(或让 zh.ts 直接 import 它)。
|
||
|
||
3) stripNumberPrefix(title)
|
||
剥离 <title> 中的章节编号前缀,关键词前置:
|
||
"5.10 字典模块" → "字典模块"
|
||
"K. 贡献指南" → "贡献指南"
|
||
正则(两分支缺一不可):
|
||
/^(?:\d+(?:\.\d+)+\s+|\d+\.\s+|[A-Z]\.\s+)/
|
||
分支说明:
|
||
- \d+(?:\.\d+)+\s+ 匹配 "5.10 " "1.6 "(数字.数字 + 空格)
|
||
- \d+\.\s+ 匹配 "1. "(单层数字 + 点 + 空格)
|
||
- [A-Z]\.\s+ 匹配 "A. " "K. "(附录字母编号)
|
||
⚠ 注意顺序与边界:不能误伤 "404 页面" 这类无编号标题
|
||
(要求编号后必须有点或已是 数字.数字 形态 + 空格)。
|
||
⚠ 只影响 <title> 和 JSON-LD headline 的 SEO 输出;
|
||
页内 H1 和侧边栏编号保持原样不动。
|
||
|
||
4) extractDescription(md) —— meta description / llms.txt 摘要的唯一来源
|
||
提取顺序:
|
||
a. 剥掉 frontmatter(/^---[\s\S]*?\n---\s*/)
|
||
b. 首选 :::tip 容器内正文:/^:::\s*tip[^\n]*\n([\s\S]*?)\n:::/m
|
||
(容器标签后缀不限定"概述",兼容 "说明"、"欢迎贡献" 等变体)
|
||
c. 回退到 H1 后第一个普通段落(跳过标题/容器/代码块/表格行)
|
||
d. 清洗:压缩空白、去行内 markdown(`code` **bold** [链接](url))
|
||
e. 截断:按中文句末标点(。!?!?)取首句,上限 100 字符;
|
||
截断处避免切碎词(去掉末尾不完整词)
|
||
输出单行纯文本;失败返回 ""(调用方应回退到站点级描述)。
|
||
|
||
5) extractFaqs(md) —— FAQPage JSON-LD 的 Q/A 提取
|
||
支持两种站内常见格式(按需增删分支):
|
||
格式 A(标题式):### Q:问题\n答案… (切块后答案截到下一个标题)
|
||
格式 B(加粗式):**Q:问题**\n答案… (答案截到下一个 Q 或标题)
|
||
共同点:
|
||
- Q/A 文本统一走 cleanText:去代码围栏行、行内 code/bold/链接、压空白
|
||
- 只在命中 ≥1 组时返回,调用方据此决定是否注入 FAQPage
|
||
⚠ 若目标项目的 FAQ 是表格形式(| 症状 | 原因 |),不产出 Q/A,
|
||
就不要强行转换——JSON-LD 必须与页面可见内容一致。
|
||
|
||
同目录新建 extract.d.mts(TypeScript 类型声明,供编辑器识别):
|
||
export declare const SITE_URL: string;
|
||
export declare const SITE_DESCRIPTION: string;
|
||
export declare function stripNumberPrefix(title: string): string;
|
||
export declare function extractDescription(md: string): string;
|
||
export declare function extractFaqs(md: string): { q: string; a: string }[];
|
||
|
||
⚠ 关键坑(本项目实测踩过):
|
||
注释里不要出现序列 "*/"。形如 zh/**/*.md 的路径写在块注释中,
|
||
其中的 "**/*" 会提前终止注释导致 SyntaxError。
|
||
改写为 "zh 目录下全部 Markdown" 之类的描述。
|
||
|
||
================================================================================
|
||
步骤 ②:SEO 钩子 .vitepress/config/seo.ts
|
||
================================================================================
|
||
|
||
导出 createSeoHooks(sidebar),返回三个 VitePress 构建钩子。
|
||
入参传 zhConfig.themeConfig?.sidebar —— 面包屑结构从侧边栏自动派生,
|
||
侧边栏改了面包屑自动跟随,无需第二份清单。
|
||
|
||
--------------------------------------------------------------------------------
|
||
2.1 transformPageData(pageData, ctx) —— 页面级 title / description
|
||
--------------------------------------------------------------------------------
|
||
对每页返回增量对象(VitePress 自动 merge):
|
||
- title: stripNumberPrefix(pageData.title)
|
||
(pageData.title 已由 VitePress 从 H1/frontmatter 推导)
|
||
- description: extractDescription(读取 pageData.relativePath 对应源文件)
|
||
仅当页面没有 frontmatter.description 且提取非空时设置;
|
||
提取失败保持 "" → 渲染时自动回退站点级描述。
|
||
读文件:fs.readFileSync(path.resolve(ctx.siteConfig.srcDir, relativePath))
|
||
|
||
效果:
|
||
<title>5.10 字典模块 | EasyGoAdmin</title>
|
||
→ <title>字典模块 | EasyGoAdmin 开发文档</title>
|
||
<meta name="description"> 全站统一文案
|
||
→ 每页独立(取该页 tip 首句)
|
||
|
||
--------------------------------------------------------------------------------
|
||
2.2 transformHead(ctx) —— canonical / OG / twitter / JSON-LD
|
||
--------------------------------------------------------------------------------
|
||
入参 ctx 含:pageData、title(已拼模板)、description、content(渲染后 HTML)、
|
||
siteConfig(root/srcDir)。
|
||
|
||
前置判断:relativePath 为空或 === "404.md" → 返回 [](404 不做 SEO)。
|
||
|
||
注入的标签清单(13 项 + 1 个 JSON-LD script):
|
||
["link", { rel: "canonical", href: url }] ← 每页独立 canonical
|
||
og:type (首页 website,内容页 article)
|
||
og:site_name "EasyGoAdmin"(按项目改)
|
||
og:locale "zh_CN"
|
||
og:title (用去编号后的页标题)
|
||
og:description (用步骤①提取的 description)
|
||
og:url (= canonical)
|
||
og:image (SITE_URL + /logo.png,必须绝对地址)
|
||
twitter:title / twitter:description / twitter:url / twitter:image
|
||
["script", { type: "application/ld+json" }, json] ← 见 2.3
|
||
|
||
URL 推导:
|
||
index.md → SITE_URL + "/"
|
||
404.md → 跳过
|
||
其他 → SITE_URL + "/" + relativePath 换 .html 后缀
|
||
(cleanUrls 关闭时正文页带 .html,与侧边栏链接一致)
|
||
|
||
⚠ 与静态 head 的去重机制:
|
||
VitePress mergeHead 按 meta 标签的"第一个属性值"判重
|
||
(property:"og:url" 视为同一 key)。因此:
|
||
- 静态 head 里的 og:url/og:description/twitter:* 会被本钩子的
|
||
同 key 输出覆盖(构建期),无需手动删除静态项;
|
||
- 静态 twitter:card(本钩子不输出)会保留 —— 正确行为。
|
||
⚠ 本钩子是 build-only("runs before writing HTML to dist"),
|
||
dev server 页面上看不到这些标签是正常的,用 docs:build 验收。
|
||
|
||
--------------------------------------------------------------------------------
|
||
2.3 JSON-LD 结构化数据(单 script,顶层对象或数组)
|
||
--------------------------------------------------------------------------------
|
||
统一转义:JSON.stringify(...).replace(/</g, "\\u003c")
|
||
(防止内容中出现 </script> 破坏注入;< 在 JSON 字符串内合法。)
|
||
|
||
【首页 index.md】
|
||
1. WebSite:name、url、inLanguage、description
|
||
2. Organization:name、url(官网)、logo、sameAs(Gitee 仓库 + 官网)
|
||
|
||
【内容页】
|
||
1. TechArticle:
|
||
headline = 页面 H1 纯文本(去编号!与 <title> 一致,关键词前置;
|
||
提取顺序:源 Markdown H1 → 渲染 HTML <h1> → 兜底 title)
|
||
description、url(= canonical)、inLanguage: "zh-CN"
|
||
mainEntityOfPage: { @type: WebPage, @id: url }
|
||
publisher: Organization 引用
|
||
dateModified: git 最后提交时间(见 2.4,无则省略字段)
|
||
2. BreadcrumbList(侧边栏派生):
|
||
itemListElement = [
|
||
{ position:1, name:"首页", item: SITE_URL + "/" },
|
||
...章节链(每级 name=去 emoji 的侧边栏文案),
|
||
末级 { name: 页面名, item: 当前页 url }
|
||
]
|
||
中间章节级无自己 URL → 只给 name 不给 item(schema.org 合法)。
|
||
侧边栏文案去前导 emoji:/^[^\p{L}\p{N}]+/u 清洗。
|
||
3. FAQPage(仅当 extractFaqs 命中):
|
||
mainEntity = [{ @type:Question, name:Q,
|
||
acceptedAnswer:{ @type:Answer, text:A } }]
|
||
⚠ Google 已收窄 FAQ 富结果资格,但 JSON-LD 对 GEO(AI 抽取)
|
||
仍然有效,且必须与页面可见 Q/A 一致。
|
||
|
||
--------------------------------------------------------------------------------
|
||
2.4 git 时间线(dateModified + sitemap lastmod 共用)
|
||
--------------------------------------------------------------------------------
|
||
单次 git 命令构建全量映射(避免每页一次进程开销):
|
||
git log --format=:::%cI --name-only --relative -n 500 -- .
|
||
cwd = 项目根(ctx.siteConfig.root)
|
||
|
||
解析:以 ":::" 开头的行是提交时间(ISO 8601),
|
||
其后普通行是文件路径(git log 新→旧,首见即最新);
|
||
key = 相对路径(--relative 保证与 pageData.relativePath 同形)。
|
||
|
||
降级策略:git 不可用 / 文件无提交记录 → 静默省略 lastmod/dateModified,
|
||
不阻塞构建。模块级缓存 Map,整个 build 只跑一次。
|
||
|
||
--------------------------------------------------------------------------------
|
||
2.5 sitemap = { hostname, transformItems }
|
||
--------------------------------------------------------------------------------
|
||
VitePress 1.x 内置 sitemap 选项(config 顶层):
|
||
sitemap: {
|
||
hostname: SITE_URL,
|
||
transformItems(items) {
|
||
// ⚠ 关键实测(Gin_EleVue 首次构建暴露):
|
||
// transformItems 收到的 item.url 是【相对路径】——
|
||
// "404.html"、"zh/core/dict.html"、""(首页),
|
||
// 无前导斜杠、无域名;hostname 是之后 SitemapStream 才拼的。
|
||
// 所以 endsWith("/404.html") 永远不命中(冒烟测试若用完整
|
||
// URL 传参会产生假阳性!测试必须模拟真实形态)。
|
||
// 1) 过滤 404:/(^|\/)404\.html$/.test(item.url) → 丢弃
|
||
// 2) 过滤 README:仓库根 README.md 会被 vitepress 当页面收录
|
||
// (README.html 不在侧边栏、属仓库自述),同样 /^...README\.html$/ 过滤
|
||
// 3) url → 相对路径反推:去尾 .html 加 .md;
|
||
// "" 或 "index.html" → "index.md"
|
||
// 4) 查 git 映射注入 lastmod;查不到保持原样
|
||
// (部分新文件/未提交文件无 lastmod 属正常降级,
|
||
// Gin 实测 154 条中 126 条有 lastmod)
|
||
}
|
||
}
|
||
产出:构建后 dist/sitemap.xml(dev 模式不存在,属正常)。
|
||
|
||
================================================================================
|
||
步骤 ③:挂载到 .vitepress/config/index.ts
|
||
================================================================================
|
||
|
||
import { createSeoHooks } from "./seo";
|
||
const seo = createSeoHooks(zhConfig.themeConfig?.sidebar);
|
||
|
||
export default defineConfig({
|
||
...sharedConfig,
|
||
transformPageData: seo.transformPageData,
|
||
transformHead: seo.transformHead,
|
||
sitemap: seo.sitemap,
|
||
...
|
||
});
|
||
|
||
⚠ createSeoHooks 必须在模块顶层调用(sitemap.transformItems 无 ctx,
|
||
依赖 transformPageData 先把 projectRoot 记下来;兜底 process.cwd())。
|
||
|
||
================================================================================
|
||
步骤 ④:titleTemplate(config/shared.ts)
|
||
================================================================================
|
||
|
||
export const sharedConfig = defineConfig({
|
||
title: "EasyGoAdmin",
|
||
titleTemplate: "EasyGoAdmin 开发文档", ← 新增
|
||
...
|
||
});
|
||
|
||
效果:<title>字典模块 | EasyGoAdmin 开发文档</title>
|
||
机制:VitePress createTitle 中 页面 frontmatter.titleTemplate 优先,
|
||
其次站点级 titleTemplate,最后兜底 " | 站点title"。
|
||
首页 index.md 自带 titleTemplate(frontmatter)→ 不受影响。
|
||
⚠ 站点名若与 titleTemplate 相同会被去重(createTitleTemplate 的
|
||
siteTitle === template 分支),保持两者不同。
|
||
|
||
================================================================================
|
||
步骤 ⑤:robots.txt + 404.md
|
||
================================================================================
|
||
|
||
【public/robots.txt】(静态文件,直接进 dist)
|
||
User-agent: *
|
||
Allow: /
|
||
|
||
# 显式放行生成式引擎 / AI 爬虫(GEO)
|
||
User-agent: GPTBot
|
||
Allow: /
|
||
User-agent: ChatGPT-User
|
||
Allow: /
|
||
User-agent: ClaudeBot
|
||
Allow: /
|
||
User-agent: Claude-Web
|
||
Allow: /
|
||
User-agent: anthropic-ai
|
||
Allow: /
|
||
User-agent: PerplexityBot
|
||
Allow: /
|
||
User-agent: Google-Extended
|
||
Allow: /
|
||
User-agent: Bytespider
|
||
Allow: /
|
||
User-agent: bingbot
|
||
Allow: /
|
||
|
||
Sitemap: <SITE_URL>/sitemap.xml
|
||
⚠ Sitemap 行的地址必须与 extract.mjs 的 SITE_URL 一致(手工同步)。
|
||
|
||
【404.md】(项目根目录,VitePress 自动识别为 404 页)
|
||
---
|
||
head:
|
||
- - meta
|
||
- name: robots
|
||
content: noindex
|
||
---
|
||
# 页面不存在
|
||
|
||
抱歉,您访问的页面不存在或已被移动。
|
||
|
||
[返回首页](/) · [浏览文档](/zh/introduction)
|
||
⚠ 首个 frontmatter head 例外:正文页约定"无 frontmatter",
|
||
404 页因需要 noindex 豁免。
|
||
⚠ 链接风格与全站一致(站内相对/根路径),死链检查才能通过。
|
||
|
||
================================================================================
|
||
步骤 ⑥:llms.txt 生成脚本 scripts/generate-llms-txt.mjs
|
||
================================================================================
|
||
|
||
零依赖 Node 脚本,import extract.mjs 复用 SITE_URL/SITE_DESCRIPTION/
|
||
extractDescription(与 meta description 同源,绝不手写两份摘要)。
|
||
|
||
流程:
|
||
1. 递归收集 zh/ 下全部 .md(排除 404,404 不在 zh/ 天然排除)
|
||
2. 每页取:首个 H1(保留编号,便于 AI 理解章节位置)
|
||
extractDescription 首句(作为条目摘要)
|
||
url = SITE_URL + /zh/<相对路径>.html
|
||
3. 按顶层目录映射到侧边栏章节分组(SECTIONS 常量数组,
|
||
与 config/zh.ts 的侧边栏章名保持一致;映射不到的目录兜底"附录")
|
||
4. 组内按 H1 编号排序(数字分支按数值、字母分支按码点、无编号最后)
|
||
5. 输出 llmstxt.org 标准格式:
|
||
|
||
# <站点标题>
|
||
> <SITE_DESCRIPTION>。<能力一句话>。本文档共 N 篇,按章节组织。
|
||
|
||
## <章节名>
|
||
- [H1](url): 首句摘要
|
||
...
|
||
6. 写入 public/llms.txt,stdout 打印页数/章节数
|
||
|
||
⚠ 同一章节名可能由多个目录合并(如 appendix + other 同为"附录"),
|
||
输出时按 label 去重只打印一次。
|
||
⚠ 脚本内不要在块注释写 "zh/**/*.md"(见步骤①的 */ 坑)。
|
||
|
||
================================================================================
|
||
步骤 ⑦:package.json 接线 + 首次生成
|
||
================================================================================
|
||
|
||
"scripts": {
|
||
"docs": "vitepress dev .",
|
||
"docs:llms": "node scripts/generate-llms-txt.mjs",
|
||
"docs:build": "node scripts/generate-llms-txt.mjs && vitepress build .",
|
||
"docs:preview": "vitepress preview ."
|
||
}
|
||
|
||
- docs:build 前置生成 llms.txt(保证部署产物永远最新)
|
||
- 新增/改名页面后若只要刷新索引:yarn docs:llms
|
||
- ⚠ Windows 下 yarn(cmd)执行 "A && B" 可用;不要改成分号。
|
||
|
||
--------------------------------------------------------------------------------
|
||
步骤 ⑧(收尾):更新 CLAUDE.md
|
||
--------------------------------------------------------------------------------
|
||
把以下约定写进项目 CLAUDE.md,防止后续改动破坏 SEO:
|
||
1. :::tip 首句 = meta description + llms.txt 条目,须"答案前置"。
|
||
2. 编号冻结:章节编号同时被侧边栏、正文互引("详见 5.10")、
|
||
JSON-LD 面包屑、llms.txt、外链锚文本引用 —— 发布后禁止重排。
|
||
3. 新增页面的 checklist:写 H1 编号 → 加 tip 概述 →
|
||
zh.ts 侧边栏登记 → (可选)yarn docs:llms。
|
||
4. 记录 SEO 工具链位置与 SITE_URL 单点修改约定。
|
||
|
||
--------------------------------------------------------------------------------
|
||
第四部分:验收标准
|
||
--------------------------------------------------------------------------------
|
||
|
||
A. 无 node_modules 的快速冒烟(Node ≥ 22.18,type stripping)
|
||
node --experimental-strip-types -e "
|
||
import('./.vitepress/config/seo.ts').then(async ({ createSeoHooks }) => {
|
||
const zh = await import('./.vitepress/config/zh.ts');
|
||
const h = createSeoHooks(zh.zhConfig.themeConfig?.sidebar);
|
||
console.log(typeof h.transformPageData, typeof h.transformHead, typeof h.sitemap);
|
||
})"
|
||
|
||
B. 完整验收(需 yarn install 后)
|
||
1) yarn docs:build 无报错(死链检查通过)
|
||
2) dist/sitemap.xml 存在,含 lastmod,无 404.html 与 README.html 条目
|
||
(lastmod 覆盖率允许 <100%:未提交的新文件无 git 记录,属正常降级)
|
||
3) dist/robots.txt、dist/llms.txt 存在
|
||
4) 抽查任意内容页 dist/zh/xxx.html 的 <head>:
|
||
- <title> 无编号前缀、带 titleTemplate 后缀
|
||
- <meta name="description"> 是该页 tip 首句(非全站统一文案)
|
||
- <link rel="canonical"> 指向本页完整 URL
|
||
- og:title/og:description/og:url/og:image 与页面一致且 URL 为绝对地址
|
||
- <script type="application/ld+json"> 可被 JSON.parse,
|
||
含 TechArticle + BreadcrumbList(FAQ 页多 FAQPage)
|
||
- TechArticle.headline 与页面 H1 一致(或至少无编号差异过大)
|
||
5) 首页含 WebSite + Organization
|
||
6) 404.html 含 noindex,且不出现在 sitemap
|
||
7) llms.txt 条目数 == zh 下 md 文件数,链接可访问
|
||
8) 用浏览器扩展(如 JSON-LD Viewer)验证结构化数据无报错
|
||
|
||
C. 提交前人工项(脚本做不了)
|
||
1) 百度/Google 站长平台验证 meta(拿到验证码后加进 shared/zh head)
|
||
2) 百度站长"主动推送"提交首页(百度对 sitemap 支持弱,主动推送是主收录手段)
|
||
3) HTTPS 证书修复后:改 extract.mjs 的 SITE_URL + zh.ts 的 META_URL,
|
||
robots.txt 的 Sitemap 行同步,重新 docs:build
|
||
|
||
--------------------------------------------------------------------------------
|
||
第五部分:移植到其他 15 个项目时的差异点
|
||
--------------------------------------------------------------------------------
|
||
|
||
同构不变量(直接照搬):
|
||
- extract.mjs / extract.d.mts / seo.ts / generate-llms-txt.mjs
|
||
四个文件整体复制(它们不依赖具体侧边栏内容,sidebar 以参数传入)
|
||
- index.ts 挂载三行、shared.ts titleTemplate、package.json 三行脚本
|
||
- robots.txt / 404.md 模板
|
||
|
||
必须按项目修改的点(清单):
|
||
[ ] SITE_URL:每个子域不同(docs.gin.elevue.... / docs.beego.layui....)
|
||
[ ] SITE_DESCRIPTION / titleTemplate / og:site_name:若各项目文案不同
|
||
[ ] extract.mjs 中 Organization 的 url/sameAs:仓库地址不同
|
||
(EasyGoAdmin_Gin_EleVue → 对应 Gitee 仓库路径)
|
||
[ ] scripts/generate-llms-txt.mjs 的 SECTIONS 目录→章节映射:
|
||
核对目标项目的 zh/ 顶层目录与侧边栏章名(各前端/后端组合
|
||
章节编号可能不同,以该项目 zh.ts 侧边栏为准)
|
||
[ ] extractFaqs 的 Q/A 格式分支:先抽查目标项目 FAQ 页格式,
|
||
表格式 FAQ 不要强转
|
||
[ ] robots.txt 的 Sitemap 行域名
|
||
[ ] llms.txt 头部章节数 N 是脚本自动统计,无需手改
|
||
[ ] 404.md 内的示例链接指向目标项目实际存在的首页/入门页
|
||
[ ] zh.ts 若有静态 og/twitter meta,确认与 transformHead 输出
|
||
key 一致会被覆盖(一般无需删;不放心可删掉静态项)
|
||
|
||
版本差异注意:
|
||
- VitePress 需 ≥ 1.0(transformPageData/transformHead/sitemap 选项
|
||
1.x 均有;本方案在 1.3.4 实测)。若他项目版本更老,先核对
|
||
node_modules/vitepress/dist/node/index.d.ts 中是否存在
|
||
sitemap / transformHead / transformPageData 三个字段。
|
||
- 侧边栏结构假设为 { "/zh/": [ {text, items:[{text,link}|{text,items}]} ] }
|
||
的标准 DefaultTheme 形态;若他项目 sidebar 是数组形态或
|
||
多语言 key 不同("/en/"),buildCrumbMap 已兼容数组入参,
|
||
但 key 需与路由前缀一致(当前写死 "/zh/",改多语言时参数化)。
|
||
|
||
--------------------------------------------------------------------------------
|
||
第六部分:实测数据与已知限制(基线项目)
|
||
--------------------------------------------------------------------------------
|
||
|
||
实测(2026-10-25,GoFrame_EleVue):
|
||
- llms.txt:143 页 / 13 章节 / 32KB,生成耗时 < 1s
|
||
- 冒烟测试:13 个 head 标签注入、TechArticle+BreadcrumbList
|
||
(首页 > 5. 核心功能 > 数据与配置 > 5.10 字典模块)、
|
||
FAQPage 24 问(appendix 加粗式)/ 18 问(quickstart 标题式)、
|
||
git dateModified 与 sitemap lastmod 正常、404 跳过、
|
||
sitemap 过滤 404 —— 全部通过
|
||
|
||
移植实测(2026-10-25,EasyGoAdmin_Gin_EleVue,按本标准全流程执行):
|
||
- 审计:与基线同构缺失;额外发现 META_IMAGE 指向不存在的 social.png、
|
||
META_TITLE/META_DESCRIPTION 误写 GoFrame、HTTPS 同样证书不匹配
|
||
- 差异点仅 6 处:SITE_URL、Gitee 仓库地址、SITE_DESCRIPTION/META 文案、
|
||
llms 脚本标题行、robots Sitemap 行、(复核)SECTIONS 章节映射
|
||
——zh/ 目录结构与侧边栏章节与基线完全一致,映射无需改动
|
||
- FAQ 4 个页全为 **Q:** 加粗式(基线为两种混用),既有 extractFaqs 直接命中
|
||
- 完整构建验收(yarn docs:build,21s):
|
||
sitemap 154 条(过滤 404+README 后)、lastmod 126/154、
|
||
内容页 title/canonical/OG/JSON-LD 全通过、
|
||
首页 WebSite+Organization、404 noindex 且不入 sitemap、
|
||
llms.txt 153 条 == zh 下 md 数、FAQPage 11 问
|
||
- 首次构建暴露 transformItems 相对路径坑(见 2.5),修复后复验通过
|
||
|
||
已知限制:
|
||
1. HTTPS:docs 子域 443 返回腾讯 CDN 默认证书(*.cdn.myqcloud.com),
|
||
curl 校验失败,全站暂用 http。修复后按步骤⑦-C3 单点切换。
|
||
2. transformHead/transformHead/sitemap 均为 build-only,
|
||
dev server 下看不到增强标签,验收必须以 docs:build 产物为准。
|
||
3. og:image 用的 logo.png(方形),非 1200×630 社交卡片尺寸 ——
|
||
可后续出专用卡片图后只改 og:image 一处。
|
||
4. 百度/Google 站长验证与主动推送需要平台账号,未包含在本方案。
|
||
5. 站点验证 meta 未加(无验证码);若后续加,放 shared.ts head。
|
||
6. 未启用 VitePress lastUpdated 主题显示(与 SEO 无关,另议)。
|
||
|
||
--------------------------------------------------------------------------------
|
||
附录:一页速查(执行顺序)
|
||
--------------------------------------------------------------------------------
|
||
1. 跑第二部分审计清单,记录结论
|
||
2. 复制/调整 extract.mjs + extract.d.mts(改 SITE_URL 等 9 个差异点)
|
||
3. 复制 seo.ts,核对 FAQ 格式分支与侧边栏 key
|
||
4. index.ts 挂 3 行;shared.ts 加 titleTemplate
|
||
5. 放 robots.txt(改域名)+ 404.md(改链接)
|
||
6. 复制 generate-llms-txt.mjs(核对 SECTIONS 映射)→ yarn docs:llms
|
||
7. package.json 改 3 行脚本
|
||
8. yarn install && yarn docs:build,按第四部分 B 逐项验收
|
||
9. 更新该项目 CLAUDE.md(tip 首句约定 + 编号冻结)
|
||
10. 站长平台验证/推送(人工)
|
||
|
||
(完)
|