unified/remark/rehype パイプライン — Markdown を高機能 HTML に変換する仕組み

unified エコシステムを使うと、Markdown ファイルをシンタックスハイライト付き・外部リンク制御付きの高機能 HTML に変換できます。各プラグインが何をしているかを、AST(抽象構文木)の変換フローと合わせて解説します。

unified エコシステムとは

unifiedテキスト処理を AST ベースのパイプラインで行うフレームワークです。

入力テキスト
    ↓  parse(パーサー)
   AST(抽象構文木)
    ↓  transform(プラグイン群)
   変換後 AST
    ↓  stringify(シリアライザー)
出力テキスト

remark は Markdown 用の unified プロセッサ、rehype は HTML 用の unified プロセッサで、両者を橋渡しする remark-rehype を介することで Markdown → HTML の変換チェーンが成立します。

登場する AST の種類

AST 名対応形式
mdastMarkdown
hastHTML

パイプラインの全体像

import { unified } from "unified";
import remarkParse from "remark-parse";
import remarkGfm from "remark-gfm";
import remarkRehype from "remark-rehype";
import rehypeRaw from "rehype-raw";
import rehypePrism from "rehype-prism-plus";
import rehypeExternalLinks from "rehype-external-links";
import rehypeStringify from "rehype-stringify";

const processedContent = await unified()
  .use(remarkParse)
  .use(remarkGfm)
  .use(remarkRehype, { allowDangerousHtml: true })
  .use(rehypeRaw)
  .use(rehypePrism)
  .use(rehypeExternalLinks, { target: "_blank", rel: ["nofollow"] })
  .use(rehypeStringify)
  .process(content);

const html = processedContent.toString();

ステップ 1: remarkParse — Markdown → mdast

.use(remarkParse)

Markdown テキストを mdast(Markdown AST)に変換するパーサーです。unified() に最初に追加する「入口」の役割を担います。

// "## 見出し" → mdast ノード
{
  "type": "heading",
  "depth": 2,
  "children": [{ "type": "text", "value": "見出し" }]
}

ステップ 2: remarkGfm — GFM 拡張の有効化

.use(remarkGfm)

GitHub Flavored Markdown の拡張構文を mdast に追加するプラグインです。

構文
テーブル| A | B |
タスクリスト- [x] 完了
打ち消し線~~text~~
自動リンクhttps://example.com

ステップ 3: remarkRehype — mdast → hast

.use(remarkRehype, { allowDangerousHtml: true })

mdast を hast(HTML AST)に変換するブリッジプラグインです。allowDangerousHtml: true を渡すことで、Markdown 内に書いた生 HTML(<div>, <span> など)を hast の raw ノードとして保持します。このオプションを省略すると生 HTML がすべて除去されます。

ステップ 4: rehypeRaw — 生 HTML ノードのパース

.use(rehypeRaw)

前ステップで raw ノードとして残った生 HTML 文字列を、正式な hast ノードに変換します。remarkRehypeallowDangerousHtml: trueセットで使うのが必須パターンです。

remarkRehype({ allowDangerousHtml: true })  → raw ノードとして残す

rehypeRaw                                   → raw ノードを hast ノードに昇格させる

この 2 ステップがないと、Markdown 内の <details><kbd> などのカスタム HTML が消えます。

ステップ 5: rehypePrism — シンタックスハイライト

.use(rehypePrism)

rehype-prism-plus が提供するプラグインです。hast 上のコードブロックノード(<pre><code class="language-xxx">)を走査し、Prism.js のトークン分割に基づいたクラスを付与します。

<!-- 出力例 -->
<pre class="language-typescript">
  <code class="language-typescript">
    <span class="token keyword">const</span> x
    <span class="token operator">=</span>
    <span class="token number">1</span>
  </code>
</pre>

CSS 側で .token.keyword などにスタイルを当てることでハイライトが実現します。

.use(rehypeExternalLinks, { target: "_blank", rel: ["nofollow"] })

http:// / https:// で始まる外部リンクに対して自動で属性を付与するプラグインです。

オプション付与される属性効果
target: "_blank"target="_blank"新しいタブで開く
rel: ["nofollow"]rel="nofollow"検索エンジンへのリンクジュース遮断

セキュリティのため noopener noreferrer も一緒に付与するのが推奨されます。

.use(rehypeExternalLinks, {
  target: "_blank",
  rel: ["nofollow", "noopener", "noreferrer"],
})

ステップ 7: rehypeStringify — hast → HTML 文字列

.use(rehypeStringify)

hast を HTML 文字列に変換する「出口」のシリアライザーです。

パイプライン全体のデータフロー

Markdown テキスト (string)

    ▼ remarkParse
mdast (Markdown AST)

    ▼ remarkGfm        ← テーブル・タスクリストなどのノードを追加
mdast (拡張済み)

    ▼ remarkRehype({ allowDangerousHtml: true })
hast + raw ノード(生 HTML 断片)

    ▼ rehypeRaw        ← raw ノードを正式な hast ノードに変換
hast (完全な HTML AST)

    ▼ rehypePrism      ← コードブロックにハイライト用クラスを付与
hast (ハイライト済み)

    ▼ rehypeExternalLinks
hast (リンク属性付き)

    ▼ rehypeStringify
HTML 文字列 (string)

ハマりやすいポイント

allowDangerousHtml と rehypeRaw のセット忘れ

remarkRehypeallowDangerousHtml: true を渡しただけでは生 HTML は残りません。rehypeRaw を後続に追加して初めて hast ノードに変換されます。

プラグインの順序

rehypeRawremarkRehype の直後に置くのが安全です。raw ノードが他のプラグインで処理される前に解決しておく必要があります。

// OK: rehypeRaw は remarkRehype の直後
.use(remarkRehype, { allowDangerousHtml: true })
.use(rehypeRaw)     // まず raw ノードを解決
.use(rehypePrism)   // その後にハイライト処理

CSS の用意を忘れない

rehype-prism-plus はクラスを付与するだけで、CSS は自分で用意する必要があります。

// src/app/layout.tsx
import "prismjs/themes/prism-tomorrow.css";

ESM 専用パッケージ

unified v11 以降、remark-* / rehype-* の主要パッケージはすべて ESM 専用です。require() で呼び出す CommonJS 環境では動作しません。

まとめ

プラグイン変換内容
remarkParseMarkdown → mdast
remarkGfmGFM 拡張構文を mdast に追加
remarkRehypemdast → hast(生 HTML は raw ノードとして保持)
rehypeRawraw ノード → 正式な hast ノード
rehypePrismコードブロックにハイライト用クラスを付与
rehypeExternalLinks外部リンクに target / rel を付与
rehypeStringifyhast → HTML 文字列

各プラグインは AST を受け取って変換した AST を返す単純な構造なので、差し替えや追加が容易です。シンタックスハイライトを rehype-highlight(highlight.js ベース)に変えたい場合は rehypePrism をそのプラグインに置き換えるだけで済みます。