Skip to Content
Fumadocs

Blog

Add a blog to your app.

Installation

npx @fumadocs/cli@latest add blog

It installs the components to components/blog, and the utilities of posts to lib/blog.ts.

Define a collection for your posts with blogPageSchema, and register your authors:

import { defineConfig } from 'astro/config';
import fumadocs from '@fumadocs/astro';

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

Write your posts under content/blog:

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
---
FieldDescription
datePublish date, posts are sorted by it
tagsEach tag has its page under /blog/tags
authorsKeys of your authors, unknown keys are names
imageCover image URL, shown on the cards of posts

Pages

getPosts() resolves the authors and tags of pages, newest first. Render them in routes under your Home Layout.

Posts

---
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>

Post

findPost() finds a post along with the newer and older posts. With toc, a floating panel shows the table of contents and a share button.

---
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>

Tags

List all tags with getTags():

---
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>

And the posts of a tag with getTagPosts(), which returns undefined for unknown tags:

---
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>

Internationalization

Pass the pages of a locale to getPosts(), like blogSource.getPages(lang).

The URLs of tag pages come from getBlogUrl in lib/blog.ts. Pass it your i18n config to match the URLs of your loader:

lib/blog.ts
import { i18n } from '@/lib/i18n';

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

To publish a feed of your posts, see RSS Feed.

How is this guide?

Last updated on