검색
로컬 검색
VitePress는 minisearch로 브라우저 내 인덱스를 사용하는 퍼지(full-text) 검색을 지원합니다. 이 기능을 활성화하려면 .vitepress/config.ts 파일에서 themeConfig.search.provider 옵션을 'local'로 설정하면 됩니다:
import { defineConfig } from 'vitepress'
export default defineConfig({
themeConfig: {
search: {
provider: 'local'
}
}
})예제 결과:

대안으로 Algolia DocSearch, https://www.npmjs.com/package/vitepress-plugin-search, https://www.npmjs.com/package/vitepress-plugin-pagefind와 같은 커뮤니티 플러그인을 사용할 수도 있습니다.
i18n
다국어 검색을 사용하려면 다음과 같은 구성을 사용해야 합니다:
import { defineConfig } from 'vitepress'
export default defineConfig({
themeConfig: {
search: {
provider: 'local',
options: {
locales: {
ko: { // 기본 로케일을 번역하려면 `root`로 설정하세요
translations: {
button: {
buttonText: '검색',
buttonAriaLabel: '검색'
},
modal: {
displayDetails: '상세 목록 표시',
resetButtonTitle: '검색 재설정',
backButtonTitle: '검색 닫기',
noResultsText: '결과가 없습니다',
footer: {
selectText: '선택',
selectKeyAriaLabel: 'Enter',
navigateText: '이동',
navigateUpKeyAriaLabel: '위쪽 화살표',
navigateDownKeyAriaLabel: '아래쪽 화살표',
closeText: '닫기',
closeKeyAriaLabel: 'Esc'
}
}
}
}
}
}
}
}
})MiniSearch 옵션
MiniSearch를 다음과 같이 구성할 수 있습니다:
import { defineConfig } from 'vitepress'
export default defineConfig({
themeConfig: {
search: {
provider: 'local',
options: {
miniSearch: {
/**
* @type {Pick<import('minisearch').Options, 'extractField' | 'tokenize' | 'processTerm'>}
*/
options: {
/* ... */
},
/**
* @type {import('minisearch').SearchOptions}
* @default
* { fuzzy: 0.2, prefix: true, boost: { title: 4, text: 2, titles: 1 } }
*/
searchOptions: {
/* ... */
}
}
}
}
}
})더 자세한 내용은 MiniSearch 문서를 참고하세요.
커스텀 컨텐츠 렌더러
마크다운 컨텐츠를 인덱싱하기 전에 렌더링하는 데 사용되는 함수를 커스터마이징할 수 있습니다:
import { defineConfig } from 'vitepress'
export default defineConfig({
themeConfig: {
search: {
provider: 'local',
options: {
/**
* @param {string} src
* @param {import('vitepress').MarkdownEnv} env
* @param {import('markdown-it-async')} md
*/
async _render(src, env, md) {
// HTML 문자열을 반환
}
}
}
}
})이 함수는 클라이언트 사이드 사이트 데이터에서 제거되므로, Node.js API를 사용할 수 있습니다.
예제: 검색에서 페이지 제외
페이지의 전문에 search: false를 추가하여 검색에서 해당 페이지를 제외할 수 있습니다. 또는:
import { defineConfig } from 'vitepress'
export default defineConfig({
themeConfig: {
search: {
provider: 'local',
options: {
async _render(src, env, md) {
const html = await md.renderAsync(src, env)
if (env.frontmatter?.search === false) return ''
if (env.relativePath.startsWith('some/path')) return ''
return html
}
}
}
}
})참고
커스텀 _render 함수가 제공된 경우, search: false 전문을 직접 처리해야 합니다. 또한, md.renderAsync가 호출되기 전에 env 객체가 완전히 채워지지 않으므로, frontmatter와 같은 선택적 env 프로퍼티에 대한 검사는 그 이후에 수행해야 합니다.
예제: 콘텐츠 변환 - 앵커 추가
import { defineConfig } from 'vitepress'
export default defineConfig({
themeConfig: {
search: {
provider: 'local',
options: {
async _render(src, env, md) {
const html = await md.renderAsync(src, env)
if (env.frontmatter?.title)
return (await md.renderAsync(`# ${env.frontmatter.title}`)) + html
return html
}
}
}
}
})Algolia 검색
VitePress는 Algolia DocSearch를 사용한 문서 사이트 검색 기능을 지원합니다. 시작 가이드를 참고하세요. .vitepress/config.ts에서 이 기능을 사용하려면 최소한 다음 구성을 제공해야 합니다:
import { defineConfig } from 'vitepress'
export default defineConfig({
themeConfig: {
search: {
provider: 'algolia',
options: {
appId: '...',
apiKey: '...',
indexName: '...'
}
}
}
})i18n
다국어 검색을 사용하려면 다음과 같이 구성해야 합니다:
클릭하여 펼치기
import { defineConfig } from 'vitepress'
export default defineConfig({
themeConfig: {
search: {
provider: 'algolia',
options: {
appId: '...',
apiKey: '...',
indexName: '...',
locales: {
zh: {
translations: {
button: {
buttonText: '搜索',
buttonAriaLabel: '搜索'
},
modal: {
searchBox: {
clearButtonTitle: '清除',
clearButtonAriaLabel: '清除查询',
closeButtonText: '关闭',
closeButtonAriaLabel: '关闭',
placeholderText: '搜索文档或向 AI 提问',
placeholderTextAskAi: '再问一个问题...',
placeholderTextAskAiStreaming: '正在回答...',
searchInputLabel: '搜索',
backToKeywordSearchButtonText: '返回关键词搜索',
backToKeywordSearchButtonAriaLabel: '返回关键词搜索',
newConversationPlaceholder: '提问',
conversationHistoryTitle: '我的对话历史',
startNewConversationText: '开始新的对话',
viewConversationHistoryText: '对话历史',
threadDepthErrorPlaceholder: '对话已达上限'
},
newConversation: {
newConversationTitle: '我今天能帮你什么?',
newConversationDescription:
'我会搜索你的文档,快速帮你找到设置指南、功能细节和故障排除提示。'
},
footer: {
selectText: '选择',
submitQuestionText: '提交问题',
selectKeyAriaLabel: '回车键',
navigateText: '导航',
navigateUpKeyAriaLabel: '向上箭头',
navigateDownKeyAriaLabel: '向下箭头',
closeText: '关闭',
backToSearchText: '返回搜索',
closeKeyAriaLabel: 'Esc 键',
poweredByText: '由…提供支持'
},
errorScreen: {
titleText: '无法获取结果',
helpText: '你可能需要检查网络连接。'
},
startScreen: {
recentSearchesTitle: '最近',
noRecentSearchesText: '暂无最近搜索',
saveRecentSearchButtonTitle: '保存此搜索',
removeRecentSearchButtonTitle: '从历史记录中移除此搜索',
favoriteSearchesTitle: '收藏',
removeFavoriteSearchButtonTitle: '从收藏中移除此搜索',
recentConversationsTitle: '最近对话',
removeRecentConversationButtonTitle: '从历史记录中移除此对话'
},
noResultsScreen: {
noResultsText: '未找到相关结果',
suggestedQueryText: '尝试搜索',
reportMissingResultsText: '认为此查询应该有结果?',
reportMissingResultsLinkText: '告诉我们。'
},
resultsScreen: {
askAiPlaceholder: '询问 AI:',
noResultsAskAiPlaceholder: '文档里没找到?让 Ask AI 帮忙:'
},
askAiScreen: {
disclaimerText: '回答由 AI 生成,可能会出错。请核实。',
relatedSourcesText: '相关来源',
thinkingText: '思考中...',
copyButtonText: '复制',
copyButtonCopiedText: '已复制!',
copyButtonTitle: '复制',
likeButtonTitle: '喜欢',
dislikeButtonTitle: '不喜欢',
thanksForFeedbackText: '感谢你的反馈!',
preToolCallText: '搜索中...',
duringToolCallText: '搜索中...',
afterToolCallText: '已搜索',
stoppedStreamingText: '你已停止此回复',
errorTitleText: '聊天错误',
threadDepthExceededMessage: '为保持回答准确,此对话已关闭。',
startNewConversationButtonText: '开始新的对话'
}
}
},
askAi: {
sidePanel: {
button: {
translations: {
buttonText: '询问 AI',
buttonAriaLabel: '询问 AI'
}
},
panel: {
translations: {
header: {
title: '询问 AI',
conversationHistoryTitle: '我的对话历史',
newConversationText: '开始新的对话',
viewConversationHistoryText: '对话历史'
},
promptForm: {
promptPlaceholderText: '提问',
promptAnsweringText: '正在回答...',
promptAskAnotherQuestionText: '再问一个问题',
promptDisclaimerText: '回答由 AI 生成,可能会出错。',
promptLabelText: '按回车发送,Shift+回车换行。',
promptAriaLabelText: '问题输入'
},
conversationScreen: {
preToolCallText: '搜索中...',
searchingText: '搜索中...',
toolCallResultText: '已搜索',
conversationDisclaimer:
'回答由 AI 生成,可能会出错。请核实。',
reasoningText: '推理中...',
thinkingText: '思考中...',
relatedSourcesText: '相关来源',
stoppedStreamingText: '你已停止此回复',
copyButtonText: '复制',
copyButtonCopiedText: '已复制!',
likeButtonTitle: '喜欢',
dislikeButtonTitle: '不喜欢',
thanksForFeedbackText: '感谢你的反馈!',
errorTitleText: '聊天错误'
},
newConversationScreen: {
titleText: '我今天能帮你什么?',
introductionText:
'我会搜索你的文档,快速帮你找到设置指南、功能细节和故障排除提示。'
},
logo: {
poweredByText: '由…提供支持'
}
}
}
}
}
}
}
}
}
}
})자세한 내용은 공식 Algolia 문서를 참고하세요. 빠르게 시작하려면 이 사이트에서 사용하는 번역을 GitHub 저장소에서 복사할 수도 있습니다.
Algolia Ask AI 지원
Ask AI 기능을 사용하려면 askAi 옵션을 추가하세요:
import { defineConfig } from 'vitepress'
export default defineConfig({
themeConfig: {
search: {
provider: 'algolia',
options: {
appId: '...',
apiKey: '...',
indexName: '...',
// askAi: "내-어시스턴트-ID"
// 또는
askAi: {
// 최소한 Algolia에서 받은 assistantId를 제공해야 합니다
assistantId: 'XXXYYY',
// 선택적 재정의 — 생략하면 상위 appId/apiKey/indexName 값이 재사용됩니다
// apiKey: '...',
// appId: '...',
// indexName: '...'
}
}
}
}
})참고
Ask AI를 사용하지 않으려면 askAi 옵션을 생략하면 됩니다.
Ask AI 사이드 패널
DocSearch v4.5+는 선택적 Ask AI 사이드 패널을 지원합니다. 활성화되면 기본적으로 Ctrl/Cmd+I로 열 수 있습니다. 사이드 패널 API 참조에 전체 옵션 목록이 있습니다.
import { defineConfig } from 'vitepress'
export default defineConfig({
themeConfig: {
search: {
provider: 'algolia',
options: {
appId: '...',
apiKey: '...',
indexName: '...',
askAi: {
assistantId: 'XXXYYY',
sidePanel: {
// @docsearch/sidepanel-js SidepanelProps API 반영
panel: {
variant: 'floating', // 또는 'inline'
side: 'right',
width: '360px',
expandedWidth: '580px',
suggestedQuestions: true
}
}
}
}
}
}
})키보드 단축키를 비활성화해야 하는 경우 사이드 패널의 keyboardShortcuts 옵션을 사용하세요:
import { defineConfig } from 'vitepress'
export default defineConfig({
themeConfig: {
search: {
provider: 'algolia',
options: {
appId: '...',
apiKey: '...',
indexName: '...',
askAi: {
assistantId: 'XXXYYY',
sidePanel: {
keyboardShortcuts: {
'Ctrl/Cmd+I': false
}
}
}
}
}
}
})모드 (auto / sidePanel / hybrid / modal)
VitePress가 키워드 검색과 Ask AI를 통합하는 방식을 선택적으로 제어할 수 있습니다:
mode: 'auto'(기본값): 키워드 검색이 구성된 경우hybrid를 추론하고, 그렇지 않으면 Ask AI 사이드 패널이 구성된 경우sidePanel을 추론합니다.mode: 'sidePanel': 사이드 패널만 강제 (키워드 검색 버튼 숨김).mode: 'hybrid': 키워드 검색 모달 + Ask AI 사이드 패널 활성화 (키워드 검색 구성 필요).mode: 'modal': Ask AI를 DocSearch 모달 내부에 유지 (사이드 패널을 구성한 경우에도).
Ask AI만 (키워드 검색 없음)
Ask AI 사이드 패널만 사용하려면 최상위 키워드 검색 구성을 생략하고 askAi 아래에 자격 증명을 제공할 수 있습니다:
import { defineConfig } from 'vitepress'
export default defineConfig({
themeConfig: {
search: {
provider: 'algolia',
options: {
mode: 'sidePanel',
askAi: {
assistantId: 'XXXYYY',
appId: '...',
apiKey: '...',
indexName: '...',
sidePanel: true
}
}
}
}
})크롤러 구성
이 사이트에서 사용하는 예제 구성을 소개합니다:
new Crawler({
appId: '...',
apiKey: '...',
rateLimit: 8,
startUrls: ['https://vitepress.dev/'],
renderJavaScript: false,
sitemaps: [],
exclusionPatterns: [],
ignoreCanonicalTo: false,
discoveryPatterns: ['https://vitepress.dev/**'],
schedule: 'at 05:10 on Saturday',
actions: [
{
indexName: 'vitepress',
pathsToMatch: ['https://vitepress.dev/**'],
recordExtractor: ({ $, helpers }) => {
return helpers.docsearch({
recordProps: {
lvl1: '.content h1',
content: '.content p, .content li',
lvl0: {
selectors: 'section.has-active div h2',
defaultValue: 'Documentation'
},
lvl2: '.content h2',
lvl3: '.content h3',
lvl4: '.content h4',
lvl5: '.content h5'
},
indexHeadings: true
})
}
}
],
initialIndexSettings: {
vitepress: {
attributesForFaceting: ['type', 'lang'],
attributesToRetrieve: ['hierarchy', 'content', 'anchor', 'url'],
attributesToHighlight: ['hierarchy', 'hierarchy_camel', 'content'],
attributesToSnippet: ['content:10'],
camelCaseAttributes: ['hierarchy', 'hierarchy_radio', 'content'],
searchableAttributes: [
'unordered(hierarchy_radio_camel.lvl0)',
'unordered(hierarchy_radio.lvl0)',
'unordered(hierarchy_radio_camel.lvl1)',
'unordered(hierarchy_radio.lvl1)',
'unordered(hierarchy_radio_camel.lvl2)',
'unordered(hierarchy_radio.lvl2)',
'unordered(hierarchy_radio_camel.lvl3)',
'unordered(hierarchy_radio.lvl3)',
'unordered(hierarchy_radio_camel.lvl4)',
'unordered(hierarchy_radio.lvl4)',
'unordered(hierarchy_radio_camel.lvl5)',
'unordered(hierarchy_radio.lvl5)',
'unordered(hierarchy_radio_camel.lvl6)',
'unordered(hierarchy_radio.lvl6)',
'unordered(hierarchy_camel.lvl0)',
'unordered(hierarchy.lvl0)',
'unordered(hierarchy_camel.lvl1)',
'unordered(hierarchy.lvl1)',
'unordered(hierarchy_camel.lvl2)',
'unordered(hierarchy.lvl2)',
'unordered(hierarchy_camel.lvl3)',
'unordered(hierarchy.lvl3)',
'unordered(hierarchy_camel.lvl4)',
'unordered(hierarchy.lvl4)',
'unordered(hierarchy_camel.lvl5)',
'unordered(hierarchy.lvl5)',
'unordered(hierarchy_camel.lvl6)',
'unordered(hierarchy.lvl6)',
'content'
],
distinct: true,
attributeForDistinct: 'url',
customRanking: [
'desc(weight.pageRank)',
'desc(weight.level)',
'asc(weight.position)'
],
ranking: [
'words',
'filters',
'typo',
'attribute',
'proximity',
'exact',
'custom'
],
highlightPreTag: '<span class="algolia-docsearch-suggestion--highlight">',
highlightPostTag: '</span>',
minWordSizefor1Typo: 3,
minWordSizefor2Typos: 7,
allowTyposOnNumericTokens: false,
minProximity: 1,
ignorePlurals: true,
advancedSyntax: true,
attributeCriteriaComputedByMinProximity: true,
removeWordsIfNoResults: 'allOptional'
}
}
})