Mixedbread
Integrate Mixedbread Search with Fumadocs
Mixedbread provides semantic search, users can search with natural language and find content by meaning rather than keywords.
Setup
Install dependencies:
npm install @mixedbread/sdkAPI Key
Sign up at Mixedbread, create an API key and a store for your docs, and store them in .env.local:
MXBAI_API_KEY="your API key"
MIXEDBREAD_STORE_ID="your store ID"Searching runs on your server, the API key is never exposed to the browser.
Sync Documents
Pre-render a static.json route to export the search documents on build, and sync them to your store with a script after each build:
import { source } from '@/lib/source';
import { toDocuments } from 'fumadocs-core/search/mixedbread';
export const revalidate = false;
export async function GET() {
return Response.json(await toDocuments(source));
}
import * as fs from 'node:fs/promises';
import { type DocumentRecord, sync } from 'fumadocs-core/search/mixedbread';
import Mixedbread from '@mixedbread/sdk';
// Next.js writes it under `route-cache` when built with an adapter, like on Vercel
const [file] = await Array.fromAsync(fs.glob('.next/server/{app,route-cache/*/*/$}/static.json.body'));
const records = JSON.parse(await fs.readFile(file, 'utf-8')) as DocumentRecord[];
await sync(new Mixedbread(), {
storeIdentifier: process.env.MIXEDBREAD_STORE_ID!,
documents: records,
});
console.log(`search updated: ${records.length} records`);
{
"scripts": {
"build": "next build && node --env-file-if-exists=.env.local scripts/sync-content.ts"
}
}sync() uploads a file for each page, and skips the pages unchanged since the last sync. Files of removed pages are deleted, use a store dedicated to your docs. The build script syncs on every build, set the same variables on your deployment platform.
Search API
Create a route to search your store:
import { createMixedbreadSearchAPI } from 'fumadocs-core/search/mixedbread';
import Mixedbread from '@mixedbread/sdk';
export const { GET } = createMixedbreadSearchAPI({
client: new Mixedbread(), // reads `MXBAI_API_KEY`
storeIdentifier: process.env.MIXEDBREAD_STORE_ID!,
});Search UI
You can consider different options for implementing the UI:
-
Using Fumadocs UI search dialog.
-
Build your own using the built-in search client hook:
import { } from 'fumadocs-core/search/client'; const { , , , } = ({ : '/api/search', // every search is a request to Mixedbread : 300, });
Options
Tag Filter
To configure tag filtering, pass a tag to toDocuments() in the static.json route:
toDocuments(source, { tag: (page) => page.slugs[0] });And update your search client:
-
Fumadocs UI: Enable Tag Filter on Search UI.
-
Search Client: pass a tag to
useFetchSearch().import { useFetchSearch } from 'fumadocs-core/search/client'; const { search, onSearchChange, isLoading, data } = useFetchSearch({ tag: '<your tag value>', // ... });
Internationalization
Documents include the locale of their pages, pass locale to search in a language:
import { useI18n } from 'fumadocs-ui/contexts/i18n';
import { useFetchSearch } from 'fumadocs-core/search/client';
const { locale } = useI18n();
const { search, onSearchChange, isLoading, data } = useFetchSearch({
locale,
// ...
});Under the Hood
Each page is a file keyed by its URL, with the page title, URL, breadcrumbs, tags and locale as metadata. Its chunks are the title, headings and paragraphs of the page, every result is a chunk:
| Chunk Metadata | Description |
|---|---|
kind | page, heading or text |
heading | ID of the heading it belongs to (optional) |
table | Table of a table row (optional) |
How is this guide?
Last updated on
