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 を使用するとのこと。この辺はまた別記事としてまとめたい。※追記:別記事にまとめました([Next.js MDX] シンタックスハイライト付き差分表示を実装する(@shikijs/transformers))
まとめ
- ハイライトはShiki + rehype-pretty-codeでVS Code互換の色を再現しつつ、MDXコードフェンスを自動変換。
- テーマは
vitesse-light/vitesse-dark、行番号なし。CSSはpre[data-theme]と--shiki-*を素直に使う。 - Shiki直呼びでも構成可能だが、装飾や自動変換を含めて楽をしたいなら
rehype-pretty-code併用が現実的。