Fumadocs

Built-in Search

Built-in document search of Fumadocs

Fumadocs supports document search with Orama, It is the default but also the recommended option since it can be self-hosted and totally free.

Search Server

You can create the search route handler from the source object, or search indexes.

Special Languages

If your language is not on the Orama Supported Languages list, you have to configure them manually:

app/api/search/route.ts
import { source } from '@/lib/source';
import { createFromSource } from 'fumadocs-core/search/server';
import { createTokenizer } from '@orama/tokenizers/mandarin';

export const { GET } = createFromSource(source, {
  localeMap: {
    // you can customise search configs for specific locales, like:
    // [locale]: Orama options

    cn: {
      components: {
        tokenizer: createTokenizer(),
      },
      search: {
        threshold: 0,
        tolerance: 0,
      },
    },
  },
});
app/api/search/route.ts
import { source } from '@/lib/source';
import { createFromSource } from 'fumadocs-core/search/server';
import { createTokenizer } from '@orama/tokenizers/mandarin';

// example for Mandarin
export const { GET } = createFromSource(source, {
  components: {
    tokenizer: createTokenizer(),
  },
  search: {
    threshold: 0,
    tolerance: 0,
  },
});

See Orama Docs for more details.

Search Client

You can search documents using:

  • Fumadocs UI: Supported out-of-the-box, see Search UI for details.
  • Search Client:
import {  } from 'fumadocs-core/search/client';

const  = ({
  : 'fetch',
});
PropTypeDefault
locale?
string
-
tag?
string
-
api?
string
'/api/search'
type
"fetch"
-

Configurations

Tag Filter

Support filtering results 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, {
  buildIndex(page) {
    return {
      title: page.data.title,
      description: page.data.description,
      url: page.url,
      id: page.url,
      structuredData: page.data.structuredData,
      // use your desired value, like page.slugs[0]
      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',
  tag: '<value>',
});

Internationalization

lib/source.ts
import { i18n } from '@/lib/i18n';
import { loader } from 'fumadocs-core/source';

// You only need i18n option on source object.
export const source = loader({
  i18n,
});
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(({ language, pages }) =>
    pages.map((page) => ({
      title: page.data.title,
      description: page.data.description,
      structuredData: page.data.structuredData,
      id: page.url,
      url: page.url,
      locale: language,
    })),
  ),
});

and update your search clients:

  • Fumadocs UI: No changes needed, Fumadocs UI handles this when you have i18n configured correctly.
  • Search Client: Add locale to the search client, this will only allow pages with specified locale to be searchable by the user.
import { useDocsSearch } from 'fumadocs-core/search/client';

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

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: use static client on Search UI.

  • Search Client: use static instead of fetch.

    import { useDocsSearch } from 'fumadocs-core/search/client';
    
    const client = useDocsSearch({
      type: 'static',
    });
    PropTypeDefault
    locale?
    string
    -
    tag?
    string
    -
    initOrama?
    ((locale?: string | undefined) => AnyOrama | Promise<AnyOrama>)
    -
    from?
    string
    '/api/search'
    type
    "static"
    -

Be Careful

Static Search requires clients to download the exported search indexes. For large docs sites, it can be expensive.

You should use cloud solutions like Orama Cloud or Algolia for these cases.

Headless

You can host the search server on other backend such as Express and Elysia.

import { initAdvancedSearch } from 'fumadocs-core/search/server';

const server = initAdvancedSearch({
  // you still have to pass indexes
});

server.search('query', {
  // you can specify `locale` and `tag` here
});

How is this guide?

Last updated on