Meilisearch-backed search engine for Dune CMS sites.
Drop-in replacement for Dune's built-in in-memory search engine. Implements the
same SearchEngine interface — no changes to templates or route handlers. Swap
in when you need:
- Typo tolerance and fuzzy matching
- Language-aware stemming and compound word handling
- Synonym expansion and stop words
- Persistent index across restarts
- Sub-millisecond queries at large corpus sizes (thousands of pages + PDFs)
Requires a running Meilisearch instance (v1.0+).
Add to your site's deno.json:
{
"imports": {
"@dune/plugin-meilisearch": "jsr:@dune/plugin-meilisearch",
"@dune/plugin-meilisearch/engine": "jsr:@dune/plugin-meilisearch/engine"
}
}Pass the engine to Dune's bootstrap where a SearchEngine is expected:
import { createMeilisearchEngine } from "@dune/plugin-meilisearch/engine";
const search = createMeilisearchEngine({
url: Deno.env.get("MEILI_URL") ?? "http://127.0.0.1:7700",
apiKey: Deno.env.get("MEILI_API_KEY"),
index: "content", // optional, default: "content"
}, pages); // your PageIndex[] from DuneOn build(), the engine:
- Creates the Meilisearch index if it does not exist
- Applies index settings (searchable/filterable attributes, ranking rules, typo tolerance)
- Syncs all published pages as documents (delete-all then batch-put)
On rebuild(pages), the full sync runs again with the new page list — used
automatically by dune dev on content changes.
All options except url are optional.
createMeilisearchEngine({
url: "http://127.0.0.1:7700",
apiKey: "your-api-key",
index: "content",
excerptLength: 160, // characters in returned excerpts
settings: {
// Override any Meilisearch index settings
stopWords: ["der", "die", "das", "und", "in"],
synonyms: {
"eu": ["europäische union", "europa"],
},
rankingRules: [
"words",
"typo",
"proximity",
"attribute",
"sort",
"exactness",
],
typoTolerance: {
enabled: true,
minWordSizeForTypos: { oneTypo: 5, twoTypos: 9 },
},
},
}, pages);Applied on every build() unless overridden:
| Setting | Default |
|---|---|
searchableAttributes |
["title", "body", "tags"] |
filterableAttributes |
["template", "language", "tags", "subtype"] |
sortableAttributes |
["date"] |
rankingRules |
sort → words → typo → proximity → attribute → exactness |
typoTolerance |
enabled, oneTypo ≥ 5 chars, twoTypos ≥ 9 chars |
If your site uses system.search.facets in site.yaml (Dune's
PageIndex.extra), those fields are automatically spread onto Meilisearch
documents and available for filtering:
# site.yaml
system:
search:
facets:
- field: subtypeWith this config, subtype values (kurzinfo, buchbesprechung, etc.) are
indexed and filterable via the standard Dune search API.
Each Dune page is indexed as:
{
id: string; // stable ID derived from route
route: string; // e.g. "/articles/my-article"
title: string;
body: string; // plain text body
date: string | null;
template: string;
language: string;
tags: string[]; // all taxonomy values flattened across vocabs
// ...extra fields from PageIndex.extra
}The quickest way to get a local instance:
# Docker
docker run -d -p 7700:7700 getmeili/meilisearch:latest
# Or download the binary
curl -L https://install.meilisearch.com | sh
./meilisearchFor production, run Meilisearch as a systemd service on the same server as Dune
and bind it to 127.0.0.1 only — no public exposure needed.
With a recent @dune/core (the release that adds SearchConfigError), an
unreachable Meilisearch does not stop your site from starting:
- Dune logs
search.engine.failedonce, with the cause, and keeps serving content. - Search is answered by Dune's built-in engine until Meilisearch is back. Ranking differs, but search keeps working.
- Dune retries in the background (5 s, doubling up to 5 min) and switches back
on its own.
/healthshows"degraded"in the meantime.
Some failures cannot be fixed by retrying, so they stop startup with a message
instead: a rejected API key (401), a key without the needed permissions
(403), and invalid index or settings (400). Connection errors, timeouts
and 5xx responses are treated as temporary.
Older cores stop startup on any Meilisearch failure, as before.
MIT