# Fumadocs (Framework Mode, Astro): ブログ
URL: /ja/docs/astro/integrations/blog
Source: https://raw.githubusercontent.com/fuma-nama/fumadocs/refs/heads/main/apps/docs/content/docs/(framework)/[framework]/integrations/blog.ja.mdx

アプリにブログを追加します。

## インストール [#installation]

```npm
npx @fumadocs/cli@latest add blog
```

コンポーネントは `components/blog` に、投稿用のユーティリティは `lib/blog.ts` にインストールされます。

`blogPageSchema` を使って投稿用のコレクションを定義し、著者を登録します。

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

export default defineConfig({
  integrations: [
    fumadocs({
      components: './src/components/mdx.tsx',
      // [!code ++:4]
      collections: {
        docs: { dir: 'content/docs' },
        blog: { dir: 'content/blog' },
      },
    }),
  ],
});
```

```ts tab="src/content.config.ts"
import { defineCollection } from 'astro:content';
import { metaLoader, pageLoader } from '@fumadocs/astro/loaders';
import { blogPageSchema } from '@/lib/blog'; // [!code ++]

export const collections = {
  docs: defineCollection({ loader: pageLoader() }),
  meta: defineCollection({ loader: metaLoader() }),
  blog: defineCollection({ loader: pageLoader('blog'), schema: blogPageSchema }), // [!code ++]
};
```

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

export const blogSource = loader({
  baseUrl: '/blog',
  source: toFumadocsSource(await getCollection('blog')),
});

export const blogAuthors: Record<string, BlogAuthor> = {
  fuma: {
    name: 'Fuma Nama',
    title: 'Maintainer',
    url: 'https://fuma-nama.dev',
    image: 'https://avatars.githubusercontent.com/u/76240755',
  },
};
```

投稿は `content/blog` 配下に作成します。

```mdx title="content/blog/hello-world.mdx"
---
title: Hello World
description: My first post.
date: 2026-10-12
tags: [Announcement]
authors: [fuma]
image: /blog/hello-world.png
---
```

| フィールド     | 説明                               |
| --------- | -------------------------------- |
| `date`    | 公開日です。投稿はこの日付順に並べ替えられます          |
| `tags`    | タグごとに `/blog/tags` 配下にページが作成されます |
| `authors` | 著者のキーです。未登録のキーは著者名として扱われます       |
| `image`   | カバー画像の URL です。投稿のカードに表示されます      |

## ページ [#pages]

`getPosts()` は、ページの著者とタグを解決したうえで、新しい順に返します。返されたページは、[Home Layout](/docs/ui/layouts/home-layout) 配下のルートでレンダリングしてください。

### 投稿 [#posts]

```astro tab="src/pages/blog/index.astro"
---
import Layout from '@/layouts/Layout.astro';
import { BlogPosts } from '@/components/blog-posts';
import { blogAuthors, blogSource } from '@/lib/source';
import { getPosts } from '@/lib/blog';
---

<Layout title="Blog">
  <BlogPosts
    client:load
    pathname={Astro.url.pathname}
    params={Astro.params}
    posts={getPosts(blogSource.getPages(), blogAuthors)}
  />
</Layout>
```

```tsx tab="src/components/blog-posts.tsx"
import { HomeLayout } from 'fumadocs-ui/layouts/home';
import { baseOptions } from '@/lib/layout.shared';
import { Provider, type ProviderProps } from '@/components/provider';
import type { BlogPost } from '@/lib/blog';
import { BlogCards } from '@/components/blog';

export function BlogPosts({ posts, ...props }: ProviderProps & { posts: BlogPost[] }) {
  return (
    <Provider {...props}>
      <HomeLayout {...baseOptions()}>
        <div className="mx-auto w-full max-w-(--fd-layout-width) px-4 py-12">
          <BlogCards posts={posts} />
        </div>
      </HomeLayout>
    </Provider>
  );
}
```

### 投稿 [#post]

`findPost()` は、投稿とその前後の投稿 (新しい投稿と古い投稿) をまとめて取得します。`toc` を指定すると、目次と共有ボタンを含むフローティングパネルが表示されます。

```astro tab="src/pages/blog/[slug].astro"
---
import type { GetStaticPaths, InferGetStaticPropsType } from 'astro';
import Layout from '@/layouts/Layout.astro';
import { BlogPostPage } from '@/components/blog-post';
import { blogAuthors, blogSource } from '@/lib/source';
import { findPost, getPosts } from '@/lib/blog';

export const getStaticPaths = (() => {
  const posts = getPosts(blogSource.getPages(), blogAuthors);

  return blogSource.getPages().map((page) => ({
    params: { slug: decodeURI(page.slugs[0]) },
    props: { page, ...findPost(posts, page.url)! },
  }));
}) satisfies GetStaticPaths;

type Props = InferGetStaticPropsType<typeof getStaticPaths>;

const { page, post, newer, older } = Astro.props;
const { body: Content, toc } = await page.data.load();
---

<Layout title={post.title} description={post.description}>
  <BlogPostPage
    client:load
    pathname={Astro.url.pathname}
    params={Astro.params}
    post={post}
    newer={newer}
    older={older}
    toc={toc}
  >
    <Content />
  </BlogPostPage>
</Layout>
```

```tsx tab="src/components/blog-post.tsx"
import type { TOCItemType } from 'fumadocs-core/toc';
import { HomeLayout } from 'fumadocs-ui/layouts/home';
import { baseOptions } from '@/lib/layout.shared';
import { Provider, type ProviderProps } from '@/components/provider';
import type { BlogPost } from '@/lib/blog';
import {
  BlogAuthors,
  BlogBackLink,
  BlogBody,
  BlogDate,
  BlogDescription,
  BlogHeader,
  BlogPage,
  BlogPostNav,
  BlogTags,
  BlogTitle,
} from '@/components/blog';

export interface BlogPostPageProps extends ProviderProps {
  post: BlogPost;
  newer?: BlogPost;
  older?: BlogPost;
  toc: TOCItemType[];
}

/** コンテンツは children として渡されます */
export function BlogPostPage({ post, newer, older, toc, children, ...props }: BlogPostPageProps) {
  return (
    <Provider {...props}>
      <HomeLayout {...baseOptions()}>
        <BlogPage toc={toc}>
          <BlogHeader>
            <BlogBackLink href="/blog" />
            <BlogTitle>{post.title}</BlogTitle>
            <BlogDescription>{post.description}</BlogDescription>
            <BlogAuthors authors={post.authors} />
            <BlogTags tags={post.tags} />
            {post.date && <BlogDate date={post.date} />}
          </BlogHeader>
          <BlogBody>{children}</BlogBody>
          <BlogPostNav newer={newer} older={older} />
        </BlogPage>
      </HomeLayout>
    </Provider>
  );
}
```

### タグ [#tags]

`getTags()` を使うと、すべてのタグを一覧表示できます。

```astro tab="src/pages/blog/tags/index.astro"
---
import Layout from '@/layouts/Layout.astro';
import { BlogTagsPage } from '@/components/blog-tags';
import { blogSource } from '@/lib/source';
import { getPosts, getTags } from '@/lib/blog';
---

<Layout title="All Tags">
  <BlogTagsPage
    client:load
    pathname={Astro.url.pathname}
    params={Astro.params}
    tags={getTags(getPosts(blogSource.getPages()))}
  />
</Layout>
```

```tsx tab="src/components/blog-tags.tsx"
import { HomeLayout } from 'fumadocs-ui/layouts/home';
import { baseOptions } from '@/lib/layout.shared';
import { Provider, type ProviderProps } from '@/components/provider';
import type { BlogTag } from '@/lib/blog';
import { BlogHeader, BlogTagCards, BlogTitle } from '@/components/blog';

export function BlogTagsPage({
  tags,
  ...props
}: ProviderProps & { tags: (BlogTag & { count: number })[] }) {
  return (
    <Provider {...props}>
      <HomeLayout {...baseOptions()}>
        <div className="mx-auto flex w-full max-w-(--fd-layout-width) flex-col gap-4 px-4 py-12">
          <BlogHeader>
            <BlogTitle>All Tags</BlogTitle>
          </BlogHeader>
          <BlogTagCards tags={tags} />
        </div>
      </HomeLayout>
    </Provider>
  );
}
```

また、特定のタグが付いた投稿は `getTagPosts()` で表示できます。存在しないタグの場合、この関数は `undefined` を返します。

```astro tab="src/pages/blog/tags/[tag].astro"
---
import type { GetStaticPaths, InferGetStaticPropsType } from 'astro';
import Layout from '@/layouts/Layout.astro';
import { BlogTagPage } from '@/components/blog-tag';
import { blogAuthors, blogSource } from '@/lib/source';
import { getPosts, getTagPosts, getTags } from '@/lib/blog';

export const getStaticPaths = (() => {
  const posts = getPosts(blogSource.getPages(), blogAuthors);

  return getTags(posts).map((tag) => ({
    params: { tag: tag.slug },
    props: getTagPosts(posts, tag.slug)!,
  }));
}) satisfies GetStaticPaths;

type Props = InferGetStaticPropsType<typeof getStaticPaths>;

const { tag, posts } = Astro.props;
---

<Layout title={tag.name}>
  <BlogTagPage
    client:load
    pathname={Astro.url.pathname}
    params={Astro.params}
    tag={tag}
    posts={posts}
  />
</Layout>
```

```tsx tab="src/components/blog-tag.tsx"
import { HomeLayout } from 'fumadocs-ui/layouts/home';
import { baseOptions } from '@/lib/layout.shared';
import { Provider, type ProviderProps } from '@/components/provider';
import type { BlogPost, BlogTag } from '@/lib/blog';
import { BlogCards, BlogHeader, BlogTitle } from '@/components/blog';

export function BlogTagPage({
  tag,
  posts,
  ...props
}: ProviderProps & { tag: BlogTag; posts: BlogPost[] }) {
  return (
    <Provider {...props}>
      <HomeLayout {...baseOptions()}>
        <div className="mx-auto flex w-full max-w-(--fd-layout-width) flex-col gap-4 px-4 py-12">
          <BlogHeader>
            <BlogTitle>{tag.name}</BlogTitle>
          </BlogHeader>
          <BlogCards posts={posts} />
        </div>
      </HomeLayout>
    </Provider>
  );
}
```

## 国際化 [#internationalization]

`getPosts()` には、`blogSource.getPages(lang)` のように特定のロケールのページを渡してください。

タグページのURLは `lib/blog.ts` の `getBlogUrl` によって生成されます。ローダーのURLと一致させるには、`getBlogUrl` に i18n の設定を渡してください。

```ts title="lib/blog.ts"
import { i18n } from '@/lib/i18n';

export const getBlogUrl = createGetUrl('/blog', i18n);
```

投稿のフィードを公開する方法については、[RSS フィード](/docs/astro/guides/rss)を参照してください。