外观
从日均 200 到 1000:VitePress SEO 收录提升 5 倍实战
为什么 VitePress 默认配置还不够
VitePress 天生对 SEO 友好:构建产物是静态 HTML、首屏直出、语义化结构清晰,默认还会根据 frontmatter 生成 title 和 description。但「能被抓取」和「能被高效收录」是两件事。
我们维护的一个组件库文档站,上线两个月后日均自然搜索点击只有 200 左右。在 Google Search Console 中排查发现三个典型问题:
- 大量页面处于「已发现 - 尚未编入索引」状态,爬虫没有及时发现新 URL;
- 同一篇文档存在带尾斜杠、不带尾斜杠、预览域名三种可访问形式,权重被分散;
- 搜索结果只是普通蓝色链接,没有面包屑、站点链接等富媒体增强,点击率偏低。
对应的解法并不神秘:用 sitemap 主动告知 URL 全集,用 canonical 收敛重复路径,再用 JSON-LD 结构化数据帮助搜索引擎理解页面类型。完成这套配置并配合主动推送后,八周内日均收录页面从约 200 增长到 1000,自然点击提升约 5 倍。
方案对比:插件、构建脚本与主题扩展
在动手前先对比三种常见实现路径:
| 方案 | 适用场景 | 优点 | 局限 |
|---|---|---|---|
| VitePress 内置 sitemap | 标准文档站 | 零依赖,构建自动生成 | 只能输出 URL,无法注入结构化数据 |
| 独立构建脚本 | 高度定制站点 | 可读取产物清单,灵活生成 JSON-LD | 需要自己维护遍历逻辑 |
| transformHead + 主题扩展 | 需要页面级元信息 | 与路由、frontmatter 深度结合 | 配置分散,需理解 VitePress 运行时 |
实际项目推荐组合使用:内置能力负责 sitemap,transformHead 负责 canonical 和 description,构建钩子负责全站 JSON-LD。这样既不重复造轮子,又保留了定制空间。
整体 SEO 数据流
步骤一:开启 VitePress sitemap
VitePress 从较新版本开始内置了 sitemap 支持,只需要在配置文件中提供站点正式域名。注意 hostname 不要带路径,也不要以斜杠结尾。
javascript
// .vitepress/config.mjs
import { defineConfig } from 'vitepress'\n
export default defineConfig({
title: 'DevUI 组件库',
description: '面向企业中后台的 Vue 3 组件库文档',
// 关键:必须是线上最终访问域名
sitemap: {
hostname: 'https://devui.example.com'
},
themeConfig: {
// 正常的导航、侧边栏配置
}
})构建后会在产物根目录生成 sitemap.xml,包含所有静态路由和 lastmod。接着需要让爬虫能找到它,在 public/robots.txt 中声明地址:
text
User-agent: *
Allow: /
Sitemap: https://devui.example.com/sitemap.xml如果文档存在草稿、内部组件或低质量页面,不建议简单地全部 Allow。可以通过 frontmatter 标记 draft: true,再在构建脚本中过滤,避免把未完成内容暴露给搜索引擎。
步骤二:用 transformHead 注入 canonical
sitemap 解决「发现」问题,canonical 解决「重复」问题。VitePress 提供的 transformHead 可以在每个页面渲染时拿到相对路径和页面数据,非常适合生成规范链接。
typescript
// .vitepress/config.ts
import { defineConfig } from 'vitepress'
import type { HeadConfig } from 'vitepress'
const SITE_URL = 'https://devui.example.com'
export default defineConfig({
sitemap: {
hostname: SITE_URL
},
transformHead({ pageData }): HeadConfig[] {
// pageData.relativePath 形如 components/button.md
const path = pageData.relativePath
.replace(/\.md$/, '')
.replace(/\index$/, '')
// 首页路径为空,其余页面统一保留尾斜杠,避免重复 URL
const canonical = path
? `${SITE_URL}/${path}/`
: `${SITE_URL}/`
const head: HeadConfig[] = [
['link', { rel: 'canonical', href: canonical }]
]
// 优先使用页面 frontmatter 中的 description
if (pageData.frontmatter.description) {
head.push([
'meta',
{
name: 'description',
content: pageData.frontmatter.description
}
])
}
return head
}
})这里最容易忽略的是尾斜杠策略。VitePress 部署在多数静态主机上时,/guide/start 和 /guide/start/ 都可能返回 200。必须选定一种形式并让 canonical、sitemap、内链保持一致,否则爬虫会把它们当作两个地址。
步骤三:注入 JSON-LD 结构化数据
结构化数据并不直接提高排名,但能提高结果的可展示性。文档站最实用的两类是 WebSite(站点级,可配合站内搜索候选)和 BreadcrumbList(面包屑)。技术文章还可以增加 TechArticle。
下面通过自定义主题的 Layout 插槽实现,避免在每个 Markdown 文件中手写脚本。
vue
<!-- .vitepress/theme/SEOLayout.vue -->
<script setup lang="ts">
import { computed } from 'vue'
import { useData, useRoute } from 'vitepress'
const { site, page, theme } = useData()
const route = useRoute()
const SITE_URL = 'https://devui.example.com'
const canonical = computed(() => {
const path = route.path.endsWith('/')
? route.path
: `${route.path}/`
return `${SITE_URL}${path === '//' ? '/' : path}`
})
// 根据侧边栏标题构造面包屑,实际项目可从自己的导航配置中读取
const breadcrumb = computed(() => {
const segments = route.path.split('/').filter(Boolean)
const items = [
{
'@type': 'ListItem',
position: 1,
name: '首页',
item: `${SITE_URL}/`
},
...segments.map((name, index) => ({
'@type': 'ListItem',
position: index + 2,
name: decodeURIComponent(name),
item: `${SITE_URL}/${segments.slice(0, index + 1).join('/')}/`
}))
]
return {
'@context': 'https://schema.org',
'@type': 'BreadcrumbList',
itemListElement: items
}
})
const website = {
'@context': 'https://schema.org',
'@type': 'WebSite',
name: 'DevUI 组件库',
url: `${SITE_URL}/`,
potentialAction: {
'@type': 'SearchAction',
'target': `${SITE_URL}/search?q={search_term_string}`,
'query-input': 'required name=search_term_string'
}
}
const article = computed(() => {
const fm = page.value.frontmatter
if (!fm.date) return null
return {
'@context': 'https://schema.org',
'@type': 'TechArticle',
headline: page.value.title,
description: fm.description || site.value.description,
datePublished: fm.date,
dateModified: fm.updatedAt || fm.date,
author: {
'@type': 'Organization',
name: 'DevUI Team'
},
mainEntityOfPage: {
'@type': 'WebPage',
'@id': canonical.value
}
}
})
const jsonLdBlocks = computed(() =>
[website, breadcrumb.value, article.value].filter(Boolean)
)
</script>
<template>
<template v-for="(block, index) in jsonLdBlocks" :key="index">
<script type="application/ld+json">
{{ JSON.stringify(block) }}
</script>
</template>
</template>然后在自定义主题入口中挂到 layout-top 插槽:
typescript
// .vitepress/theme/index.ts
import DefaultTheme from 'vitepress/theme'
import SEOLayout from './SEOLayout.vue'
export default {
extends: DefaultTheme,
Layout() {
return {
// VitePress 提供的具名插槽
'layout-top': () => h(SEOLayout)
}
}
} satisfies DefaultTheme.Theme如果不习惯在 Layout 中返回渲染函数,也可以直接封装一个 <SEOProvider> 组件,在 .vitepress/theme/index.ts 的 enhanceApp 中注册。核心原则只有一条:结构化数据必须出现在最终 HTML 中,而不是仅在客户端 hydration 后追加,否则爬虫看到的首屏内容可能缺失。
步骤四:提交 sitemap 并观察索引
部署完成后,不要只等待自然抓取。进入 Google Search Console 与 Bing Webmaster Tools,分别完成域名所有权验证,然后提交 sitemap 地址。对于频繁更新的文档,还可以在 CI 中调用 IndexNow 或在发布成功后 ping 搜索引擎:
javascript
// scripts/ping-search-engines.mjs
const SITEMAP_URL = 'https://devui.example.com/sitemap.xml'
const endpoints = [
`https://www.google.com/ping?sitemap=${encodeURIComponent(SITEMAP_URL)}`,
`https://www.bing.com/ping?sitemap=${encodeURIComponent(SITEMAP_URL)}`
]
for (const url of endpoints) {
try {
const res = await fetch(url)
console.log(`[seo] ${res.status} ${url}`)
} catch (error) {
// 推送失败不应阻断文档发布流程,只记录告警
console.warn('[seo] ping failed:', error)
}
}需要说明的是,Google 已不再保证传统 sitemap ping 接口长期有效,因此更稳妥的方式是 Search Console API 配合发布流水线。无论使用哪种方式,都要监控 sitemap.xml 的 HTTP 状态、Content-Type 和可访问性,避免被鉴权、CDN 规则或预览环境环境变量误伤。
避坑总结:上线前必须检查的 5 个细节
1. canonical 域名写成了预览环境地址
常见现象是测试域名内容被收录,正式域名反而出现重复网页。原因通常是把 hostname 写成了 process.env.NODE_ENV 推断值,或者 CI 中 SITE_URL 指向了 preview 域名。修正方法是显式区分环境,只有生产构建才输出 canonical 和 sitemap,预览环境统一加 x-robots-tag: noindex。
2. JSON-LD 脚本能在浏览器看到,源码中却不存在
有人把结构化数据放在 onMounted 中动态插入,结果在「查看网页源代码」里找不到。虽然现代爬虫能执行 JavaScript,但这会增加渲染队列成本,也不利于首轮抓取。应通过 VitePress 主题插槽或 transformHead 直接写入 HTML,并用 Rich Results Test 验证。
3. sitemap 包含 404、重定向或草稿页
如果 sitemap 中大量 URL 返回 404、302 或 noindex,搜索引擎会降低对该文件的信任度。发布前用脚本批量请求状态码,只保留 200 且可索引的地址;内部文档、搜索页、标签聚合页如果没有独立内容价值,应通过 robots meta 禁止索引。
4. description 全站相同或被自动截断
很多项目只在站点级配置 description,页面级 frontmatter 缺失,导致搜索摘要千篇一律。建议为每个文档补充 70~120 字的独特描述,并避免堆砌关键词。transformHead 中应优先读取 frontmatter.description,缺失时再回退到摘要或站点描述。
5. 结构化数据字段类型不合法
例如把 datePublished 写成 2024/05/01、把 itemListElement.position 输出成字符串,都可能触发校验错误。日期统一使用 ISO 8601 格式,位置使用数字,并在 CI 中对 JSON-LD 做一次 JSON 解析与 schema 必填字段检查。
SEO 不是一次性开关,而是内容质量、抓取效率和语义表达的乘积。VitePress 已经提供了优秀的静态渲染基础,补齐 sitemap、canonical 和结构化数据后,剩下的工作就是持续观察 Search Console 中的索引覆盖率与查询报告,让真正有价值的文档更快命中用户搜索。
免责声明
本工具与文章内容仅用于学习测试场景,优化建议仅供参考,请勿直接用于生产环境,由此产生的风险自行承担。