markdown-content について
markdown-contentは、Next.js App RouterでMDXコンテンツページを構築するためのスキルです。MDXとReactコンポーネントのコード表示を共通の処理とスタイルで揃えます。
主な機能
- 01MDXコレクションとスキーマの定義
- 02コンテンツページの読み込みと解決
- 03ビルド時と実行時のコードハイライト
- 04タブ付きコードブロックとコピー機能
- 05本文とコードブロックのスタイル設定
活用シーン
- 01ブログや変更履歴の構築
- 02法務ページやナレッジベースの作成
- 03シンタックスハイライトの表示統一
- 04コード例へのコピー機能の追加
インストール
コマンドをコピーしてターミナルで実行します。
npx skills add https:/ / github.com/ joyco-studio/ skills --skill markdown-contentインストール後、Claude Code で/markdown-contentを入力して読み込まれたか確認します。
詳細
Markdown コンテンツページ — Next.js
このスキルでは、MDX を使用して、Next.js App Router プロジェクトでコンテンツ主体のページ(ブログ記事、変更履歴、法的ページ、ナレッジベース)を構築する方法を定義します。
目標は、次の間で視覚的および動作上の一貫性を保つことです。
.mdxファイルに記述された Markdown コードブロック(ビルド時に処理)- ページ上にレンダリングされるカスタム React コードコンポーネント(実行時にハイライト)
スタックの概要
| レイヤー | ライブラリ | 目的 |
|---|---|---|
| コンテンツフレームワーク | fumadocs-mdx | MDX コレクション定義、フロントマターのスキーマ、ビルド時の処理 |
| コンテンツランタイム | fumadocs-core | ソースローダー、ページツリー、スラッグの解決 |
| シンタックスハイライト | shiki + rehype-pretty-code | ビルド時(MDX)と実行時(コンポーネント)のデュアルテーマコードハイライト |
| スキーマ検証 | zod | デフォルト値とオプションフィールドを備えた、型付きフロントマター |
| スタイリング | Tailwind CSS v4 | ユーティリティファーストのスタイル、カスタム prose CSS、コードブロックのスタイル |
| フレームワーク | Next.js(App Router) | コンテンツページ用のキャッチオール [[...slug]] を使ったファイルベースのルーティング |
npm install fumadocs-mdx fumadocs-core shiki rehype-pretty-code zod
プロジェクト構成
├── app/
│ ├── layout.tsx # Root layout
│ ├── styles/
│ │ ├── globals.css # Tailwind + stylesheet imports (order matters)
│ │ ├── prose.css # Self-contained typography for .prose class
│ │ ├── shiki.css # Code block styling (themes, line numbers, highlights)
│ │ └── theming.css # Color variables (light/dark) — imported first
│ └── (content)/ # Route group for content pages
│ ├── layout.tsx # Content layout wrapper
│ └── [[...slug]]/
│ └── page.tsx # Catch-all page rendering MDX content
├── content/
│ ├── meta.json # Top-level page ordering
│ ├── index.mdx # Landing page
│ └── <category>/ # e.g. blog/, legal/, changelog/
│ ├── meta.json # Category page ordering
│ └── <page>.mdx # Individual content pages
├── components/ # Code block components (see Code Block Components)
├── lib/
│ ├── source.ts # fumadocs-core source loader
│ ├── shiki.ts # Shared shiki config, transformers, highlightCode()
│ └── cn.ts # clsx + tailwind-merge utility
├── mdx-components.tsx # MDX element → React component mappings
└── source.config.ts # fumadocs-mdx collection + rehype pipeline
コンテンツコレクション
source.config.ts
次の 2 つを定義します。
- コンテンツコレクション —
content/ディレクトリを参照するdefineDocs()と、Zod で拡張したフロントマターのスキーマです。 - MDX パイプライン — デフォルトの fumadocs ハイライターを置き換える
rehype-pretty-codeを指定したdefineConfig()です。
主なパターン:
rehypePlugins内でplugins.shift()を呼び出して組み込みの fumadocs シンタックスハイライターを削除し、その後plugins.push()でrehype-pretty-codeを追加します。- Shiki のデュアルテーマ(ライトとダークを 1 つずつ)を設定して、両方の配色をマークアップに埋め込みます。どちらを表示するかは CSS で切り替えます。
- カスタムの Shiki トランスフォーマーを渡して、コンポーネントがレンダリング時に読み取れるメタデータ(ソーステキスト)を AST に追加します。
onVisitTitleを使ってコードブロックのタイトル要素にnot-proseを追加し、prose のタイポグラフィが適用されないようにします。
next.config.ts
fumadocs-mdx/next の createMDX() で Next.js の設定をラップします。
lib/source.ts
fumadocs-core/source から loader() を作成し、baseUrl とコレクションの .toFumadocsSource() を指定します。ソースと、InferPageType を使用した Page 型をエクスポートします。
コンテンツファイル
MDX フロントマター
すべての .mdx ファイルは、source.config.ts の Zod スキーマで検証される YAML フロントマターから始まります。インポートは、閉じる --- の後に記述します。標準の Markdown に加えて、React コンポーネントをインラインで使用できます。コードブロックには言語タグ付きのフェンス構文を使用し、title="filename.tsx" というメタ文字列を追加するとタイトルバーが表示されます。
meta.json
ディレクトリ内のページの順序を制御します — スラッグ文字列の配列です。
{
"pages": ["index", "terms-of-service", "privacy-policy"]
}
キャッチオールコンテンツページ
[[...slug]]/page.tsx ルートは source.getPage(params.slug) でスラッグを解決し、見つからない場合は notFound() を返して、次をレンダリングします。
<article className="prose">
<h1>{page.data.title}</h1>
<p className="text-muted-foreground">{page.data.description}</p>
<MDX components={getMDXComponents()} />
</article>
静的生成には、source.generateParams() を使用して generateStaticParams をエクスポートします。
Shiki の設定
lib/shiki.ts
シンタックスハイライトの設定を一元管理します。MDX パイプライン(ビルド時)とカスタムコードコンポーネント(実行時)の両方で使用します。
このモジュールでは、次をエクスポートします。
-
言語マップ — ファイル拡張子(
ts、tsx、shなど)を Shiki の言語識別子に対応付けるRecord<string, BuiltinLanguage>です。さらに、getLanguageFromExtension()ヘルパーも用意します。 -
Shiki トランスフォーマー — MDX のコンパイル時に実行され、コンポーネントがレンダリング時に読み取るカスタムプロパティを AST ノードに追加します。コンポーネントのマッピングが使用できるよう、コピー用ボタンのための元のソース文字列を追加します。
-
highlightCode(code, language)— MDX パイプラインと同じデュアルテーマを使用し、codeToHtml()でハイライトする非同期関数です。これは一貫性を保つための仕組みです。ビルド時と実行時のどちらのハイライトにも同じ Shiki 設定を使用し、同じデータ属性(data-line-numbers、data-line)を生成することで、どこでも視覚的な出力が一致します。
MDX コンポーネントのマッピング
mdx-components.tsx
MDX コンパイラーが出力する HTML 要素を React コンポーネントにマッピングします。生の Markdown とコンポーネントシステムをつなぐ役割を担います。
主なマッピング:
-
code— スタイル付きのインライン<code>要素をレンダリングします。 -
pre— props からトランスフォーマーのメタデータを読み取ります。生のソーステキストを使って、CopyButtonを重ねた<pre>をレンダリングします。 -
img— 最適化のために、必要に応じて Next.js のImageでラップします。
処理の流れ:作成者がフェンス付きコードブロックを書く → rehype-pretty-code と shiki が構文トークン付きの <pre><code> を生成する → shiki のトランスフォーマーがメタデータを付与する → MDX がこれらのマッピングを呼び出す → コンポーネントがメタデータを読み取り、それに応じてレンダリングする。
コードブロックコンポーネント
コード表示システム用に、以下のクライアントコンポーネントを作成します。
CodeBlock
単一のコード表示です。highlightedCode(HTML 文字列)、language、任意の title、コピー用の rawCode、maxHeight、wrap を受け取ります。任意の <figcaption> タイトルバー、dangerouslySetInnerHTML によるハイライト済み HTML、CopyButton を含む <figure data-rehype-pretty-code-figure> をレンダリングします。
CodeBlockTabs
複数ファイルのタブ付きコード表示です。{ filename, highlightedCode, rawCode } の配列をタブとして受け取ります。制御されたタブ状態を使用します。<figcaption> 内にタブと、アクティブなタブ用の CopyButton をレンダリングします。
CopyButton
視覚的なフィードバックを備えたクリップボードボタンです。navigator.clipboard.writeText() を呼び出す useCopyToClipboard フックを使用し、約 2 秒間チェックマークを表示します。右上に絶対位置で配置し、グループにホバーしたときに不透明度が切り替わります。
スタイリングのアーキテクチャ
globals.css — インポート順序
@import 'tailwindcss';
@import './theming.css';
@import './prose.css';
@import './shiki.css';
theming.css は最初に読み込む必要があります。prose と shiki が使用するカラー変数を定義するためです。
theming.css — カラー変数
shiki.css とコンポーネントのクラスで使用するライトモードおよびダークモードの変数を定義します。
--color-code— コードブロックの背景色--color-code-foreground— コードブロックのテキスト色--color-code-highlight— ハイライトされた行の背景色
prose.css — 自己完結型のタイポグラフィ
@tailwindcss/typography や UI フレームワークに依存しない、独自に作成した .prose クラスです。:where() セレクターを使い、詳細度をゼロに保ちます。埋め込みコンポーネントを除外するための手段として .not-prose を使用します。
最低限、本文の色と行の高さ、見出し(h1–h6)、段落、リンク、strong、リスト(ul/ol/li)、引用、水平線、表、コードの figure(figure[data-rehype-pretty-code-figure])を対象にします。
デザイン上の決定事項:
- 読みやすさを柔らかくするため、本文の不透明度を下げます(例:
color-mixを使って--foregroundの 70% にする) - 見出しには
--foregroundをそのまま使い、行の高さを詰めます - コードの figure には上下のマージンを設定し、最初と最後の子要素にはリセットを適用します
shiki.css — コードブロックのスタイル
すべてのコードブロックに統一したスタイルを適用します。MDX がレンダリングしたコードブロックとコンポーネントがレンダリングしたコードブロックのどちらにも、同じデータ属性を使用します。
主なルール:
- デュアルテーマの切り替え —
.dark内では、[data-rehype-pretty-code-figure] spanのcolorをvar(--shiki-dark)に切り替えます。ライトテーマがデフォルトです。shiki は両方のカラーセットを CSS 変数として埋め込みます。 - コンテナー —
[data-rehype-pretty-code-figure]の背景色に--color-codeを使い、角を丸くして、はみ出した部分を隠します。 - 行番号 —
[data-line-numbers] [data-line]::beforeに CSS カウンターを使います。横スクロール時にも表示されるよう、左側にスティッキー配置します。 - 行のハイライト —
[data-highlighted-line]に控えめな背景色と左ボーダーを設定します。 - タイトルバー —
[data-rehype-pretty-code-title]に等幅フォント、控えめな色、下ボーダーを設定します。 - テキストの折り返し —
[data-wrap='true'] codeでwhite-space: pre-wrapを有効にします。
一貫性モデル
フェンス付き Markdown コードブロックと <CodeBlock> React コンポーネントが、同一の視覚的出力を生成することを保証します。これは次の方法で実現しています。
- 同じ Shiki テーマ — MDX パイプラインと
highlightCode()で、同じデュアルテーマの組み合わせを使用します。 - 同じ CSS ターゲット — どちらも
shiki.cssでスタイル設定された[data-rehype-pretty-code-figure]要素を生成します。 - 同じデータ属性 — どちらの経路でも
data-line、data-line-numbers、data-highlighted-line、data-wrapを使用します。 - 同じカラー変数 —
theming.cssの--color-codeファミリーが全体に適用されます。 - 共通のコピー用ボタン — すべてのコード表示で同じ
CopyButtonコンポーネントを使用します。
実装チェックリスト
- 依存関係をインストールします:
fumadocs-mdx、fumadocs-core、shiki、rehype-pretty-code、zod - frontmatter スキーマと rehype-pretty-code パイプラインを含む
source.config.tsを作成します createMDX()でnext.config.tsをラップします- ソースローダーを含む
lib/source.tsを作成します - 言語マップ、トランスフォーマー、
highlightCode()を含むlib/shiki.tsを作成します preとcodeをカスタムコンポーネントにマッピングするmdx-components.tsxを作成します- コードブロックコンポーネント(
CodeBlock、CodeBlockTabs、CopyButton)を作成します prose.css(自己完結型のタイポグラフィ)とshiki.css(コードブロックのスタイリング)を設定します--color-codeファミリーを含むテーマ変数をtheming.cssで定義しますmeta.jsonファイルを含むコンテンツディレクトリを作成します- キャッチオールの
[[...slug]]/page.tsxルートを作成します globals.cssでスタイルシートを正しい順序(theming → prose → shiki)でインポートします