前提
現在、Next.jsとSupabaseを使って自作CMSを開発しています。
今回記事の目次が欲しかったので、rehypeプラグインを使ってCMSの目次を自動生成する機能を追加しました。
完成図はこんな感じです。
開発環境
- TypeScript 5.x
- Supabase(データベース・ストレージ)
- Tailwind CSS 3.x + @tailwindcss/typography
- ReactMarkdown 9.x
- rehype-slug / rehype-autolink-headings などのrehype系プラグイン
rehypeとは何か
rehype は、HTMLを構文木(AST)として扱い、プラグインで自由に加工できる仕組みです。MarkdownをHTMLに変換した後、そのHTMLに対して追加処理を行いたいときに使われます。
Markdown変換の流れ
Markdown
↓
remark(Markdown構文を解析)
↓
rehype(HTML構文を解析・加工)
↓
HTML / React要素として出力
rehypeプラグインの代表例
| プラグイン名 | 役割 | 主な用途 |
|---|---|---|
| rehype-slug | 見出しタグに自動で id を付与 |
目次やリンクジャンプ |
| rehype-autolink-headings | 見出しを自動でリンク化 | Qiita・Zennのような構成 |
| rehype-highlight | コードブロックのシンタックスハイライト | 技術記事での装飾 |
| rehype-sanitize | 不正なHTMLを除去 | セキュリティ対策 |
目次を自動生成する仕組みにどう使うか
rehype自体はHTMLにidを付与するだけですが、この情報を利用すれば、Markdown本文から見出しを抽出し、目次を自動生成することができます。
// Markdown本文から見出し(##, ###)を抽出して配列で返す関数
export function extractHeadings(markdown: string) {
// ## または ### に続くテキストを取得するための正規表現
// ^ :行頭から始まる
// (#{2,3}):#が2〜3個(つまりh2かh3)
// \s+ :空白を1つ以上
// (.*) :見出しの本文
// gm :複数行(g)かつ行単位(m)で検索
const regex = /^(#{2,3})\s+(.*)$/gm;
// 見出しを格納する配列
const headings = [];
// 1件ずつマッチング結果を取得
let match;
while ((match = regex.exec(markdown)) !== null) {
// match[1] → "#"の数(見出しレベル)
// match[2] → 見出しテキスト
headings.push({
level: match[1].length, // #の数(2ならh2, 3ならh3)
text: match[2], // 見出しテキスト本体
id: match[2].replace(/\s+/g, "-"), // スペースをハイフンに変換してid化
});
}
// 抽出した見出しデータを返す
// 例: [{ level: 2, text: "概要", id: "概要" }]
return headings;
}
このように見出しを抽出すれば右側に自動で目次を描画できます。
まとめ
- rehypeはHTML構文木を扱うための変換層
- remarkがMarkdownを解析し、rehypeがHTMLを加工
- ReactMarkdownでは、rehypeプラグインを通じて出力を自在にカスタマイズできる
-
rehype-slugとrehype-autolink-headingsの組み合わせで、見出しリンクや目次生成を自動化可能
rehypeの仕組みを理解すると、MarkdownベースのCMSや技術ブログをより拡張性高く設計できるようになります。
