搜索方案选型对比
为 VitePress 站点选择合适的搜索方案是提升用户体验的关键决策。本文全面对比本地搜索、Algolia DocSearch 和其他第三方方案,帮助你做出最佳选择。
方案概览
| 特性 | 本地搜索 | Algolia DocSearch | Pagefind | 自定义搜索 |
|---|---|---|---|---|
| 成本 | 免费 | 免费(开源项目)/ 付费 | 免费 | 取决于实现 |
| 索引构建 | 构建时 | 云端爬虫 | 构建后 | 自定义 |
| 索引大小 | 全量加载 | 按需加载 | 分片加载 | 取决于实现 |
| 搜索速度 | 快 | 非常快 | 快 | 取决于实现 |
| 中文支持 | ⚠️ 需配置 | ✅ 原生支持 | ✅ 支持 | 取决于实现 |
| 离线可用 | ✅ 是 | ❌ 否 | ✅ 是 | 取决于实现 |
| 配置复杂度 | 低 | 中 | 低 | 高 |
| 维护成本 | 低 | 低 | 低 | 高 |
| 适用规模 | < 500 页 | 任意 | < 1000 页 | 任意 |
本地搜索
工作原理
text
┌─────────────┐ 构建时 ┌──────────────┐
│ Markdown │ ──────────────→ │ 搜索索引 │
│ 源文件 │ miniSearch │ (JSON) │
└─────────────┘ └──────────────┘
│
▼
┌─────────────┐ 运行时 ┌──────────────┐
│ 用户输入 │ ──────────────→ │ 客户端搜索 │
│ 搜索词 │ miniSearch │ 结果匹配 │
└─────────────┘ └──────────────┘VitePress 内置本地搜索基于 miniSearch,在构建时生成搜索索引,运行时在浏览器端完成搜索。
优势
- 零成本:无需第三方服务
- 零依赖:无需申请 API Key
- 离线可用:PWA 场景下可用
- 配置简单:开箱即用
- 数据私密:索引不离开站点
劣势
- 索引体积:所有页面索引打包进 JS,页面多时影响加载
- 中文分词:默认不支持中文分词,需额外配置
- 搜索质量:模糊匹配能力有限,同义词/拼写纠错支持弱
- 更新延迟:需重新构建才能更新索引
最佳配置
ts
// .vitepress/config.mts
export default defineConfig({
themeConfig: {
search: {
provider: 'local',
options: {
// 中文分词优化
_render(src, env, md) {
// 添加 HTML 标签以支持中文搜索
const html = md.render(src, env)
// 移除 HTML 标签,保留纯文本
return html
.replace(/<[^>]+>/g, ' ')
.replace(/\s+/g, ' ')
.trim()
},
// 详细视图配置
detailedView: true,
// 翻译
locales: {
root: {
translations: {
button: { buttonText: '搜索文档', buttonAriaLabel: '搜索文档' },
modal: {
noResultsText: '无法找到相关结果',
resetButtonTitle: '清除查询条件',
footer: { selectText: '选择', navigateText: '切换', closeText: '关闭' }
}
}
}
}
}
}
}
})适用场景
- 个人博客或小型文档站(< 200 页)
- 内网/离线环境
- 不想依赖第三方服务
- 快速原型验证
Algolia DocSearch
工作原理
text
┌─────────────┐ 爬虫抓取 ┌──────────────┐ API 查询 ┌──────────────┐
│ VitePress │ ─────────────→ │ Algolia │ ←──────────── │ 用户浏览器 │
│ 站点页面 │ 定期爬取 │ 云端索引 │ 搜索请求 │ 搜索结果 │
└─────────────┘ └──────────────┘ └──────────────┘Algolia DocSearch 通过爬虫定期抓取站点内容,在云端建立索引,用户搜索时通过 API 实时查询。
优势
- 搜索质量高:支持同义词、拼写纠错、高亮匹配
- 中文支持好:原生支持中文分词
- 零运维:索引托管在 Algolia 云端
- 分析功能:搜索统计和用户行为分析
- 扩展性强:支持大规模文档(10 万+ 页面)
- 按需加载:搜索索引不占用站点体积
劣势
- 需要申请:开源项目免费,商业项目需付费
- 更新延迟:爬虫定期抓取,非实时更新
- 网络依赖:需联网才能搜索
- 配置复杂:需配置爬虫、API Key 等
- 国内访问:Algolia 服务器在海外,国内可能有延迟
最佳配置
ts
// .vitepress/config.mts
export default defineConfig({
themeConfig: {
search: {
provider: 'algolia',
options: {
appId: 'YOUR_APP_ID',
apiKey: 'YOUR_API_KEY',
indexName: 'your-index-name',
// 搜索行为优化
searchParameters: {
facetFilters: ['language:zh'],
hitsPerPage: 10
},
// 结果转换
transformItems(items) {
return items.map(item => ({
...item,
// 高亮匹配文本
highlight: item._highlightResult?.hierarchy?.lvl1?.value || item.hierarchy.lvl1
}))
},
// 多语言
locales: {
'/zh/': {
placeholder: '搜索文档',
translations: {
button: { buttonText: '搜索' },
modal: { noResultsText: '没有找到相关结果' }
}
}
}
}
}
}
})适用场景
- 中大型文档站(> 200 页)
- 开源项目文档
- 需要高质量搜索体验
- 有国际用户(多语言搜索)
- 需要搜索分析数据
Pagefind
工作原理
text
┌─────────────┐ 构建后 ┌──────────────┐ 运行时 ┌──────────────┐
│ VitePress │ ─────────────→ │ Pagefind │ ─────────────→ │ 浏览器搜索 │
│ 构建输出 │ 索引生成 │ 分片索引 │ 按需加载 │ 结果匹配 │
└─────────────┘ └──────────────┘ └──────────────┘Pagefind 是一个完全静态的搜索方案,在构建完成后生成索引,支持分片按需加载。
优势
- 免费开源:无需申请
- 分片加载:大站点索引不会一次性加载
- 中文支持:内置中文分词
- 离线可用:静态索引,PWA 友好
- UI 自定义:提供灵活的 UI 组件
劣势
- 社区较小:相比 Algolia 生态较小
- VitePress 集成:需手动集成,非官方支持
- 搜索质量:介于本地搜索和 Algolia 之间
- 维护风险:单人维护项目
集成方法
bash
# 安装 Pagefind
npm install -D pagefindts
// 构建后运行 Pagefind 索引
// package.json
{
"scripts": {
"build": "vitepress build docs && npx pagefind --site docs/.vitepress/dist --output-path public/pagefind",
"preview": "vitepress preview docs"
}
}vue
<!-- .vitepress/theme/components/PagefindSearch.vue -->
<script setup lang="ts">
import { ref, onMounted } from 'vue'
const searchRef = ref<HTMLDivElement>()
const results = ref<any[]>([])
const query = ref('')
onMounted(async () => {
// 动态加载 Pagefind
if (typeof window !== 'undefined') {
// @ts-expect-error Pagefind 运行时注入
window.pagefind = await import('/pagefind/pagefind.js')
}
})
async function handleSearch() {
if (!window.pagefind || !query.value) {
results.value = []
return
}
const search = await window.pagefind.search(query.value)
results.value = await Promise.all(
search.results.map((r: any) => r.data())
)
}
</script>
<template>
<div class="pagefind-search">
<input
v-model="query"
type="text"
placeholder="搜索文档..."
@input="handleSearch"
/>
<ul v-if="results.length" class="search-results">
<li v-for="result in results" :key="result.url">
<a :href="result.url" v-html="result.meta?.title" />
<p v-if="result.excerpt" v-html="result.excerpt" />
</li>
</ul>
</div>
</template>
<style scoped>
.pagefind-search {
position: relative;
}
.pagefind-search input {
width: 100%;
padding: 8px 12px;
border: 1px solid var(--vp-c-divider);
border-radius: 8px;
background: var(--vp-c-bg-soft);
color: var(--vp-c-text-1);
}
.search-results {
position: absolute;
top: 100%;
left: 0;
right: 0;
background: var(--vp-c-bg);
border: 1px solid var(--vp-c-divider);
border-radius: 8px;
max-height: 400px;
overflow-y: auto;
z-index: 99;
}
.search-results li {
padding: 8px 12px;
border-bottom: 1px solid var(--vp-c-divider);
}
.search-results a {
color: var(--vp-c-brand);
font-weight: 500;
}
</style>适用场景
- 中型文档站(200-1000 页)
- 需要更好的中文支持但不想用 Algolia
- 需要离线搜索
- 希望控制搜索索引大小
自定义搜索方案
Elasticsearch + Vue
适用于已有 Elasticsearch 基础设施的团队:
ts
// composables/useElasticsearch.ts
import { ref } from 'vue'
interface SearchResult {
title: string
url: string
excerpt: string
score: number
}
export function useElasticsearch(indexUrl: string) {
const results = ref<SearchResult[]>([])
const isLoading = ref(false)
const error = ref<string | null>(null)
async function search(query: string) {
if (!query) {
results.value = []
return
}
isLoading.value = true
error.value = null
try {
const response = await fetch(`${indexUrl}/_search`, {
method: 'POST',
headers: { 'Content-Type': 'application/json' },
body: JSON.stringify({
query: {
multi_match: {
query,
fields: ['title^3', 'content^2', 'headings^2'],
type: 'best_fields',
fuzziness: 'AUTO'
}
},
highlight: {
fields: {
content: { fragment_size: 150, number_of_fragments: 1 }
}
},
size: 10
})
})
const data = await response.json()
results.value = data.hits.hits.map((hit: any) => ({
title: hit._source.title,
url: hit._source.url,
excerpt: hit.highlight?.content?.[0] || hit._source.content?.slice(0, 150),
score: hit._score
}))
} catch (e) {
error.value = '搜索请求失败'
results.value = []
} finally {
isLoading.value = false
}
}
return { results, isLoading, error, search }
}MeiliSearch
适用于需要自托管搜索服务的场景:
bash
# Docker 部署 MeiliSearch
docker run -d \
-p 7700:7700 \
-e MEILI_MASTER_KEY=your-master-key \
getmeili/meilisearch:latestts
// composables/useMeiliSearch.ts
import { ref } from 'vue'
import { MeiliSearch } from 'meilisearch'
export function useMeiliSearch(host: string, apiKey: string, indexName: string) {
const client = new MeiliSearch({ host, apiKey })
const results = ref<any[]>([])
const isLoading = ref(false)
async function search(query: string) {
if (!query) {
results.value = []
return
}
isLoading.value = true
try {
const searchResults = await client.index(indexName).search(query, {
attributesToHighlight: ['title', 'content'],
attributesToCrop: ['content'],
cropLength: 150,
limit: 10
})
results.value = searchResults.hits
} finally {
isLoading.value = false
}
}
return { results, isLoading, search }
}选型决策树
text
需要搜索功能?
├── 否 → 不配置搜索
└── 是 → 页面数量?
├── < 200 页 → 中文内容为主?
│ ├── 是 → 本地搜索(配置分词)
│ └── 否 → 本地搜索
├── 200-1000 页 → 是开源项目?
│ ├── 是 → Algolia DocSearch(推荐)
│ └── 否 → 预算?
│ ├── 免费 → Pagefind
│ └── 有预算 → Algolia
└── > 1000 页 → 自托管 vs 云服务?
├── 云服务 → Algolia
└── 自托管 → Elasticsearch / MeiliSearch性能对比
索引体积
| 方案 | 100 页 | 500 页 | 2000 页 |
|---|---|---|---|
| 本地搜索 | ~50 KB | ~250 KB | ~1 MB |
| Algolia | 0 KB | 0 KB | 0 KB |
| Pagefind | ~30 KB(分片) | ~150 KB(分片) | ~600 KB(分片) |
说明
Algolia 索引存储在云端,不占用站点体积。Pagefind 使用分片加载,初始仅加载核心索引。
搜索延迟
| 方案 | 首次搜索 | 后续搜索 | 网络请求 |
|---|---|---|---|
| 本地搜索 | 50-100 ms | 10-30 ms | 无 |
| Algolia | 100-300 ms | 50-150 ms | 每次搜索 |
| Pagefind | 50-150 ms | 10-50 ms | 首次加载 |
迁移指南
从本地搜索迁移到 Algolia
ts
// 1. 申请 Algolia DocSearch
// 访问 https://docsearch.algolia.com/apply/
// 2. 修改配置
// 修改前:
export default defineConfig({
themeConfig: {
search: {
provider: 'local'
}
}
})
// 修改后:
export default defineConfig({
themeConfig: {
search: {
provider: 'algolia',
options: {
appId: 'YOUR_APP_ID',
apiKey: 'YOUR_API_KEY',
indexName: 'your-index-name'
}
}
}
})从 Algolia 迁移到 Pagefind
bash
# 1. 安装 Pagefind
npm install -D pagefind
# 2. 修改构建脚本
# 在 package.json 中添加构建后索引步骤
# 3. 替换搜索组件
# 移除 Algolia 配置,使用自定义 Pagefind 组件最佳实践
1. 搜索优化通用建议
markdown
<!-- ✅ 好的标题结构:层级清晰 -->
## 安装指南
### 系统要求
### 安装步骤
<!-- ❌ 差的标题结构:缺少层级 -->
## 安装指南
#### 系统要求 <!-- 跳过了三级标题 -->
#### 安装步骤2. 提升搜索质量的技巧
| 技巧 | 说明 |
|---|---|
| 标题层级一致 | 确保标题层级连续,利于索引 |
| 关键词前置 | 重要关键词放在标题和首段 |
| 避免重复内容 | 相同内容不要出现在多个页面 |
| 使用 alt 文本 | 图片 alt 有助于搜索索引 |
| 排除无用页面 | 搜索配置中排除 404、变更日志等 |
3. 搜索排除配置
ts
// 排除不需要搜索的页面
export default defineConfig({
themeConfig: {
search: {
provider: 'local',
options: {
// 排除指定页面
exclude: [
'/changelog',
'/404',
'/tags/**',
'/community/team'
]
}
}
}
})4. 搜索体验优化
ts
// Algolia 高级配置:搜索结果优化
export default defineConfig({
themeConfig: {
search: {
provider: 'algolia',
options: {
appId: 'YOUR_APP_ID',
apiKey: 'YOUR_API_KEY',
indexName: 'your-index-name',
// 精确匹配权重更高
searchParameters: {
exactOnSingleWordQuery: 'attribute',
attributesToSnippet: ['content:20']
},
// 自定义结果排序
transformItems(items) {
return items.sort((a, b) => {
// 官方文档优先
if (a.url.includes('/guide/') && !b.url.includes('/guide/')) return -1
if (!a.url.includes('/guide/') && b.url.includes('/guide/')) return 1
return 0
})
}
}
}
}
})常见问题
Q: 本地搜索中文不生效?
确保 miniSearch 配置了中文分词。可以自定义 _render 函数:
ts
search: {
provider: 'local',
options: {
_render(src, env, md) {
const html = md.render(src, env)
return html.replace(/<[^>]+>/g, ' ').trim()
}
}
}Q: Algolia 搜索结果更新延迟?
Algolia 爬虫默认每 24 小时抓取一次。可以在 Algolia Dashboard 手动触发抓取,或配置 Webhook 在部署后自动触发。
Q: 如何在搜索结果中排除某些页面?
ts
// 方式一:frontmatter 排除
---
search: false
---
// 方式二:配置排除(本地搜索)
search: {
provider: 'local',
options: { exclude: ['/changelog'] }
}
// 方式三:爬虫配置排除(Algolia)
// 在 DocSearch 配置 JSON 中设置
{
"exclude_paths": ["changelog", "blog/**"]
}Q: 大型文档站应该选择哪个方案?
- 页面 > 2000:推荐 Algolia(搜索质量最好)
- 页面 500-2000:Algolia 或 Pagefind 均可
- 内网/离线环境:Pagefind 或本地搜索
- 自托管需求:MeiliSearch 或 Elasticsearch
相关资源
- 本地搜索配置 — VitePress 内置搜索详解
- Algolia 搜索集成 — Algolia 深度配置指南
- Pagefind 官方文档
- MeiliSearch 官方文档