Shiki と rehype-pretty-code でMDXのシンタックスハイライトを移行した記録
Next.js(App Router)の MDX ハイライトを rehype-highlight + highlight.js から Shiki + rehype-pretty-code に移行したメモ。
ライトテーマは vitesse-light、ダークは vitesse-dark。
行番号はとりあえずなし、シンプルなコードブロックを実装する。
Shiki とは
VS Code と同じく TextMate ベースのシンタックスハイライタ。テーマや言語定義をそのまま使えるため、エディタと近い配色を再現しやすい。
トークンごとにインライン style と CSS カスタムプロパティ(--shiki-*)を出力するのが特徴。
出力例:
console.log("test");実際に出力されるコード:
<code data-language="tsx" data-theme="vitesse-light vitesse-dark" style="display:grid">
<span data-line="">
<span style="--shiki-light:#E45649;--shiki-dark:#BD976A">console</span>
<span style="--shiki-light:#383A42;--shiki-dark:#666666">.</span>
<span style="--shiki-light:#4078F2;--shiki-dark:#80A665">log</span>
<span style="--shiki-light:#383A42;--shiki-dark:#666666">(</span>
<span style="--shiki-light:#50A14F;--shiki-dark:#C98A7D77">"</span>
<span style="--shiki-light:#50A14F;--shiki-dark:#C98A7D">test</span>
<span style="--shiki-light:#50A14F;--shiki-dark:#C98A7D77">"</span>
<span style="--shiki-light:#383A42;--shiki-dark:#666666">);</span>
</span>
</code>rehype とは
Markdown / MDX を HTML(HAST)に変換して処理するためのパイプライン。
rehype-* プラグインを挟むことで、コードハイライトや要素変換、属性付与などの前処理を行える。今回利用する rehype-pretty-code も、rehype 段でコードブロックを装飾するプラグインの一つ。
なぜ Shiki?これまでとの違い
- これまで:
rehype-highlight+highlight.jsの CSS を読み込み- 導入は手軽だが、テーマ選択やダーク / ライト切り替えを自前の CSS で調整する必要があった。
- これから: Shiki にテーマを指定し、VS Code と近い配色を再現。
rehype-pretty-codeを組み合わせることで、MDX のコードフェンスを自動的に Shiki ハイライトへ変換できる。
rehype-pretty-code とは(類似ライブラリとの違い)
rehype プラグインの一つで、MDX 中のコードフェンスを Shiki でハイライトし、行番号・行ハイライト・ファイル名表示などの装飾用 HTML を生成してくれる。
類似ライブラリとしては、rehype-prism-plus(Prism 系)、rehype-highlight(highlight.js 系)、rehype-shiki(シンプルに Shiki を噛ませるだけ)などがある。
装飾機能まで含めて一括で管理したい場合は、rehype-pretty-code が便利。
参考: Shiki 単体で使う場合
Shiki 公式の Next.js ガイドでは、codeToHtml / codeToHast を直接呼び出し、生成した HTML / HAST をコンポーネントに埋め込む構成が紹介されている(例: サーバーコンポーネントで codeToHtml して dangerouslySetInnerHTML する)。
この方法だと DOM 構造と CSS を自前で完全に制御できる一方で、MDX のコードフェンスを自動変換したい場合は、別途 remark / rehype 側の追加処理が必要になる。
参考コード(RSC での直呼び):
import { codeToHtml } from "shiki";
type Props = {
code: string;
lang: "tsx" | "diff";
};
export default async function CodeBlock({ code, lang }: Props) {
const html = await codeToHtml(code, {
lang,
theme: "vitesse-dark", // lightを使うなら vitesse-light など
});
return (
<div className="markdown-body" dangerouslySetInnerHTML={{ __html: html }} />
);
}今回採用した方法: rehype-pretty-code 経由
lib/note.ts/lib/schedule.tsのrehypePluginsにrehype-pretty-codeを設定。テーマは{ light: "vitesse-light", dark: "vitesse-dark" }。- 行番号は使用しない(オプション無効)。
- コードフェンスの言語は Shiki が理解するものを使う。差分なら
```diff、TSX なら```tsx。 Shiki 本体にはdiff-tsxのような複合言語 ID はない。差分表示をしたい場合は、lang="tsx"のまま@shikijs/transformersのtransformerNotationDiffを使い、コード内で// [!code ++]や// [!code --]の記法で差分を表現する。
参考コード(MDX の rehypePlugins 設定):
// lib/note.ts 抜粋
import { compileMDX } from "next-mdx-remote/rsc";
import rehypePrettyCode from "rehype-pretty-code";
const rehypePrettyCodeOptions = {
theme: {
light: "vitesse-light",
dark: "vitesse-dark",
},
// dual theme を指定すると、CSS変数が --shiki-light, --shiki-dark の形式で出力される
// メディアクエリで切り替える必要がある
// 行番号などの装飾を ON にしたいときはここにオプションを追加
};
const { content } = await compileMDX({
source: fileContent,
options: {
parseFrontmatter: true,
mdxOptions: {
rehypePlugins: [[rehypePrettyCode, rehypePrettyCodeOptions]],
},
},
});pre[data-theme] と --shiki-* の扱い
rehype-pretty-code は pre/code に data-theme や style="--shiki-light: ..." のようなカスタムプロパティを埋め込む。
CSS 側ではこれらをそのまま color / background-color にマッピングすればよい。最低限は次のイメージ。
.markdown-body pre[data-theme] {
color: var(--shiki-light);
background: var(--shiki-light-bg);
}
@media (prefers-color-scheme: dark) {
.markdown-body pre[data-theme] {
color: var(--shiki-dark);
background: var(--shiki-dark-bg);
}
}Shiki だけではだめ? rehype-pretty-code と併用する理由
- Shiki の直呼びだけでもハイライト自体は可能。ただし、行番号や行ハイライト、コードフェンス → HTML 変換の自動処理は自前実装が必要。
rehype-pretty-codeを挟むと、MDX のコードフェンスをそのまま変換し、装飾用の DOM とクラス / 属性を用意してくれる。今回は行番号をオフにして最小構成で利用している。
実際の導入ステップ
1. 依存パッケージの入れ替え
既存の highlight.js と rehype-highlight を削除し、shiki と rehype-pretty-code をインストール。
yarn remove rehype-highlight highlight.js
yarn add shiki rehype-pretty-code2. CSS の修正
2-1. highlight.js の CSS import を削除
app/layout.tsx や app/globals.css などから、以下のような highlight.js のテーマ読み込みを削除。
/* 削除: highlight.js のテーマ */
@import 'highlight.js/styles/github.css';2-2. Shiki 用の CSS を追加
app/globals.css や専用のスタイルファイルに、Shiki の CSS 変数を使ったスタイルを追加。
/* Shiki のライト/ダークテーマ切り替え */
.markdown-body pre[data-theme] {
color: var(--shiki-light);
background: var(--shiki-light-bg);
padding: 1rem;
border-radius: 0.5rem;
overflow-x: auto;
}
@media (prefers-color-scheme: dark) {
.markdown-body pre[data-theme] {
color: var(--shiki-dark);
background: var(--shiki-dark-bg);
}
}
/* コードブロック内のテキストは親から継承 */
.markdown-body pre[data-theme] code {
color: inherit;
background: transparent;
font-size: 0.875rem;
line-height: 1.6;
}3. MDX 設定の変更
3-1. lib/note.ts の修正
compileMDX の rehypePlugins 設定を変更。
変更前:
import rehypeHighlight from 'rehype-highlight';
const { content } = await compileMDX({
source: fileContent,
options: {
mdxOptions: {
rehypePlugins: [rehypeHighlight],
},
},
});変更後:
import rehypePrettyCode from 'rehype-pretty-code';
const rehypePrettyCodeOptions = {
theme: {
light: "vitesse-light",
dark: "vitesse-dark",
},
};
const { content } = await compileMDX({
source: fileContent,
options: {
parseFrontmatter: true,
mdxOptions: {
rehypePlugins: [[rehypePrettyCode, rehypePrettyCodeOptions]],
},
},
});3-2. 他のMDX使用ファイルをの修正
このサイトでは lib/schedule.ts でも MDX を使っているため、同じ変更を適用。
4. コードフェンスの言語指定を確認
MDX ファイル内のコードブロックで、Shiki が認識する言語名を使用していることを確認する。
対応例:
- TypeScript:
```tsまたは```typescript - TSX:
```tsx - Diff:
```diff - Shell:
```shellまたは```bash - CSS:
```cssなど
注意: diff-tsx のような複合指定は Shiki では非対応。差分を表現したい場合は、@shikijs/transformers の transformerNotationDiff を使用するとのこと。この辺はまた別記事としてまとめたい。
まとめ
- ハイライトは Shiki + rehype-pretty-code で VS Code 互換の色を再現しつつ、MDX コードフェンスを自動変換。
- テーマは
vitesse-light/vitesse-dark、行番号なし。CSS はpre[data-theme]と--shiki-*を素直に使う。 - Shiki 直呼びでも構成可能だが、装飾や自動変換を含めて楽をしたいなら
rehype-pretty-code併用が現実的。