Typesense Search
Integrate Typesense Search with Fumadocs
This is a community-maintained integration.
Setup
Install Dependencies
npm install typesense typesense-fumadocs-adapterStart Typesense Server
You can either self-host the Typesense server or use their cloud service. Follow their getting started guide to set up your server and obtain the API key and server URL.
Sync Dataset
Export the search indexes by pre-rendering a static route.
import { source } from '@/lib/source';
import { findPath } from 'fumadocs-core/page-tree';
import type { DocumentRecord } from 'typesense-fumadocs-adapter';
export async function exportSearchIndexes() {
const results: DocumentRecord[] = [];
function isBreadcrumbItem(item: unknown): item is string {
return typeof item === 'string' && item.length > 0;
}
for (const page of source.getPages()) {
let breadcrumbs: string[] | undefined;
const pageTree = source.getPageTree(page.locale);
const path = findPath(
pageTree.children,
(node) => node.type === 'page' && node.url === page.url,
);
if (path) {
breadcrumbs = [];
path.pop();
if (isBreadcrumbItem(pageTree.name)) {
breadcrumbs.push(pageTree.name);
}
for (const segment of path) {
if (!isBreadcrumbItem(segment.name)) continue;
breadcrumbs.push(segment.name);
}
}
results.push({
_id: page.url,
structured: page.data.structuredData,
url: page.url,
title: page.data.title,
description: page.data.description,
breadcrumbs,
locale: page.locale,
});
}
return results;
}import { exportSearchIndexes } from '@/lib/export-search-indexes';
export const revalidate = false;
export async function GET() {
return Response.json(await exportSearchIndexes());
}Create a script to sync search indexes:
import * as fs from 'node:fs';
import { sync, DocumentRecord } from 'typesense-fumadocs-adapter';
import { Client } from 'typesense';
const filePath = '<see below>';
const content = fs.readFileSync(filePath);
const records = JSON.parse(content.toString()) as DocumentRecord[];
const client = new Client({
nodes: [{ url: 'YOUR_TYPESENSE_SERVER_URL' }],
apiKey: 'YOUR_TYPESENSE_API_KEY_WITH_WRITE_ACCESS',
connectionTimeoutSeconds: 60 * 15,
});
// update the collection settings and sync search indexes
void sync(client, {
typesenseCollectionName: 'YOUR_COLLECTION_NAME',
documents: records,
});filePath refers to the path of pre-rendered static.json, choose one according to your setup:
const filePath = '.next/server/app/static.json.body';Make sure to run the script after build:
{
"scripts": {
"build": "... && bun scripts/sync-content.ts"
}
}You can also integrate it with your CI/CD pipeline.
Search UI
To implement the search UI, you can either:
- Build your own search UI with the Typesense search client hook.
import { Client } from 'typesense'; import { useTypesenseSearch } from 'typesense-fumadocs-adapter/client'; const client = new Client({ nodes: [{ url: 'YOUR_TYPESENSE_SERVER_URL' }], apiKey: 'YOUR_TYPESENSE_SEARCH_ONLY_API_KEY', }); const { search, setSearch, query } = useTypesenseSearch({ typesenseCollectionName: 'YOUR_COLLECTION_NAME', client, });
Options
Tag Filter
To configure tag filtering, add a tag value to indexes.
import { source } from '@/lib/source';
import type { DocumentRecord } from 'typesense-fumadocs-adapter';
export async function exportSearchIndexes() {
const results: DocumentRecord[] = [];
for (const page of source.getPages()) {
results.push({
// other fields...
tag: '<your value>',
});
}
return results;
}And update your search client:
- Fumadocs UI: Enable Tag Filter on Search UI.
- Search Client: You can add the tag filter like:
import { useTypesenseSearch } from 'typesense-fumadocs-adapter/client'; const { search, setSearch, query } = useTypesenseSearch({ tag: '<your tag value>', // ... });
Internationalization (i18n)
To support internationalization, make sure to add a locale value to each document.
import { source } from '@/lib/source';
import { findPath } from 'fumadocs-core/page-tree';
import type { DocumentRecord } from 'typesense-fumadocs-adapter';
export async function exportSearchIndexes() {
const results: DocumentRecord[] = [];
for (const page of source.getPages()) {
results.push({
// other fields...
locale: page.locale,
});
}
return results;
}And update your search client:
import { useI18n } from 'fumadocs-ui/contexts/i18n';
import { useTypesenseSearch } from 'typesense-fumadocs-adapter/client';
const { locale } = useI18n();
const { search, setSearch, query } = useTypesenseSearch({
locale,
// other fields...
});Under the hood, Typesense will create separate collections for each locale. The collection name is appended with the _{locale} suffix (e.g., YOUR_COLLECTION_NAME_en, YOUR_COLLECTION_NAME_fr).
How is this guide?
Last updated on
