このブログの作り方: Next.jsでMarkdownを運用する
このブログで使っているMarkdown運用の実装メモ。frontmatterの読み取り、Mermaid表示、ネスト箇条書き対応、記事追加時の手順をまとめます。
このブログの作り方: Next.jsでMarkdownを運用する
このブログは、nextjs/src/content/blog 配下のMarkdownファイルを読み込み、Next.js側で表示しています。 この記事では、実際の実装に沿って「記事をどう書き、どう描画するか」をまとめます。
全体構成
まず、ブログ実装にあたって自分の要件を次のように置いています。
- Markdownファイルだけで記事を書けること
- frontmatterでタイトル・日付・要約・タグを管理できること
- 記事中のMermaid図をそのまま描画できること
- 箇条書きのネスト(
ul/ol)が崩れずに表示されること - コードブロックをワンクリックでコピーできること(アイコンのみUI)
- 記事一覧をシンプルにページ分割できること(
?page=クエリ) - ページネーションを上部・下部の両方に表示し、左右中央に揃えること
- 表・コード・引用など、技術記事でよく使う要素を安定して描画できること
- 運用時に壊れにくいよう、描画ルールをアプリ側で制御できること
ポイントは、Markdownをそのまま外部ライブラリに丸投げするのではなく、このプロジェクト向けの描画ルールを実装している点です。
記事メタ自動生成(自前実装)
記事本文とは別に、公開運用向けのメタ情報もアプリ側で自動生成しています。
| 項目 | どこで生成 | 内容 |
|---|---|---|
readTime | src/lib/blog.ts | 本文語数から読了時間を計算 |
category | src/lib/blog.ts | slug 先頭ディレクトリから推定 |
| canonical/OG/Twitter | app/blog/**/page.tsx + src/lib/siteMetadata.ts | ページURLとホストに応じてメタを生成 |
| カテゴリ一覧 | src/lib/blog.ts | 記事群からカテゴリを集約して生成 |
この構成にしているので、記事ファイルを追加してもメタ更新を手作業でほぼ書かずに済みます。
frontmatterパーサの自前実装
frontmatterは外部ライブラリ任せではなく、src/lib/blog.ts で自前パースしています。
- 先頭
---/ 終端---を検出して本文と分離 key: value形式を抽出- 配列は
["a", "b"]形式とインデント形式の両方を処理 - 文字列クオートを正規化(
"/')
用途を絞った最小実装にして、挙動を追いやすくしているのがポイントです。
描画エンジンの自前化
MarkdownContent.tsx で、ブロック単位のレンダリングを自前実装しています。
- 見出し、水平線、表、引用、画像、コードを個別に判定
- 箇条書き/番号付きリストはインデントから再帰的に構築
- インラインは太字・コード・リンク・改行をトークン分解して描画
この方式だと「このブログでどう見せたいか」を実装で固定しやすく、 デザインや仕様変更にも追従しやすくなります。
コードブロックのコピー対応(今回の要件)
通常コードブロックに、アイコンだけのコピーボタンを実装しています。
MarkdownContent.tsxで通常コードブロック時にCopyCodeButtonを表示CopyCodeButton.tsxはクリックでクリップボードへコピー- 押下後は短時間だけチェックアイコンに切り替え
- Mermaidブロックには表示しない(通常コードのみ)
実装を分離しているので、今後デザインを変更するときも CopyCodeButton.tsx のみで調整できます。
記事一覧ページネーション(今回の要件)
記事一覧(/blog)は、?page=2 のようなクエリでシンプルにページ分割しています。
app/blog/page.tsxでsearchParams.pageを解釈POSTS_PER_PAGEで1ページ件数を管理- 範囲外ページは最終ページへ丸め込み
- ページネーションはヒーロー下(総記事数の下)と一覧下の両方に表示
- 下側のページネーションもページ全体基準で中央に寄せる
実装を増やしすぎず、運用時の見通しを優先した構成にしています。
Mermaid安全描画(自前ラッパー)
MermaidDiagram.tsx でMermaid本体をラップし、安全側の設定で描画しています。
import("mermaid")の動的ロードsecurityLevel: "strict"を指定- 描画失敗時はエラー表示を出す
- SVGをレスポンシブ表示できるようにスタイルを固定
Mermaidを許可しつつ、無制限に埋め込まないための実務的なバランスを取っています。
ライブラリとは別に自分で実装しているところ
上の4点以外にも、次の部分はこのプロジェクト独自で実装しています。
- 非公開記事の除外(
_private/_drafts/_foo.md) - 旧URLから新URLへの解決(
resolveMovedBlogSlug) - 安全なリンク判定(
http/https/mailto/#/などのみ許可) - 画像ソース制御(ローカルパスのみ描画)
- カテゴリ名・説明のマッピング辞書
「記事を書く体験」と「公開運用の安全性」を両立するために、 Markdown処理だけでなく周辺ロジックも自前で揃えています。
Next.jsでブログ作成に使うライブラリまとめ
ここでは、このプロジェクトで実際に使っているものと、 Next.jsブログでよく採用される候補を分けて整理します。
このプロジェクトで使っているライブラリ
| ライブラリ | 用途 | このブログでの位置づけ |
|---|---|---|
next | アプリ基盤 / ルーティング / SSG | 記事一覧・詳細ページの配信基盤 |
react / react-dom | UIレンダリング | Markdown描画コンポーネントの土台 |
mermaid | 図の描画 | MermaidDiagram.tsx で動的描画 |
typescript | 型安全な実装 | パーサ・レンダラーの保守性を向上 |
Next.jsブログでよく使う候補(比較検討用)
| ライブラリ | 主用途 | 向いているケース |
|---|---|---|
gray-matter | frontmatter解析 | frontmatterだけを素早く扱いたい |
react-markdown | Markdown描画 | 既製レンダラーで早く構築したい |
remark / rehype 系 | Markdown AST変換 | 変換ルールを柔軟に拡張したい |
next-mdx-remote / MDX | Markdown + JSX | 記事内でReactコンポーネントを使いたい |
contentlayer | コンテンツ型定義 + 取り込み | 記事数が増えて型付き運用したい |
このブログでの方針
- Mermaidだけは
mermaidライブラリで導入 - それ以外のfrontmatter解析とMarkdown描画は自前実装
- 必要最小の依存に絞り、挙動を追いやすく保つ
frontmatterの書き方
このブログでは、記事先頭の --- ブロックを src/lib/blog.ts でパースしています。
最小テンプレート
---
title: "記事タイトル"
date: "2026-05-31"
excerpt: "記事の概要"
tags: ["nextjs", "markdown"]
---主な項目
| キー | 型 | 用途 |
|---|---|---|
title | 文字列 | 記事タイトル |
date | 文字列 | 並び順・表示日 |
excerpt | 文字列 | 一覧やOG向けの要約 |
tags | 文字列配列 | タグ表示・検索の補助 |
author | 文字列(任意) | 未指定時は既定値 RK |
image | 文字列(任意) | OGPなどで使う拡張用 |
Mermaidを表示できるようにする仕組み
このブログでは、コードフェンスが mermaid のときだけ専用コンポーネントに渡しています。
MarkdownContent.tsxでコードブロックを判定- 言語が
mermaidの場合はMermaidDiagramを使用 MermaidDiagram.tsx内でimport("mermaid")を実行して描画securityLevel: "strict"で安全側に設定
記事側の書き方
ネスト箇条書きを表示できるようにする仕組み
以前は、インデント付き箇条書きが同じ段落に見えるケースがありました。 現在はレンダラー側で、インデントを見て再帰的にリスト構造を組み立てる実装になっています。
ul/olの両方に対応- 子リストはインデントで判定
- 二重・三重ネストでも描画可能
記事側の書き方(4スペース推奨)
- 親項目A
- 子項目A-1
- 子項目A-2
- 孫項目A-2-1
- 親項目B
- 子項目B-1
番号付きリストも同様です。
- ステップ1
- ステップ2
- 補足2-1
- 補足2-2
- ステップ3
このレンダラーで対応している要素
MarkdownContent.tsx では、次の要素を扱っています。
- 見出し(
#〜###) - 水平線(
---) - 表(
|記法) - 引用(
>) - 箇条書き・番号付きリスト(ネスト含む)
- コードブロック(通常コード /
mermaid) - 画像(
/から始まるパス) - インライン要素(太字、コード、リンク、改行)
記事追加の実務フロー
src/content/blog/<category>/に.mdを追加する- frontmatterを記入する(
title/date/excerpt/tags) - 本文を書く(必要なら表・Mermaid・ネスト箇条書き)
- プレビューで崩れを確認する
- タイトル・導線・タグを最終調整する
よくハマるポイント
- frontmatterの
---を閉じ忘れる tagsの配列記法が崩れている- Mermaidブロックの言語名を
mermaid以外にしてしまう - ネスト箇条書きのインデントが不揃い
まとめ
このブログの運用は、次の3つを押さえると安定します。
- frontmatterを正しく書く
- Mermaidは
`mermaidで書く - ネスト箇条書きはインデントを揃える
記事を書く人の体験を良くするには、Markdownの自由度だけでなく、どこまで描画仕様を固定するかの設計が重要だと感じています。