2026.02.10Next.jsMDXTypeScript
Next.js App RouterでMDXブログを構築する
はじめに
個人ブログを Next.js の App Router で構築しました。MDX を使うことで、Markdown の手軽さと React コンポーネントの表現力を両立できます。
この記事では、実際のセットアップ手順と設計判断を紹介します。
技術選定
今回のブログでは以下の技術を採用しました。
- Next.js 16 (App Router) — SSG + Cache Components
- MDX — Markdown + JSX
- Tailwind CSS v4 — CSS-first configuration
- gray-matter — フロントマター解析
MDX の設定
@next/mdx を使って、App Router にネイティブ統合します。
// next.config.ts
import createMDX from "@next/mdx";
const nextConfig = {
pageExtensions: ["js", "jsx", "md", "mdx", "ts", "tsx"],
};
const withMDX = createMDX({
options: {
remarkPlugins: [],
rehypePlugins: [],
},
});
export default withMDX(nextConfig);
プロジェクトルートに mdx-components.tsx を配置するのを忘れないでください。App Router では必須です。
フロントマターの型定義
gray-matter で解析したフロントマターに型安全性を持たせます。
type Post = {
slug: string;
title: string;
description: string;
date: string;
tags: string[];
published: boolean;
content: string;
};
記事の取得
ファイルシステムから MDX ファイルを読み取り、フロントマターを解析します。
import fs from "fs/promises";
import path from "path";
import matter from "gray-matter";
const BLOG_DIR = path.join(process.cwd(), "content/blog");
export const getAllPosts = async (): Promise<Post[]> => {
const files = await fs.readdir(BLOG_DIR);
const posts = await Promise.all(
files
.filter((f) => f.endsWith(".mdx"))
.map(async (file) => {
const content = await fs.readFile(
path.join(BLOG_DIR, file),
"utf-8"
);
const { data } = matter(content);
return { slug: file.replace(/\.mdx$/, ""), ...data };
})
);
return posts.sort((a, b) => (a.date > b.date ? -1 : 1));
};
シンタックスハイライト
rehype-pretty-code と shiki を組み合わせることで、VS Code と同等のシンタックスハイライトを実現できます。テーマは GitHub の Light/Dark を使い分けています。
まとめ
App Router + MDX の組み合わせは、個人ブログには最適な選択肢だと感じました。Server Components のおかげで、ビルド時にすべてが静的生成され、クライアントには最小限の JavaScript しか配信されません。