Fulltext Search with Meilisearch
Fulltext Search with Meilisearch
Scope of this page. This is a short public overview of how the documentation site's search is wired up. Operational detail — the Meilisearch server setup, the Apache reverse proxy, master-key handling, and the GitLab CI/CD variables — lives in the ICDP-internal wiki under "Meilisearch ops". Only the client-side and repository-facing pieces are described here.
Problem
The default VuePress search (@vuepress/plugin-search) only indexes page titles and headings, not body text. For ~300 documentation pages that is too shallow. A self-hosted Meilisearch instance now provides full-text search over the rendered HTML, with results scoped to each page's main content.
What is used and why
| Piece | Choice | Reason |
|---|---|---|
| Search engine | Meilisearch (self-hosted, behind an Apache reverse proxy) | Fast, self-hosted, no third-party credentials to lose. |
| Browser widget | @algolia/autocomplete-js + @meilisearch/autocomplete-client | Renders the always-visible input with a dropdown of live results — the same UX pattern the old header-only search had, so users do not have to learn a new interaction. A separate search page is intentionally not built. |
| Indexing | Node script in .vuepress/scripts/index-meilisearch.mjs, run as a CI stage | Reindexes the live site on every successful deploy. |
Note:
meilisearch-docsearch(the Algolia-DocSearch-v3-style package) was considered and rejected — it renders a button that opens a modal overlay, not an inline dropdown, and itscontainer/placeholderAPI is frequently misdocumented online.
How it is wired into the repo
Client widget —
.vuepress/components/Docsearch.vuerenders an<div id="meili-autocomplete">and initialises Algolia Autocomplete insideonMounted(client-side only; the heavy libraries are dynamically imported so they stay out of the SSR bundle). It is registered globally under the nameDocsearch, which is the component name theme-hope's navbar already probes for in its"Search"navbar slot. No layout change is needed.Build-time configuration —
.vuepress/config.shared.tsdeclares a smallmeilisearch-defineplugin that forwards three values to the client bundle via the bundler'sdefinehook:MEILISEARCH_HOST— public URL of the Meilisearch reverse proxy.MEILISEARCH_SEARCH_API_KEY— a search-only key. This is safe to expose: it can only runsearchon the docs index.MEILISEARCH_INDEX_UID— index name (e.g.docs).
The master key is never sent to the browser; it is only used by the CI indexer job.
Old search removed —
@vuepress/plugin-searchwas uninstalled and its registration deleted, so the navbar shows one search box, not two.Indexer —
.vuepress/scripts/index-meilisearch.mjswalks the builtdist/, extracts each page's<h1>/<h2>hierarchy and body text (scoped to.theme-hope-content), and pushes documents to Meilisearch in batches of 100. It runs as theindex searchGitLab CI stage, after the HTML is deployed, withallow_failure: trueso a Meilisearch outage never blocks a documentation release.
PDF build is unaffected
The vuepress-export-pdf CI job (.gitlab-ci.yml) overwrites .vuepress/client.ts with client.vuepress-export.ts and switches to @vuepress/theme-default before building, so the Docsearch component is neither registered nor rendered in the PDF. The 500-page offline PDF therefore contains no search box, as intended.
Operations (see internal wiki)
Server-side topics that do not belong in this public repo:
- Running Meilisearch under systemd / Docker and persisting
/meili_data. - The Apache
ProxyPasssnippet that exposes Meilisearch same-origin underhttps://data.icdp-online.org/mdis-docs/search/(avoids CORS entirely). - Creating the search-only API key scoped to
actions:["search"]on the docs index, and rotating it. - The four GitLab CI/CD variables that must be defined in the project settings:
MEILISEARCH_HOST,MEILISEARCH_SEARCH_API_KEY,MEILISEARCH_MASTER_KEY(masked + protected),MEILISEARCH_INDEX_UID. - Backing up the data directory and the master key.
Local development
Without the env vars set, Docsearch.vue logs a warning and renders nothing, so npm run dev / npm run build work offline. To exercise the widget locally against a running Meilisearch:
export MEILISEARCH_HOST=http://localhost:7700
export MEILISEARCH_SEARCH_API_KEY=<your-search-only-key>
export MEILISEARCH_INDEX_UID=docs
npm run dev
To dry-run the indexer against a built dist/ without contacting Meilisearch:
DRY_RUN=1 DIST_DIR=./.vuepress/dist node .vuepress/scripts/index-meilisearch.mjs
Maintenance checklist
- [ ] Monitor the
index searchCI job for failures. - [ ] Rotate the search-only API key periodically (cheap; just update the CI variable and redeploy).
- [ ] Upgrade the Meilisearch binary/container and the
@algolia/autocomplete-*/@meilisearch/*npm packages together, since their APIs move in lockstep. - [ ] Back up
/meili_dataand the master key off the server.