Tech3分で読める

このブログの作り方: Next.jsでMarkdownを運用する

このブログで使っているMarkdown運用の実装メモ。frontmatterの読み取り、Mermaid表示、ネスト箇条書き対応、記事追加時の手順をまとめます。

#nextjs#markdown#frontmatter#mermaid#typescript#blog

このブログの作り方: Next.jsでMarkdownを運用する

このブログは、nextjs/src/content/blog 配下のMarkdownファイルを読み込み、Next.js側で表示しています。 この記事では、実際の実装に沿って「記事をどう書き、どう描画するか」をまとめます。


全体構成

まず、ブログ実装にあたって自分の要件を次のように置いています。

  • Markdownファイルだけで記事を書けること
  • frontmatterでタイトル・日付・要約・タグを管理できること
  • 記事中のMermaid図をそのまま描画できること
  • 箇条書きのネスト(ul/ol)が崩れずに表示されること
  • コードブロックをワンクリックでコピーできること(アイコンのみUI)
  • 記事一覧をシンプルにページ分割できること(?page= クエリ)
  • ページネーションを上部・下部の両方に表示し、左右中央に揃えること
  • 表・コード・引用など、技術記事でよく使う要素を安定して描画できること
  • 運用時に壊れにくいよう、描画ルールをアプリ側で制御できること

ポイントは、Markdownをそのまま外部ライブラリに丸投げするのではなく、このプロジェクト向けの描画ルールを実装している点です。


記事メタ自動生成(自前実装)

記事本文とは別に、公開運用向けのメタ情報もアプリ側で自動生成しています。

項目どこで生成内容
readTimesrc/lib/blog.ts本文語数から読了時間を計算
categorysrc/lib/blog.tsslug 先頭ディレクトリから推定
canonical/OG/Twitterapp/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.tsxsearchParams.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-domUIレンダリングMarkdown描画コンポーネントの土台
mermaid図の描画MermaidDiagram.tsx で動的描画
typescript型安全な実装パーサ・レンダラーの保守性を向上

Next.jsブログでよく使う候補(比較検討用)

ライブラリ主用途向いているケース
gray-matterfrontmatter解析frontmatterだけを素早く扱いたい
react-markdownMarkdown描画既製レンダラーで早く構築したい
remark / rehypeMarkdown AST変換変換ルールを柔軟に拡張したい
next-mdx-remote / MDXMarkdown + JSX記事内でReactコンポーネントを使いたい
contentlayerコンテンツ型定義 + 取り込み記事数が増えて型付き運用したい

このブログでの方針

  • Mermaidだけは mermaid ライブラリで導入
  • それ以外のfrontmatter解析とMarkdown描画は自前実装
  • 必要最小の依存に絞り、挙動を追いやすく保つ

frontmatterの書き方

このブログでは、記事先頭の --- ブロックを src/lib/blog.ts でパースしています。

最小テンプレート

MD
---
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. ステップ1
  2. ステップ2
    • 補足2-1
    • 補足2-2
  3. ステップ3

このレンダラーで対応している要素

MarkdownContent.tsx では、次の要素を扱っています。

  • 見出し(####
  • 水平線(---
  • 表(| 記法)
  • 引用(>
  • 箇条書き・番号付きリスト(ネスト含む)
  • コードブロック(通常コード / mermaid
  • 画像(/ から始まるパス)
  • インライン要素(太字、コード、リンク、改行)

記事追加の実務フロー

  1. src/content/blog/<category>/.md を追加する
  2. frontmatterを記入する(title/date/excerpt/tags
  3. 本文を書く(必要なら表・Mermaid・ネスト箇条書き)
  4. プレビューで崩れを確認する
  5. タイトル・導線・タグを最終調整する

よくハマるポイント

  • frontmatterの --- を閉じ忘れる
  • tags の配列記法が崩れている
  • Mermaidブロックの言語名を mermaid 以外にしてしまう
  • ネスト箇条書きのインデントが不揃い

まとめ

このブログの運用は、次の3つを押さえると安定します。

  • frontmatterを正しく書く
  • Mermaidは `mermaid で書く
  • ネスト箇条書きはインデントを揃える

記事を書く人の体験を良くするには、Markdownの自由度だけでなく、どこまで描画仕様を固定するかの設計が重要だと感じています。

RK

1997年生まれ

ITエンジニア

インフラ・SRE