Skip to content

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

bash
npm install -D @ampernic/vitepress-plugin-pagefind pagefind

pagefind is the indexing CLI and must be a devDependency of the docs project.

Usage

1. Wiring in the VitePress config

ts
// .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:

  • transformHtml injects <span hidden data-pagefind-filter="..."> before </body> on every page
  • buildEnd writes pagefind/distro-meta.json, then runs pagefind --site .vitepress/dist

2. Search component

vue
<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:

ts
export default defineConfig({
  ...PagefindPlugin({ ... }),
  vite: {
    plugins: [...PagefindPlugin({ ... }).vite.plugins, /* other plugins */],
  },
})
OptionTypeDefaultDescription
outDirstring'.vitepress/dist'Built site directory to index
distroNamestring-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
extraArgsstring[][]Extra CLI arguments for pagefind
debugbooleanfalseVerbose log: collected filters, CLI arguments, outDir path. When using the theme, toggled via createSharedConfig({ debug: true })

PagefindSearch (component)

PropTypeDefaultDescription
filtersRecord<string, string>{}Search filters (for example { version: '11.1' })
placeholderstring'Search...'Text field placeholder
maxResultsnumber0Maximum number of results; 0 means no limit

Emit: close when the modal window closes.

How filtering works

  1. During build, transformHtml adds 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-filter attributes on the same element, the HTML spec keeps only the first duplicate. Several filters need separate elements.

  2. Collected filter values are written to pagefind/distro-meta.json on buildEnd:

    json
    {"distro":["alt-workstation"],"version":["10.0","10.1","11.0","11.1","11.2"]}

    The file is consumed by ADGlobalSearch to render the exact per-distribution versions, since pagefind.filters() on merged indexes returns values from every site without isolation.

  3. Pagefind indexes the attributes on buildEnd.

  4. 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.

bash
vitepress build  # build the index once
vitepress dev    # search works