はじめに
個人技術ブログ FumiBlog を公開しました。
リポジトリ
以前Qiita記事の投稿を始める前に中途半端に作成していたブログを作り直しました。
今回はシンプルな技術スタックを選んで実装しています。この記事では構成や実装のポイントをまとめます。
技術スタック
| 役割 | 技術 |
|---|---|
| フロントエンド | Next.js 15 (App Router) |
| スタイリング | Tailwind CSS v4 |
| コンポーネント管理 | Storybook + Atomic Design |
| コンテンツ管理 | microCMS |
| ホスティング | Vercel |
コンポーネント設計:Atomic Design
コンポーネントの整理に Atomic Design を採用しました。
src/components/
├── atoms/ # Button, TagBadge, SourceBadge, Logo など
├── molecules/ # ArticleCard, FeaturedCard, CategoryFilter など
└── organisms/ # Header, Footer, HeroSection, BootScreen など
分類の基準
-
atoms - それ単体で意味が完結する最小単位。Props
で見た目を制御するのみで、外部状態に依存しない -
molecules - atoms
を組み合わせた再利用可能なパーツ。「記事カード」「カテゴリフィルター」など、ある程度まとまった
UI のかたまり -
organisms - ページを構成する大きなブロック。Header や
HeroSection のように、複数の molecules を束ねた単位
この基準を決めておくと「このコンポーネントどこに置く?」という迷いがなくなりました。
Storybook との組み合わせ
各コンポーネントに .stories.tsx を用意し、Storybook で独立して確認できるようにしました。Props のバリエーション(サイズ・テーマ・状態)をひと目で確認できるため、デザインの一貫性を保ちやすくなります。
Next.js App Router の活用
ページ構成
src/app/
├── page.tsx # トップページ
├── blog/
│ ├── page.tsx # 記事一覧
│ └── [slug]/page.tsx # 記事詳細
├── category/[slug]/ # カテゴリ別一覧
├── tag/[slug]/ # タグ別一覧
├── about/ # About ページ
├── contact/ # Contact ページ
├── sitemap.ts # 自動サイトマップ
└── feed.xml/route.ts # RSS フィード
App Router
のファイルベースルーティングにより、ディレクトリ構成がそのままサイトマップになります。generateStaticParams で動的ルートも静的生成できます。
OGP / generateMetadata
全ページに metadata / generateMetadata を実装しました。記事ページでは microCMS のアイキャッチ画像を OG 画像として設定しています。
export async function generateMetadata({ params }: Props): Promise<Metadata> {
const article = await getBlogBySlug((await params).slug);
return {
title: article.title,
description: article.description,
openGraph: {
title: article.title,
description: article.description,
type: 'article',
images: article.eyecatch ? [{ url: article.eyecatch.url }] : [],
},
};
}
sitemap.ts
app/sitemap.ts に MetadataRoute.Sitemap 型の配列を返す関数を置くだけで /sitemap.xml が自動生成されます。
export default async function sitemap(): Promise<MetadataRoute.Sitemap> {
const articles = await getBlogs({ limit: 200 });
return [
{ url: `${siteUrl}/`, changeFrequency: 'daily', priority: 1.0 },
...articles.map((a) => ({
url: `${siteUrl}/blog/${a.slug}`,
lastModified: new Date(a.publishDate),
priority: 0.8,
})),
];
}
RSS フィード
app/feed.xml/route.ts で RSS 2.0 フィードを配信しています。Route Handlers で Content-Type: application/xml を返すだけなので実装はシンプルです。
microCMS との連携
microCMS の導入手順・型定義・データ取得パターンについては以前の記事で詳しく解説しています。
今回は microCMS で書いた記事に加えて、Qiita・Zenn の外部記事も自動取得してブログ上に表示する仕組みを追加しました。
各ソースを統一の Article 型に変換する transformer パターン を採用しており、microCMS・Qiita・Zenn のどの記事も同じコンポーネントで表示できます。
type Article = {
id: string;
title: string;
slug: string;
publishDate: string;
source: 'microcms' | 'qiita' | 'zenn';
};
記事一覧ページでは SourceBadge コンポーネントで投稿元を視覚的に区別しています。
Vercel へのデプロイ
GitHub リポジトリと Vercel を連携させ、main ブランチへのプッシュで自動デプロイされる構成にしました。
設定として必要だった環境変数は以下の 4 つです。
| 変数名 | 用途 |
|---|---|
MICROCMS_SERVICE_DOMAIN |
microCMS のサービスドメイン |
MICROCMS_API_KEY |
microCMS の API キー |
QIITA_USERNAME |
Qiita 記事取得用のユーザー名 |
NEXT_PUBLIC_SITE_URL |
OGP・sitemap 用のサイト URL |
Vercel の管理画面の Settings → Environment Variables
に設定するだけで、あとはデプロイ時に自動で読み込まれます。
おわりに
Next.js App Router と microCMS の組み合わせは、個人ブログにちょうどいい規模感でした。
記事の投稿・管理は microCMS の管理画面から行えるため、ローカル環境を立ち上げることなく運用できます。
このブログでは、Qiita に書くような専門的な技術記事だけでなく、
開発メモや調べたことの整理など、もう少し気軽に書ける内容も発信していく予定です。
