[Next.js MDX] Markdown形式のテーブルを表示する(remark-gfm + next-mdx-remote)

問題:MDXでテーブルが文字列として表示される

Next.jsプロジェクトでMDXファイル内にMarkdown形式のテーブルを書いたところ、テーブルとして表示されず文字列のまま表示されてしまった。

| column_name | is_identity | identity_generation | column_default                      |
| ----------- | ----------- | ------------------- | ----------------------------------- |
| onidazo_id  | NO          | null                | nextval('onidazo_id_seq'::regclass') |

この記事では、このようなMarkdown形式のテーブル(パイプテーブル)を正しく<table>要素としてレンダリングできるようにした実装手順を紹介する。

環境

  • Next.js 16.0.7 (App Router)
  • TypeScript
  • next-mdx-remote 5.0.0 (MDXのレンダリング)
  • rehype-pretty-code 0.14.1 (コードブロックの整形)
  • github-markdown-css 5.8.1 (GitHubスタイルのMarkdown CSS)
  • shiki 3.19.0 (シンタックスハイライト)

原因:GitHub Flavored Markdownのサポートが必要だった

調べてみると、Markdown形式のテーブルは GitHub Flavored Markdown (GFM) の拡張構文であり、標準のMarkdownパーサーではサポートされていないことが分かった。そのため、remark-gfmプラグインを追加する必要があった。

remarkとrehypeの違い

  • remark: Markdown → mdast(Markdown抽象構文木)変換を担当
  • rehype: HTML操作に特化(コードブロックのシンタックスハイライトなど)

テーブル構文の解析はMarkdownレベルで行われるため、remarkプラグインremark-gfm)を使用した。

なお、このプロジェクトでは既にコードブロックのシンタックスハイライトのためにrehype-pretty-codeshikirehypeプラグインとして使用していたが、remarkとrehypeは処理のタイミングが異なるため、両方を併用できた。

実装手順

1. remark-gfmパッケージをインストール

まず、remark-gfmパッケージをインストールした。

yarn add remark-gfm

2. mdxOptionsにremarkPluginsを追加

今回のプロジェクトではnext-mdx-remotecompileMDXを使ってMDXをレンダリングしていたので、mdxOptionsremarkPluginsを追加した。

既にrehype-pretty-codeshikiを使用していたため、既存のrehypePluginsはそのままで、remarkPlugins配列にremarkGfmを追加した。

// lib/note.ts
import { compileMDX } from "next-mdx-remote/rsc";
import rehypePrettyCode from "rehype-pretty-code";
import remarkGfm from "remark-gfm";
 
const { content, frontmatter } = await compileMDX({
  source: fileContent,
  options: {
    parseFrontmatter: true,
    mdxOptions: {
      remarkPlugins: [remarkGfm],  // ← remarkプラグインとして追加
      rehypePlugins: [
        // 既存のrehypeプラグイン(そのまま残す)
        [
          rehypePrettyCode,
          {
            theme: {
              light: "vitesse-light",
              dark: "vitesse-dark",
            },
          },
        ],
      ],
    },
  },
  components: {
    // カスタムコンポーネント
  },
});

3. テーブルのスタイリング(オプション)

次に、テーブルにカスタムスタイルを適用するため、MDXコンポーネントをカスタマイズした。

// app/mdx-components.tsx
import type { MDXComponents } from "mdx/types";
 
const components: MDXComponents = {
  table: ({ children }) => (
    <div>
      <table>
        {children}
      </table>
    </div>
  ),
  thead: ({ children }) => (
    <thead>{children}</thead>
  ),
  tbody: ({ children }) => (
    <tbody>{children}</tbody>
  ),
  tr: ({ children }) => (
    <tr>{children}</tr>
  ),
  th: ({ children }) => (
    <th>
      {children}
    </th>
  ),
  td: ({ children }) => (
    <td>{children}</td>
  ),
};
 
export function useMDXComponents(): MDXComponents {
  return components;
}

このカスタムコンポーネントをcompileMDXcomponentsに追加した。

// lib/note.ts
import { useMDXComponents } from "@/app/mdx-components";
 
const { content, frontmatter } = await compileMDX({
  source: fileContent,
  options: {
    // ...
  },
  components: {
    ...useMDXComponents(),  // ← カスタムコンポーネントを展開
    // その他のコンポーネント
  },
});

動作確認

開発サーバーを再起動して動作確認した。

yarn dev

重要: Next.jsの設定ファイル(next.config.mjslib/note.ts)を変更した場合は、必ず再起動が必要である。

トラブルシューティング

テーブルが正しく表示されない場合は、以下を試すと良い。

  1. 開発サーバーを完全に停止して再起動
  2. ブラウザのキャッシュをクリア(ハードリロード: Cmd+Shift+R)
  3. .nextフォルダを削除して再ビルド
rm -rf .next
yarn dev

結果

これで、Markdown形式のテーブルが正しくHTMLの<table>要素としてレンダリングされるようになった。

column_name is_identity identity_generation column_default
onidazo_id NO null nextval('onidazo_id_seq'::regclass')

remark-gfmで使えるその他の機能

remark-gfmを追加したことで、テーブル以外にも以下のGitHub Flavored Markdown機能が使えるようになった。

取り消し線

~~text~~で取り消し線を表示できる。

  • 取り消された文字

タスクリスト

- [ ]- [x]でチェックボックス付きリストを表示できる。

  • 未完了のタスク
  • 完了したタスク

自動リンク

URLを書くだけで自動的にリンクになる。

脚注

[^1]のような記法で脚注を追加できる。1

まとめ

  • Markdown形式のテーブルを表示するにはremark-gfmプラグインが必要
  • 今回はnext-mdx-remoteを使用していたのでmdxOptions.remarkPluginsに追加した
  • (参考)@next/mdxを使用している場合はnext.config.mjsに追加する
  • テーブルのスタイリングはmdx-components.tsxでカスタマイズした
  • 設定変更後は開発サーバーの再起動が必須

参考リンク

Footnotes

  1. これは脚注の例である。脚注は自動的にページ下部に表示される。