The official `@kirchdev/duxt-meilisearch` companion layer consumes the entire
[`duxt:search:records` snapshot](/reference/hooks). It keeps URL-derived IDs,
normalized plaintext, source, version and locale, including generated sections.
Meilisearch is optional; the built-in search needs no service.

::package-managers{command="add @kirchdev/duxt-meilisearch"}
::

```ts [nuxt.config.ts]
export default defineNuxtConfig({
  extends: ['@kirchdev/duxt', '@kirchdev/duxt-meilisearch'],
  duxtMeilisearch: {
    host: process.env.MEILISEARCH_HOST,
    index: 'docs',
    searchKey: process.env.MEILISEARCH_SEARCH_KEY,
    syncKey: process.env.MEILISEARCH_SYNC_KEY,
    taskKey: process.env.MEILISEARCH_TASK_KEY,
    sync: {
      enabled: process.env.MEILISEARCH_SYNC_ENABLED === 'true',
      deployment: process.env.DUXT_DEPLOYMENT === 'production'
        ? 'production' : 'preview'
    }
  }
})
```

Set both production opt-ins only in your production publication job. Possessing
`syncKey` alone enables no writes. Development, Nuxt prepare and recognized PR
or preview contexts skip synchronization; other CI systems must map preview
contexts to `deployment: 'preview'`. Cloudflare Pages also needs
`DUXT_PRODUCTION_BRANCH` to match `CF_PAGES_BRANCH`.

Use a dedicated `docs` index. Give the private build key `indexes.create`,
`indexes.get`, `indexes.delete`, `indexes.swap`, `settings.update`,
`documents.add`, and `tasks.get` for `docs` and `docs__duxt_*`.
The layer builds a unique staging index, waits for settings and document-import
tasks, swaps it atomically with `docs`, waits for successful publication, then
removes the prior snapshot. Later builds remove deleted pages, and empty builds
clear the index. Failed imports leave the previous live snapshot in place.
Uncertain swaps retain staging rather than deleting possibly published data;
cleanup failures are reported. Serialize publication jobs when build order
matters. The first sync creates an empty target after staging is complete.

The private key never enters browser runtime config. Browser integration is
separate: omit `searchKey` to retain built-in search while synchronizing. To
connect the existing dialog, create a separate key with **only** the `search`
action and **only** the configured `docs` index. The default search key covers
all indexes; do not use it. Never use a master or admin key in public config.
The provider rejects reuse of a known build key; opaque key strings do not reveal
permissions, so scoped provisioning is your responsibility. Browser-only use
needs no private build key. Allow your site's origin in the service's CORS policy.

Search keeps each artefact's selected/default version and the content-language
fallback chain. A translated page suppresses its original; a page lacking that
translation remains reachable in the original language. The companion reads a
cached page-path inventory through Content and excludes covered pages within
each source/version/locale scope before Meilisearch ranks and limits results.
The provider's filter-only `page` field holds the URL without its anchor;
hidden sections cannot consume the engine's 1,000-hit window. Synchronize once
before using browser search with an index created by an earlier provider build.
Service errors yield an empty result
and a console warning. HTTP requests time out after 30 seconds; build tasks have
a five-minute budget, configurable through `sync.taskTimeoutMs` (at most one hour).

This package has its own release version and changelog. It is a separate Nuxt
layer, with no SDK, credentials or provider configuration added to duxt.
The browser helper is exported at `@kirchdev/duxt-meilisearch/search`; explicit
build tooling may use `@kirchdev/duxt-meilisearch/sync` with the same opt-in.

The service guarantees come from Meilisearch's [atomic index swap](https://specs.meilisearch.dev/specifications/text/0191-swap-indexes-api.html)
and [asynchronous task API](https://www.meilisearch.com/docs/reference/api/async-task-management/get-task).
See [creating a scoped API key](https://www.meilisearch.com/docs/reference/api/keys/create-api-key)
for the separate browser credential.

Meilisearch 1.54.3 hides the global `indexSwap` task (`indexUid: null`) from
index-restricted keys, even when they cover both swapped indexes. With a scoped
write key, supply a separate private `taskKey` with only `actions: ['tasks.get']`
and `indexes: ['*']`. It is used only for task polling and never enters public
config. Without it, `syncKey` must itself be able to read global tasks. A hidden
404 fails publication confirmation and retains staging; it is never taken as
success. This behavior was verified against the real engine.