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の調整
生成されるファイルへのパスエイリアスを追加します:
{
"compilerOptions": {
// ...
"paths": {
"@/*": ["./*"],
"content-collections": ["./.content-collections/generated"]
}
}
}
Next.js設定の更新
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で無視するよう設定します:
.content-collections
設定ファイルの作成
プロジェクトルートに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
を作成:
---
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に変換できます。
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を指定して、その記事の詳細ページを作することができます。
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の後継として、よりシンプルで使いやすい設計になっており、これからブログを作成する方には特におすすめです。
またブログのデモはこちらから見れます!
Discussion