😊

Content Collectionsでブログを作る

に公開

Content Collectionsとは

Content Collectionsは、2024年に登場したTypeSafeなマークダウンベースのコンテンツ管理ツールです。Contentlayerの後継として注目されており、より簡潔で使いやすい設計になっています。Supa Starterでも採用されているなど、実際のプロダクトでも活用されています。

主な特徴:

  • TypeSafeであること
  • コンテンツの強力なバリデーション
  • HMR(Hot Module Replacement)対応

セットアップ(Next.js)

このセクションではNext.jsプロジェクトにContent Collectionsを導入する手順を説明します。

インストール

必要なパッケージをインストールします:

pnpm add @content-collections/core @content-collections/next zod -D

tsconfig.jsonの調整

生成されるファイルへのパスエイリアスを追加します:

tsconfig.json
{
  "compilerOptions": {
    // ...
    "paths": {
      "@/*": ["./*"],
      "content-collections": ["./.content-collections/generated"]
    }
  }
}

Next.js設定の更新

next.config.ts
import type { NextConfig } from "next";
import { withContentCollections } from "@content-collections/next";

const nextConfig: NextConfig = {
  /* config options here */
};

// withContentCollections must be the outermost plugin
export default withContentCollections(nextConfig);

.gitignoreに追加

生成されるファイルをGitで無視するよう設定します:

.gitignore
.content-collections

設定ファイルの作成

プロジェクトルートにcontent-collections.tsを作成:

content-collections.ts
import { defineCollection, defineConfig } from "@content-collections/core";
import { z } from "zod";

const posts = defineCollection({
  name: "posts",
  directory: "src/posts",
  include: "**/*.md",
  schema: z.object({
    title: z.string(),
    summary: z.string(),
  }),
});

export default defineConfig({
  collections: [posts],
});

コンテンツファイルの作成

例としてsrc/posts/hello-world.mdを作成:

src/posts/hello-world.md
---
title: "Hello world"
summary: "This is my first post!"
---

# Hello world

This is my first post!

これで基本的なセットアップは完了です。ここからは、Content Collectionsでブログを作るにあたって、必要なページやセクションの作成の仕方を紹介します。

マークダウンで書きたい

Content Collectionsでは、マークダウンの公式パッケージ @content-collections/markdown が用意されており、compileMarkdownを使うことでHTMLに変換できます。

content-collections.ts
import { defineCollection, defineConfig } from "@content-collections/core";
import { compileMarkdown } from "@content-collections/markdown";
import { z } from "zod";

const posts = defineCollection({
  name: "posts",
  directory: "content/posts",
  include: "*.md",
  schema: z.object({
    title: z.string(),
    date: z.string(),
    published: z.boolean().default(false),
  }),
  transform: async (document, context) => {
    const html = await compileMarkdown(context, document);
    return {
      ...document,
      html,
    };
  },
});

export default defineConfig({
  collections: [posts],
});

これを使うことで、マークダウンのコンテンツをpost.htmlとして取得することができます。

import { allPosts } from "content-collections";

export default function App() {
  return (
    <main>
      <h1>Posts</h1>
      <ul>
        {allPosts.map((post) => (
          <li key={post._meta.path}>
            <h2>{post.title}</h2>
            <div dangerouslySetInnerHTML={{ __html: post.html }} />
          </li>
        ))}
      </ul>
    </main>
  );
}

記事の詳細ページを作りたい

allPostsからslugを指定して、その記事の詳細ページを作することができます。

app/posts/[slug]/page.tsx
import { allPosts } from "content-collections";
import { notFound } from "next/navigation";
export default async function PostPage({
  params,
}: {
  params: Promise<{ slug: string }>
}) {
  const { slug } = await params;
  const post = allPosts.find((post) => post._meta.path === slug);

  if (!post) notFound();

  return (
    <div>
      <h1>{post.title}</h1>
      <div dangerouslySetInnerHTML={{ __html: post.html }} />
    </div>
  )
}

タグを追加したい

まずcollectionのschemaに、tagsという配列を追加します。

tags: z.array(z.string()).optional(),

post.tagsを使って、タグを表示することができます。

タグ一覧ページを作りたい

タグ一覧ページを作るには、allPostsからtagsを取得し、タグの重複分をcountとして取得します。

import { allPosts } from "content-collections";
import { pipe, flatMap, groupBy, map, entries, sortBy } from "remeda";

export interface Tag {
  slug: string;
  count: number;
}

export const getAllTags = (): Tag[] => {
  return pipe(
    allPosts,
    flatMap((post) => post.tags || []), // タグを平坦化
    groupBy((tag) => tag), // 同じタグをグループ化
    entries(), // オブジェクトを配列に変換
    map(([slug, tags]) => ({ slug, count: tags.length })), // カウント付きオブジェクトに変換
    sortBy((tag) => -tag.count) // 使用回数順に並べ替え
  )
}

カテゴリを追加したい

tagと同じく、collectionのschemaに、categoryを追加します。

category: z.string().optional(),

post.categoryを使って、カテゴリを表示することができます。

まとめ

Content Collectionsを使ったブログ構築について、基本的なTipsを紹介しました。

主なポイント:

  • TypeSafeなコンテンツ管理でDX(開発体験)が向上
  • タグやカテゴリ機能も柔軟に実装可能
  • HMR対応で開発効率が大幅に改善

Content CollectionsはContentlayerの後継として、よりシンプルで使いやすい設計になっており、これからブログを作成する方には特におすすめです。

またブログのデモはこちらから見れます!
https://zenn-content-collection.vercel.app/

参考記事

GitHubで編集を提案

Discussion