# Fumadocs (Framework Mode, Astro): 手動インストール
URL: /ja/docs/astro/manual-installation
Source: https://raw.githubusercontent.com/fuma-nama/fumadocs/refs/heads/main/apps/docs/content/docs/(framework)/[framework]/manual-installation.ja.mdx

既存のAstroプロジェクトにFumadocsを追加します。

[Fumadocs CLI](/docs/cli#init) を使って自動でセットアップすることもできます。

```npm
npx @fumadocs/cli init
```

## 前提条件 [#prerequisites]

先に進む前に、以下が設定済みであることを確認してください。

* Node.js 22.23.4 以降(Astro の要件)
* Tailwind CSS 4

このガイドでは、コンテンツソースとして Astro の content collections を使用します。`@fumadocs/astro` は content collections を Fumadocs でコンパイルし、その MDX を React アイランドとしてレンダリングします。

## はじめに [#getting-started]

パッケージをインストールします。

```npm
npm i fumadocs-core fumadocs-ui@npm:@fumadocs/base-ui @fumadocs/astro @astrojs/react react react-dom @types/mdx
```

テンプレートと同様に、このコマンドでは `fumadocs-ui` を、Fumadocs UI の [Base UI](/docs/ui/component-library) 版である `@fumadocs/base-ui` のエイリアスとしてインストールします。Radix UI を使う場合は、エイリアスなしで `fumadocs-ui` をインストールしてください。

### スタイル [#styles]

Tailwind CSS ファイルに以下を追加します。

```css
@import 'tailwindcss';
/* [!code ++:2] */
@import 'fumadocs-ui/css/neutral.css';
@import 'fumadocs-ui/css/preset.css';
```

### インテグレーション [#integration]

Astro の設定に Fumadocs インテグレーションを追加します。

```js title="astro.config.mjs"
import { defineConfig } from 'astro/config';
import tailwindcss from '@tailwindcss/vite';
import fumadocs from '@fumadocs/astro';

export default defineConfig({
  integrations: [fumadocs({ components: './src/components/mdx.tsx' })],
  vite: {
    plugins: [tailwindcss()],
  },
});
```

### コンテンツコレクション [#content-collections]

コンテンツコレクションを定義します。

```ts title="src/content.config.ts"
import { defineCollection } from 'astro:content';
import { metaLoader, pageLoader } from '@fumadocs/astro/loaders';

export const collections = {
  docs: defineCollection({ loader: pageLoader() }),
  meta: defineCollection({ loader: metaLoader() }),
};
```

コレクションから Fumadocs のソースを作成します。

```ts title="src/lib/source.ts"
import { getCollection } from 'astro:content';
import { loader } from 'fumadocs-core/source';
import { toFumadocsSource } from '@fumadocs/astro/source';

export const source = loader({
  source: toFumadocsSource(await getCollection('docs'), await getCollection('meta')),
  baseUrl: '/docs',
});
```

1つの `loader()` で他のソースと組み合わせる場合は、`toFumadocsSource()` の第3引数に `{ baseDir }` を渡すと、そのファイルを特定のディレクトリ配下に配置できます。

### ルート [#routes]

次のファイルを作成します。

```tsx tab="src/components/mdx.tsx"
import defaultMdxComponents from 'fumadocs-ui/mdx';
import { DocsBody } from 'fumadocs-ui/layouts/docs/page';
import type { MDXComponents } from 'mdx/types';

export function getMDXComponents(components?: MDXComponents) {
  return {
    ...defaultMdxComponents,
    ...components,
  } satisfies MDXComponents;
}

// for the Fumadocs integration, `wrapper` gives the content its prose styles
export default getMDXComponents({ wrapper: DocsBody });
```

```tsx tab="src/components/provider.tsx"
import { navigate } from 'astro:transitions/client';
import type { ReactNode } from 'react';
import { RootProvider } from 'fumadocs-ui/provider/astro';
import SearchDialog from '@/components/search';

export interface ProviderProps {
  /** `Astro.url.pathname` */
  pathname: string;
  /** `Astro.params` */
  params: Record<string, string | undefined>;
  children?: ReactNode;
}

export function Provider({ pathname, params, children }: ProviderProps) {
  return (
    <RootProvider pathname={pathname} params={params} navigate={navigate} search={{ SearchDialog }}>
      {children}
    </RootProvider>
  );
}
```

```tsx tab="src/components/search.tsx"
import {
  SearchDialog,
  SearchDialogClose,
  SearchDialogContent,
  SearchDialogHeader,
  SearchDialogIcon,
  SearchDialogInput,
  SearchDialogList,
  SearchDialogOverlay,
  type SharedProps,
} from 'fumadocs-ui/components/dialog/search';
import { useStaticSearch } from 'fumadocs-core/search/client';

export default function DefaultSearchDialog(props: SharedProps) {
  const search = useStaticSearch();

  return (
    <SearchDialog {...search} {...props}>
      <SearchDialogOverlay />
      <SearchDialogContent>
        <SearchDialogHeader>
          <SearchDialogIcon />
          <SearchDialogInput />
          <SearchDialogClose />
        </SearchDialogHeader>
        <SearchDialogList />
      </SearchDialogContent>
    </SearchDialog>
  );
}
```

```tsx tab="src/components/docs.tsx"
import { type SerializedPageTree, useFumadocsLoader } from 'fumadocs-core/source/client';
import type { TOCItemType } from 'fumadocs-core/toc';
import { DocsLayout } from 'fumadocs-ui/layouts/docs';
import { DocsDescription, DocsPage, DocsTitle } from 'fumadocs-ui/layouts/docs/page';
import { Provider, type ProviderProps } from '@/components/provider';
import { baseOptions } from '@/lib/layout.shared';

export interface DocsProps extends ProviderProps {
  tree: SerializedPageTree;
  title: string;
  description?: string;
  toc: TOCItemType[];
}

/**
 * The React island of docs pages, the content is passed as children.
 */
export function Docs({ tree, title, description, toc, children, ...props }: DocsProps) {
  const data = useFumadocsLoader({ tree });

  return (
    <Provider {...props}>
      <DocsLayout {...baseOptions()} tree={data.tree}>
        <DocsPage toc={toc}>
          <DocsTitle>{title}</DocsTitle>
          <DocsDescription>{description}</DocsDescription>
          {children}
        </DocsPage>
      </DocsLayout>
    </Provider>
  );
}
```

```tsx tab="src/lib/layout.shared.tsx"
import type { BaseLayoutProps } from 'fumadocs-ui/layouts/shared';

export function baseOptions(): BaseLayoutProps {
  return {
    nav: {
      title: 'Astro',
    },
  };
}
```

```astro tab="src/layouts/Layout.astro"
---
import { ClientRouter } from 'astro:transitions';
import '@/styles/global.css';

interface Props {
  title: string;
  description?: string;
}

const { title, description } = Astro.props;
---

<html lang="en" transition:animate="none">
  <head>
    <meta charset="utf-8" />
    <meta name="viewport" content="width=device-width, initial-scale=1" />
    <title>{title}</title>
    {description && <meta name="description" content={description} />}
    <ClientRouter />
  </head>
  <body class="flex flex-col min-h-screen">
    <slot />
  </body>
</html>
```

```astro tab="src/pages/docs/[...slug].astro"
---
import type { GetStaticPaths, InferGetStaticPropsType } from 'astro';
import Layout from '@/layouts/Layout.astro';
import { Docs } from '@/components/docs';
import { source } from '@/lib/source';

export const prerender = true;

export const getStaticPaths = (() => {
  return source.getPages().map((page) => ({
    params: { slug: decodeURI(page.slugs.join('/')) || undefined },
    props: { page },
  }));
}) satisfies GetStaticPaths;

type Props = InferGetStaticPropsType<typeof getStaticPaths>;

const { page } = Astro.props;
const { body: Content, toc } = await page.data.load();
const pageTree = await source.serializePageTree(source.getPageTree());
---

<Layout title={page.data.title} description={page.data.description}>
  <Docs
    client:load
    transition:persist="docs"
    pathname={Astro.url.pathname}
    params={Astro.params}
    tree={pageTree}
    title={page.data.title}
    description={page.data.description}
    toc={toc}
  >
    <Content />
  </Docs>
</Layout>
```

```ts tab="src/pages/api/search.ts"
import type { APIRoute } from 'astro';
import { createFromSource } from 'fumadocs-core/search/server';
import { source } from '@/lib/source';

const server = createFromSource(source, {
  // https://docs.orama.com/docs/orama-js/supported-languages
  language: 'english',
});

export const GET: APIRoute = () => server.staticGET();
```

検索ルートでは ZBSearch を使用しています。設定方法については[ドキュメント検索](/docs/headless/search)を参照してください。

### 完了 [#finish]

開発サーバーを起動し、`content/docs` にドキュメントを作成してください。

```mdx title="content/docs/index.mdx"
---
title: Hello World
---

## Introduction

I love Fumadocs.
```