コンテンツへスキップ
Fumadocs

Meilisearch

Meilisearch を Fumadocs に統合する

Meilisearch は、タイプミス許容とフィルタリング機能を備えたオープンソースの検索エンジンです。セルフホストすることも、Meilisearch Cloud を利用することもできます。

セットアップ

依存関係をインストールします。

npm install meilisearch

API キー

セルフホストするか Meilisearch Cloud を利用して Meilisearch インスタンスを起動し、その認証情報を .env.local に保存します(NEXT_PUBLIC_ の部分は、Vite ベースのフレームワークでは VITE_、Astro では PUBLIC_ に置き換えてください)。

NEXT_PUBLIC_MEILISEARCH_HOST="URL of your instance"
NEXT_PUBLIC_MEILISEARCH_SEARCH_KEY="search API key"
MEILISEARCH_ADMIN_KEY="Default Admin API Key"

検索キーはブラウザに公開されるため、docs インデックスの検索のみが可能なキーを作成してください。管理キーは外部に公開しないでください。

ドキュメントの同期

static.json ルートを事前レンダリングしてビルド時に検索ドキュメントをエクスポートし、ビルドのたびにスクリプトで Meilisearch へ同期します。

app/static.json/route.ts
import { source } from '@/lib/source';
import { toDocuments } from 'fumadocs-core/search/meilisearch';

export const revalidate = false;

export async function GET() {
  return Response.json(await toDocuments(source));
}
scripts/sync-content.ts
import * as fs from 'node:fs/promises';
import { type DocumentRecord, sync } from 'fumadocs-core/search/meilisearch';
import { Meilisearch } from 'meilisearch';

// 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[];

const client = new Meilisearch({
  host: process.env.NEXT_PUBLIC_MEILISEARCH_HOST!,
  apiKey: process.env.MEILISEARCH_ADMIN_KEY!,
});

await sync(client, {
  indexName: 'docs',
  documents: records,
});

console.log(`search updated: ${records.length} records`);
package.json
{
  "scripts": {
    "build": "next build && node --env-file-if-exists=.env.local scripts/sync-content.ts"
  }
}

sync() はインデックスを設定してドキュメントを置き換えます。新しいドキュメントのインデックス登録が完了するまでは、古いドキュメントを引き続き検索できます。build スクリプトはビルドのたびに同期を行うため、デプロイ先のプラットフォームにも同じ環境変数を設定してください。

検索 UI

UI の実装方法には、いくつかの選択肢があります。

  • Fumadocs UI の検索ダイアログを使用する。

  • 組み込みの検索クライアントフックを使用して独自に構築する。

    import {  } from 'meilisearch';
    import {  } from 'fumadocs-core/search/client';
    
    const  = new ({
      : 'host url',
      : 'search api key',
    });
    
    const { , , ,  } = ({
      ,
      : 'docs',
    });

    プロパティ

    型

  • Meilisearch の公式クライアントを直接使用する。

オプション

タグフィルター

タグフィルターを設定するには、static.json ルートで toDocuments() に tag を渡します。

toDocuments(source, { tag: (page) => page.slugs[0] });

あわせて、検索クライアントも更新してください。

  • Fumadocs UI: 検索 UI でタグフィルターを有効にします。

  • 検索クライアント: useMeilisearch() にタグを渡します。

    import { useMeilisearch } from 'fumadocs-core/search/client';
    
    const { search, onSearchChange, isLoading, data } = useMeilisearch({
      tag: '<your tag value>', 
      // ...
    });

国際化

ドキュメントには各ページのロケールが含まれているため、locale を渡すと特定の言語で検索できます。

import { useI18n } from 'fumadocs-ui/contexts/i18n';
import { useMeilisearch } from 'fumadocs-core/search/client';

const { locale } = useI18n();
const { search, onSearchChange, isLoading, data } = useMeilisearch({
  locale, 
  // ...
});

インデックス設定

sync() が設定するのは searchableAttributes と filterableAttributes のみで、シノニムやタイポ許容などのその他の設定はそのまま保持されます。これらの設定は Meilisearch のダッシュボードまたは meilisearch クライアントから調整できます。

内部の仕組み

各見出しと段落はそれぞれ1つのドキュメントとして扱われ、ページタイトル、URL、パンくずリストを持ちます。検索対象は content のみで、検索クライアントは結果をページ単位でグループ化します。

属性説明
typepage、heading または text
contentページタイトル、見出し、または段落
heading所属する見出しの ID (任意)
tag結果を絞り込むためのタグ (任意)
localeページのロケール (任意)

How is this guide?

最終更新日