vitepress-plugin-pagefind
Pagefind integration for VitePress: static search with filtering, zero bundle overhead and a ready-made UI component.
What you get compared to VitePress's built-in local search:
- No 2 MB MiniSearch index in the bundle, Pagefind loads chunks lazily (~45 KB on the first search)
- Configurable filters (by distribution, version, etc.)
- Support for merging indexes of several sites (
mergeIndex) for global search - Match highlighting in snippets out of the box
Installation
npm install -D @ampernic/vitepress-plugin-pagefind pagefindpagefind is the indexing CLI and must be a devDependency of the docs project.
Usage
1. Wiring in the VitePress config
// .vitepress/config.ts
import { defineConfig } from 'vitepress'
import { PagefindPlugin } from '@ampernic/vitepress-plugin-pagefind'
export default defineConfig({
...PagefindPlugin({
distroName: 'alt-workstation', // site slug for the distro filter
extractFilters: (id) => {
// Tag pages by version (extracted from the file path)
const m = id.match(/[/\\](\d+\.\d+(?:-\w+)?)[/\\]/)
return m ? { version: m[1] } : null
},
}),
// ... rest of the config
})The plugin adds to the config:
transformHtmlinjects<span hidden data-pagefind-filter="...">before</body>on every pagebuildEndwritespagefind/distro-meta.json, then runspagefind --site .vitepress/dist
2. Search component
<script setup lang="ts">
import { PagefindSearch } from '@ampernic/vitepress-plugin-pagefind/client'
import { useRoute } from 'vitepress'
import { computed } from 'vue'
const route = useRoute()
const filters = computed(() => {
const m = route.path.match(/^\/(\d+\.\d+(?:-\w+)?)\//)
const ver = m?.[1]
return ver ? { version: ver } : {}
})
</script>
<template>
<PagefindSearch
:filters="filters"
:placeholder="filters.version ? `Search in version ${filters.version}...` : 'Search...'"
@close="/* close */"
/>
</template>API
PagefindPlugin(options?)
Returns a VitePress config fragment. Spread it into the root of the config:
export default defineConfig({
...PagefindPlugin({ ... }),
vite: {
plugins: [...PagefindPlugin({ ... }).vite.plugins, /* other plugins */],
},
})| Option | Type | Default | Description |
|---|---|---|---|
outDir | string | '.vitepress/dist' | Built site directory to index |
distroName | string | - | Distribution slug, injected as distro:<distroName> filter on every page |
extractFilters | (filePath: string) => Record<string, string> | null | - | Function that extracts extra filters from the output HTML path |
extraArgs | string[] | [] | Extra CLI arguments for pagefind |
debug | boolean | false | Verbose log: collected filters, CLI arguments, outDir path. When using the theme, toggled via createSharedConfig({ debug: true }) |
PagefindSearch (component)
| Prop | Type | Default | Description |
|---|---|---|---|
filters | Record<string, string> | {} | Search filters (for example { version: '11.1' }) |
placeholder | string | 'Search...' | Text field placeholder |
maxResults | number | 0 | Maximum number of results; 0 means no limit |
Emit: close when the modal window closes.
How filtering works
During build,
transformHtmladds one hidden<span>per filter before</body>:html<span hidden data-pagefind-filter="distro:alt-workstation"></span> <span hidden data-pagefind-filter="version:11.1"></span>Important: you cannot put several
data-pagefind-filterattributes on the same element, the HTML spec keeps only the first duplicate. Several filters need separate elements.Collected filter values are written to
pagefind/distro-meta.jsononbuildEnd:json{"distro":["alt-workstation"],"version":["10.0","10.1","11.0","11.1","11.2"]}The file is consumed by
ADGlobalSearchto render the exact per-distribution versions, sincepagefind.filters()on merged indexes returns values from every site without isolation.Pagefind indexes the attributes on
buildEnd.At runtime the filters are passed to
pagefind.search(query, { filters: { version: '11.1' } }).
Distribution/version auto-preset
The unified <ADGlobalSearch> dialog (in the theme) accepts defaultDistro and defaultVersion, which the themed ADNavBarSearch fills from the current URL. The distribution is taken from the LAST segment of site.base, regex \/([^/]+)\/?$ (not ^\/...$), because on the preview hosting the base has the shape /docs-vitepress/<distro>/ (two segments) while prod is /<distro>/ (one segment). Both cases resolve to the current distro.
Behavior in dev mode
The plugin adds Vite middleware that serves files from outDir/pagefind/, so search works on the index produced by the last vitepress build.
vitepress build # build the index once
vitepress dev # search works