記事一覧に戻る
Next.jsでMarkdownを独自コンポーネントにマッピングしていい感じに表示する

Next.jsでMarkdownを独自コンポーネントにマッピングしていい感じに表示する

react-markdownを使ってMarkdownをReactコンポーネントに変換する方法を紹介します。remark-gfmプラグインの導入、Next.jsのLinkコンポーネントへの変換、react-syntax-highlighterによるコードブロックのシンタックスハイライトまで解説します。

Next.jsでMarkdownをいい感じにレンダリングしたい場面は多いと思います。今回はreact-markdownを使って、Markdownを独自のReactコンポーネントにマッピングする方法を紹介します。

react-markdownの基本

react-markdownはReactコンポーネントとして提供されていて、Markdown文字列を子要素として渡すだけで対応するHTMLタグに変換してくれます。

<ReactMarkdown>{ここにmarkdownをstringで置く}</ReactMarkdown>

remark-gfmプラグインの導入

GitHub Flavored Markdown(テーブルや取り消し線など)に対応するために、remark-gfmを導入します。

npm install remark-gfm
import remarkGfm from "remark-gfm";

export const Markdown = ({ children }: { children: string }) => {
  return (
    <ReactMarkdown
      remarkPlugins={[remarkGfm]}
    >
      {children}
    </ReactMarkdown>
  );
};

カスタムコンポーネントの適用

react-markdownでは、タグとコンポーネントの対応をkey-valueで指定できます。

import remarkGfm from "remark-gfm";

export const Markdown = ({ children }: { children: string }) => {
  return (
    <ReactMarkdown
      components={{
        h1: ({children}) => (<div>{children}</div>),
      }}
      remarkPlugins={[remarkGfm]}
    >
      {children}
    </ReactMarkdown>
  );
};

aタグをNext.jsのLinkコンポーネントに変換

内部リンクと外部リンクで挙動を分けるカスタムコンポーネントを作ります。

import Link from "next/link";

type AProps = {
  children: React.ReactNode;
  href?: string;
};

export const A = ({ href, children }: AProps) => {
  if (!href) {
    return <></>;
  }
  if (href.startsWith("/") || href.startsWith("#")) {
    return (
      <Link className="link-font-color" href={href}>
        {children}
      </Link>
    );
  } else {
    return (
      <a
        className="link-font-color"
        href={href}
        target="_blank"
        rel="noopener noreferrer"
      >
        {children}
      </a>
    );
  }
};

Markdownコンポーネントに適用します。

import remarkGfm from "remark-gfm";
import { A } from "@/components/a";

export const Markdown = ({ children }: { children: string }) => {
  return (
    <ReactMarkdown
      components={{
        a: A,
      }}
      remarkPlugins={[remarkGfm]}
    >
      {children}
    </ReactMarkdown>
  );
};

コードブロックのシンタックスハイライト

react-syntax-highlighterでコードブロックをハイライト表示します。

npm install react-syntax-highlighter
"use client";

import { CodeComponent } from "react-markdown/lib/ast-to-react";
import { Prism as SyntaxHighlighter } from "react-syntax-highlighter";
import { vscDarkPlus } from "react-syntax-highlighter/dist/esm/styles/prism";

export const Code: CodeComponent = ({ children, inline, className }) => {
  const language = className?.replace("language-", "").split(":")[0];
  const filename = className?.replace("language-", "").split(":")[1];
  if (inline) {
    return (
      <code className="mx-1 rounded-sm bg-gray-500 px-1 py-0.5 font-mono text-sm text-orange-400">
        {children}
      </code>
    );
  } else {
    return (
      <div className="my-4">
        {filename && (
          <div className="w-fit rounded-t bg-gray-600 px-2 py-1 font-mono text-xs text-white">
            {filename}
          </div>
        )}
        <SyntaxHighlighter
          language={language}
          style={vscDarkPlus}
          showLineNumbers={true}
          wrapLines={true}
          customStyle={{
            marginTop: "0",
          }}
        >
          {String(children).replace(/\n$/, "")}
        </SyntaxHighlighter>
      </div>
    );
  }
};

これでコードブロックにシンタックスハイライトが適用されるようになりました。

まとめ

react-markdownはプラグインなしでも柔軟にコンポーネントを適用できる便利なライブラリです。Next.jsのLinkコンポーネントへの変換やシンタックスハイライトなど、実用的なカスタマイズが簡単に実現できます。