Fumadocs

Built-in Search

Built-in document search of Fumadocs

Fumadocs supports searching document based on Orama.

As the built-in search of Fumadocs, It is the default but also recommended option since it's easier to setup and totally free.

If you're using a CMS, you may use the API provided by the CMS instead.

Setup

From Source

Create a route handler from Source API.

app/api/search/route.ts
import { source } from '@/lib/source';
import { createFromSource } from 'fumadocs-core/search/server';
 
export const { GET } = createFromSource(source);

From Search Indexes

Pass search indexes to the function, see type information for required properties.

For content indexing, each index needs a structuredData field. It can be processed from Markdown/MDX document, or provided by your content source (e.g. Fumadocs MDX).

You can extract structured data using the Structure remark plugin.

app/api/search/route.ts
import { source } from '@/lib/source';
import { createSearchAPI } from 'fumadocs-core/search/server';
 
export const { GET } = createSearchAPI('advanced', {
  indexes: source.getPages().map((page) => ({
    title: page.data.title,
    description: page.data.description,
    url: page.url,
    id: page.url,
    structuredData: page.data.structuredData,
  })),
});

Client

You can query it using:

  • Fumadocs UI: The built-in Search UI supports it out-of-the-box.

  • Search Client:

    import {  } from 'fumadocs-core/search/client';
     
    const  = ({
      : 'fetch',
    });

Tag Filter

Support filtering by tag, it's useful for implementing multi-docs similar to this documentation.

app/api/search/route.ts
import { source } from '@/lib/source';
import { createFromSource } from 'fumadocs-core/search/server';
 
export const { GET } = createFromSource(source, (page) => ({
  title: page.data.title,
  description: page.data.description,
  url: page.url,
  id: page.url,
  structuredData: page.data.structuredData,
  tag: '<value>',
}));

and update your search client:

  • Fumadocs UI: Configure Tag Filter on Search UI.

  • Search Client: pass a tag to the hook.

    import { useDocsSearch } from 'fumadocs-core/search/client';
     
    // Pass `tag` in your custom search dialog
    const client = useDocsSearch(
      {
        type: 'fetch',
      },
      undefined, // locale code, can be `undefined`
      'tag',
    );

Index by Content

Index with the raw content of document (unrecommended).

app/api/search/route.ts
import { allDocs } from 'content-collections';
import { createSearchAPI } from 'fumadocs-core/search/server';
 
export const { GET } = createSearchAPI('simple', {
  indexes: allDocs.map((docs) => ({
    title: docs.title,
    content: docs.content, // Raw Content
    url: docs.url,
  })),
});

Internationalization

Update the route handler (From Source)

Enable i18n from source object (in loader function).

Update the route handler (From Search Indexes)

Use createI18nSearchAPI for i18n functionality.

app/api/search/route.ts
import { source } from '@/lib/source';
import { createI18nSearchAPI } from 'fumadocs-core/search/server';
import { i18n } from '@/lib/i18n';
 
export const { GET } = createI18nSearchAPI('advanced', {
  i18n,
  indexes: source.getLanguages().flatMap((entry) =>
    entry.pages.map((page) => ({
      title: page.data.title,
      description: page.data.description,
      structuredData: page.data.structuredData,
      id: page.url,
      url: page.url,
      locale: entry.language,
    })),
  ),
});

Update search client

For Fumadocs UI

You can ignore this, Fumadocs UI handles this when you have i18n configured correctly.

Add locale to the search client, this will only allow pages with specified locale to be searchable by the user.

const { search, setSearch, query } = useDocsSearch(
  {
    type: 'fetch',
  },
  locale,
);

Special Languages

Orama requires extra configurations for Chinese, Japanese, and some other languages.

app/api/search/route.ts
import { source } from '@/lib/source';
import { createFromSource } from 'fumadocs-core/search/server';
// @ts-expect-error -- untyped
import { createTokenizer } from '@orama/tokenizers/mandarin';
import { stopwords } from '@orama/stopwords/mandarin';
 
export const { GET, search } = createFromSource(source, undefined, {
  localeMap: {
    // the prop name should be its locale code in your i18n config, (e.g. `cn`)
    cn: {
      // options for the language
      tokenizer: await createTokenizer({
        stopWords: stopwords,
      }),
      search: {
        threshold: 0,
        tolerance: 0,
      },
    },
  },
});

See Orama Docs for more details.

Static Export

To work with Next.js static export, use staticGET from search server.

app/api/search/route.ts
import { source } from '@/lib/source';
import { createFromSource } from 'fumadocs-core/search/server';
 
// it should be cached forever
export const revalidate = false;
 
export const { staticGET: GET } = createFromSource(source);

staticGET is also available on createSearchAPI.

and update your search clients:

  • Fumadocs UI:

    You can pass search options to Root Provider. See options.

  • Search Client:

    On your search client, use static instead of fetch.

    import { useDocsSearch } from 'fumadocs-core/search/client';
     
    const client = useDocsSearch({
      type: 'static',
    });

Be Careful

Static Search requires clients to download the exported search indexes. For large docs sites, its size can be really big.

Especially with i18n (e.g. Chinese tokenizers), the bundle size of tokenizers can exceed ~500MB. You should use 3rd party solutions like Algolia for these cases.

Search Client

useDocsSearch is a hook to search documents using the built-in search clients.

Client

Choose the client to use.

import { useDocsSearch } from 'fumadocs-core/search/client';
 
const client = useDocsSearch({
  type: 'static',
  // you can pass options for the client you specified
});
TypeDescription
fetch (default)send HTTP request to a search server with fetch
staticLoad search map on client-side and query them
algoliaUse Algolia Search (See Algolia

Return Type

PropTypeDescription
queryQueryQuery status and results
searchstringSearching text (not debounced)
setSearch(v: string) => voidSet searching text

Response Data

Type
emptyIf the searching text is empty or blank
SortedResult[]Array of matching pages, headings and contents.

Custom Algorithm

You can port your own search algorithm by returning a list of SortedResult from your custom /api/search/route.ts route handler (API Endpoint). You can also integrate it with Fumadocs UI.

PropTypeDefault
id
string
-
url
string
-
type
"page" | "heading" | "text"
-
content
string
-

Headless

You can host the search server on Express or Elysia, without Next.js.

import { initAdvancedSearch } from 'fumadocs-core/search/server';
 
const server = initAdvancedSearch({
  // options
});
 
server.search('query', {
  // you can specify `locale` and `tag` here
});
Edit on GitHub

Last updated on

On this page