📄

Next.jsでMarkdownを表示

2023/02/08に公開

はじめに

react-markdown を使い、GFMTeX を含み、Markdown を Next.js で表示する方法を紹介します。

ソースは以下を参照ください。

https://github.com/hayato94087/nextjs-markdown

Markdownを表示する方法

Markdown を HTML に変換して表示するにはいくつか方法がありますが、本記事では、react-markdown を利用し Markdown を Next.js で表示する方法を紹介します。

以下、Markdown を HTML に変換するライブラリの一覧です。

以下の記事で上記のライブラリの比較がされています。

https://chaika.hatenablog.com/entry/2022/12/02/083000

GFMとは

  • GFM とは GitHub Flavored Markdown の略です。
  • GFM とは GitHub が独自に拡張した Markdown のことです。
  • 自動リンクや、テーブルなどが GFM の拡張です。

以下が GFM の仕様です。

https://github.github.com/gfm/

以降で、GFM の仕様を含めた、Markdown を Next.js で表示する方法を紹介します。

Next.jsのプロジェクト作成

Next.js のプロジェクトを作成します。

terminal
$ yarn create next-app nextjs-tex --typescript --eslint --src-dir --import-alias "@/*"

実施結果

terminal
yarn create v1.22.19
warning package.json: No license field
[1/4] 🔍  Resolving packages...
[2/4] 🚚  Fetching packages...
[3/4] 🔗  Linking dependencies...
[4/4] 🔨  Building fresh packages...
success Installed "create-next-app@13.1.6" with binaries:
      - create-next-app
✔ Would you like to use experimental `app/` directory with this project? … No / Yes
Creating a new Next.js app in /Users/hayato94087/Work/nextjs-react-markdown.

Using yarn.

Installing dependencies:
- react
- react-dom
- next
- @next/font
- typescript
- @types/react
- @types/node
- @types/react-dom
- eslint
- eslint-config-next

yarn add v1.22.19
warning ../package.json: No license field
info No lockfile found.
[1/4] 🔍  Resolving packages...
[2/4] 🚚  Fetching packages...
[3/4] 🔗  Linking dependencies...
[4/4] 🔨  Building fresh packages...
success Saved lockfile.
success Saved 198 new dependencies.
info Direct dependencies
├─ @next/font@13.1.6
├─ @types/node@18.13.0
├─ @types/react-dom@18.0.10
├─ @types/react@18.0.27
├─ eslint-config-next@13.1.6
├─ eslint@8.33.0
├─ next@13.1.6
├─ react-dom@18.2.0
├─ react@18.2.0
└─ typescript@4.9.5
info All dependencies
├─ @babel/runtime@7.20.13
├─ @eslint/eslintrc@1.4.1
├─ @humanwhocodes/config-array@0.11.8
├─ @humanwhocodes/module-importer@1.0.1
├─ @humanwhocodes/object-schema@1.2.1
├─ @next/env@13.1.6
├─ @next/eslint-plugin-next@13.1.6
├─ @next/font@13.1.6
├─ @next/swc-darwin-x64@13.1.6
├─ @nodelib/fs.scandir@2.1.5
├─ @nodelib/fs.stat@2.0.5
├─ @nodelib/fs.walk@1.2.8
├─ @pkgr/utils@2.3.1
├─ @rushstack/eslint-patch@1.2.0
├─ @swc/helpers@0.4.14
├─ @types/json5@0.0.29
├─ @types/node@18.13.0
├─ @types/prop-types@15.7.5
├─ @types/react-dom@18.0.10
├─ @types/react@18.0.27
├─ @types/scheduler@0.16.2
├─ @typescript-eslint/parser@5.51.0
├─ @typescript-eslint/scope-manager@5.51.0
├─ @typescript-eslint/typescript-estree@5.51.0
├─ acorn-jsx@5.3.2
├─ acorn@8.8.2
├─ ajv@6.12.6
├─ ansi-regex@5.0.1
├─ ansi-styles@4.3.0
├─ argparse@2.0.1
├─ aria-query@5.1.3
├─ array-includes@3.1.6
├─ array-union@2.1.0
├─ array.prototype.flat@1.3.1
├─ array.prototype.tosorted@1.1.1
├─ ast-types-flow@0.0.7
├─ axe-core@4.6.3
├─ axobject-query@3.1.1
├─ balanced-match@1.0.2
├─ brace-expansion@1.1.11
├─ braces@3.0.2
├─ callsites@3.1.0
├─ caniuse-lite@1.0.30001451
├─ chalk@4.1.2
├─ client-only@0.0.1
├─ color-convert@2.0.1
├─ color-name@1.1.4
├─ concat-map@0.0.1
├─ cross-spawn@7.0.3
├─ csstype@3.1.1
├─ damerau-levenshtein@1.0.8
├─ deep-is@0.1.4
├─ define-lazy-prop@2.0.0
├─ emoji-regex@9.2.2
├─ enhanced-resolve@5.12.0
├─ es-get-iterator@1.1.3
├─ es-set-tostringtag@2.0.1
├─ es-to-primitive@1.2.1
├─ escape-string-regexp@4.0.0
├─ eslint-config-next@13.1.6
├─ eslint-import-resolver-node@0.3.7
├─ eslint-import-resolver-typescript@3.5.3
├─ eslint-module-utils@2.7.4
├─ eslint-plugin-import@2.27.5
├─ eslint-plugin-jsx-a11y@6.7.1
├─ eslint-plugin-react-hooks@4.6.0
├─ eslint-plugin-react@7.32.2
├─ eslint-scope@7.1.1
├─ eslint-utils@3.0.0
├─ eslint@8.33.0
├─ esquery@1.4.0
├─ esrecurse@4.3.0
├─ estraverse@5.3.0
├─ fast-deep-equal@3.1.3
├─ fast-glob@3.2.12
├─ fast-json-stable-stringify@2.1.0
├─ fast-levenshtein@2.0.6
├─ fastq@1.15.0
├─ file-entry-cache@6.0.1
├─ fill-range@7.0.1
├─ find-up@5.0.0
├─ flat-cache@3.0.4
├─ flatted@3.2.7
├─ function.prototype.name@1.1.5
├─ get-symbol-description@1.0.0
├─ get-tsconfig@4.4.0
├─ glob-parent@6.0.2
├─ glob@7.1.7
├─ globalthis@1.0.3
├─ globalyzer@0.1.0
├─ globby@13.1.3
├─ globrex@0.1.2
├─ graceful-fs@4.2.10
├─ grapheme-splitter@1.0.4
├─ has-bigints@1.0.2
├─ has-flag@4.0.0
├─ has-proto@1.0.1
├─ import-fresh@3.3.0
├─ imurmurhash@0.1.4
├─ is-bigint@1.0.4
├─ is-boolean-object@1.1.2
├─ is-callable@1.2.7
├─ is-core-module@2.11.0
├─ is-date-object@1.0.5
├─ is-docker@2.2.1
├─ is-extglob@2.1.1
├─ is-map@2.0.2
├─ is-negative-zero@2.0.2
├─ is-number-object@1.0.7
├─ is-number@7.0.0
├─ is-path-inside@3.0.3
├─ is-set@2.0.2
├─ is-string@1.0.7
├─ is-symbol@1.0.4
├─ is-typed-array@1.1.10
├─ is-weakmap@2.0.1
├─ is-weakref@1.0.2
├─ is-weakset@2.0.2
├─ is-wsl@2.2.0
├─ isexe@2.0.0
├─ js-sdsl@4.3.0
├─ js-tokens@4.0.0
├─ json-schema-traverse@0.4.1
├─ json-stable-stringify-without-jsonify@1.0.1
├─ json5@1.0.2
├─ jsx-ast-utils@3.3.3
├─ language-subtag-registry@0.3.22
├─ language-tags@1.0.5
├─ locate-path@6.0.0
├─ lodash.merge@4.6.2
├─ loose-envify@1.4.0
├─ lru-cache@6.0.0
├─ micromatch@4.0.5
├─ minimatch@3.1.2
├─ minimist@1.2.7
├─ ms@2.1.2
├─ nanoid@3.3.4
├─ natural-compare@1.4.0
├─ next@13.1.6
├─ object-assign@4.1.1
├─ object-inspect@1.12.3
├─ object-is@1.1.5
├─ object.hasown@1.1.2
├─ open@8.4.0
├─ optionator@0.9.1
├─ p-limit@3.1.0
├─ p-locate@5.0.0
├─ parent-module@1.0.1
├─ path-exists@4.0.0
├─ path-key@3.1.1
├─ path-type@4.0.0
├─ picomatch@2.3.1
├─ postcss@8.4.14
├─ prop-types@15.8.1
├─ punycode@2.3.0
├─ queue-microtask@1.2.3
├─ react-dom@18.2.0
├─ react-is@16.13.1
├─ react@18.2.0
├─ regenerator-runtime@0.13.11
├─ regexpp@3.2.0
├─ resolve-from@4.0.0
├─ reusify@1.0.4
├─ rimraf@3.0.2
├─ run-parallel@1.2.0
├─ safe-regex-test@1.0.0
├─ scheduler@0.23.0
├─ shebang-command@2.0.0
├─ shebang-regex@3.0.0
├─ slash@4.0.0
├─ source-map-js@1.0.2
├─ stop-iteration-iterator@1.0.0
├─ string.prototype.matchall@4.0.8
├─ string.prototype.trimend@1.0.6
├─ string.prototype.trimstart@1.0.6
├─ strip-ansi@6.0.1
├─ strip-bom@3.0.0
├─ strip-json-comments@3.1.1
├─ styled-jsx@5.1.1
├─ supports-color@7.2.0
├─ synckit@0.8.5
├─ tapable@2.2.1
├─ text-table@0.2.0
├─ tiny-glob@0.2.9
├─ to-regex-range@5.0.1
├─ tsconfig-paths@3.14.1
├─ tsutils@3.21.0
├─ type-check@0.4.0
├─ type-fest@0.20.2
├─ typed-array-length@1.0.4
├─ typescript@4.9.5
├─ unbox-primitive@1.0.2
├─ uri-js@4.4.1
├─ which-collection@1.0.1
├─ which@2.0.2
├─ word-wrap@1.2.3
├─ yallist@4.0.0
└─ yocto-queue@0.1.0
✨  Done in 8.00s.

Initializing project with template: default

Initialized a git repository.

Success! Created nextjs-react-markdown at /Users/hayato94087/Work/nextjs-react-markdown

✨  Done in 12.22s.

react-markdown

react-markdown は、Markdown を React コンポーネントに変換するライブラリです。

https://github.com/remarkjs/react-markdown

インストール

react-markdown をインストールします。

terminal
$ yarn add react-markdown

実施結果

yarn add v1.22.19
warning ../package.json: No license field
[1/4] 🔍  Resolving packages...
[2/4] 🚚  Fetching packages...
[3/4] 🔗  Linking dependencies...
warning " > react-markdown@8.0.5" has unmet peer dependency "@types/react@>=16".
warning " > react-markdown@8.0.5" has unmet peer dependency "react@>=16".
[4/4] 🔨  Building fresh packages...
success Saved lockfile.
success Saved 15 new dependencies.
info Direct dependencies
└─ remark-gfm@3.0.1
info All dependencies
├─ markdown-table@3.0.3
├─ mdast-util-gfm-footnote@1.0.2
├─ mdast-util-gfm-strikethrough@1.0.3
├─ mdast-util-gfm-table@1.0.7
├─ mdast-util-gfm-task-list-item@1.0.2
├─ mdast-util-gfm@2.0.2
├─ mdast-util-phrasing@3.0.1
├─ micromark-extension-gfm-autolink-literal@1.0.3
├─ micromark-extension-gfm-footnote@1.0.4
├─ micromark-extension-gfm-strikethrough@1.0.4
├─ micromark-extension-gfm-table@1.0.5
├─ micromark-extension-gfm-tagfilter@1.0.1
├─ micromark-extension-gfm-task-list-item@1.0.3
├─ micromark-extension-gfm@2.0.1
└─ remark-gfm@3.0.1
✨  Done in 3.47s.

ページの作成

以下の通り、src/pages/markdown-test-1.tsx を修正します。

src/pages/markdown-test-1.tsx
import ReactMarkdown from 'react-markdown';

const markdownString = `
# Markdown

Lorem ipsum dolor sit amet consectetur, adipisicing elit. Magni, nemo!
`;

const Home = () => {
  return <ReactMarkdown>{markdownString}</ReactMarkdown>;
};

export default Home;

動作を確認

ローカルサーバを起動します。

terminal
$ yarn dev

無事、Markdown が表示されています。

remark-gfm

react-gfm は、GFM をサポートするためのライブラリです。

https://github.com/remarkjs/remark-gfm

インストール

remark-gfm をインストールします。

terminal
$ yarn add remark-gfm

実施結果

yarn add v1.22.19
warning ../package.json: No license field
[1/4] 🔍  Resolving packages...
[2/4] 🚚  Fetching packages...
[3/4] 🔗  Linking dependencies...
[4/4] 🔨  Building fresh packages...

success Saved lockfile.
success Saved 42 new dependencies.
info Direct dependencies
└─ react-markdown@8.0.5
info All dependencies
├─ @types/debug@4.1.7
├─ @types/ms@0.7.31
├─ bail@2.0.2
├─ character-entities@2.0.2
├─ comma-separated-tokens@2.0.3
├─ dequal@2.0.3
├─ diff@5.1.0
├─ extend@3.0.2
├─ hast-util-whitespace@2.0.1
├─ inline-style-parser@0.1.1
├─ is-plain-obj@4.1.0
├─ kleur@4.1.5
├─ mdast-util-definitions@5.1.2
├─ mdast-util-from-markdown@1.3.0
├─ mdast-util-to-hast@12.3.0
├─ mdast-util-to-string@3.1.1
├─ micromark-core-commonmark@1.0.6
├─ micromark-factory-destination@1.0.0
├─ micromark-factory-label@1.0.2
├─ micromark-factory-title@1.0.2
├─ micromark-factory-whitespace@1.0.0
├─ micromark-util-classify-character@1.0.0
├─ micromark-util-combine-extensions@1.0.0
├─ micromark-util-decode-string@1.0.2
├─ micromark-util-html-tag-name@1.1.0
├─ micromark-util-sanitize-uri@1.1.0
├─ micromark@3.1.0
├─ mri@1.2.0
├─ property-information@6.2.0
├─ react-is@18.2.0
├─ react-markdown@8.0.5
├─ remark-parse@10.0.1
├─ remark-rehype@10.1.0
├─ sade@1.8.1
├─ space-separated-tokens@2.0.2
├─ style-to-object@0.4.1
├─ trim-lines@3.0.1
├─ trough@2.1.0
├─ unist-util-generated@2.0.1
├─ unist-util-position@4.0.4
├─ unist-util-visit-parents@5.1.3
└─ vfile-message@3.1.4
✨  Done in 3.77s.

ページの作成

以下の通り、src/pages/markdown-test-2.tsx を修正します。

src/pages/markdown-test-2.tsx
import ReactMarkdown from 'react-markdown';
import remarkGfm from 'remark-gfm';

const markdownString = `
# GFM

Lorem ipsum dolor sit amet consectetur, adipisicing elit. Magni, nemo!

## Autolink literals

www.example.com, https://example.com, and contact@example.com.

## Footnote

A note[^1]

[^1]: Big note.

## Strikethrough

~one~ or ~~two~~ tildes.

## Table

| a | b  |  c |  d  |
| - | :- | -: | :-: |

## Tasklist

* [ ] to do
* [x] done
`;

const Home = () => {
  return (
    <ReactMarkdown remarkPlugins={[remarkGfm]}>{markdownString}</ReactMarkdown>
  );
};

export default Home;

動作を確認

ローカルサーバを起動します。

terminal
$ yarn dev

無事、GFM が表示されています。

remark-math, rehype-katex

  • remark-math は、Markdown の数式を処理するためのライブラリです。
  • rehype-katex は、数式をレンダリングするためのライブラリです。

インストール

remark-mathrehype-katex をインストールします。

terminal
$ yarn add remark-math rehype-katex

実施結果

yarn add remark-math rehype-katex
yarn add v1.22.19
warning ../package.json: No license field
[1/4] 🔍  Resolving packages...
[2/4] 🚚  Fetching packages...
[3/4] 🔗  Linking dependencies...
[4/4] 🔨  Building fresh packages...
success Saved lockfile.
success Saved 16 new dependencies.
info Direct dependencies
├─ rehype-katex@6.0.2
└─ remark-math@5.1.1
info All dependencies
├─ hast-util-from-parse5@7.1.1
├─ hast-util-is-element@2.1.3
├─ hast-util-parse-selector@3.1.1
├─ hast-util-to-text@3.1.2
├─ hastscript@7.2.0
├─ katex@0.15.6
├─ mdast-util-math@2.0.2
├─ micromark-extension-math@2.0.2
├─ parse5@6.0.1
├─ rehype-katex@6.0.2
├─ rehype-parse@8.0.4
├─ remark-math@5.1.1
├─ unist-util-find-after@4.0.1
├─ unist-util-remove-position@4.0.2
├─ vfile-location@4.1.0
└─ web-namespaces@2.0.1
✨  Done in 4.33s.

ページの作成

以下の通り、src/pages/markdown-test-3.tsx を修正します。

src/pages/markdown-test-3.tsx
import ReactMarkdown from 'react-markdown';
import remarkMath from 'remark-math';
import rehypeKatex from 'rehype-katex';
import 'katex/dist/katex.min.css';

const markdownString = `
# TeX

x^2 + y^2 = 1 をインライン表示すると $x^2 + y^2 = 1$ になります。

$$ S=\\sum_{n=1}^\\infty a_n $$

$$\\frac{1}{2} $$

`;

const Home = () => {
  return (
    <ReactMarkdown remarkPlugins={[remarkMath]} rehypePlugins={[rehypeKatex]}>
      {markdownString}
    </ReactMarkdown>
  );
};

export default Home;

動作を確認

ローカルサーバを起動します。

terminal
$ yarn dev

無事、TeX が表示されています。

まとめ

Next.js で react-markdown を使い Markdown、GFM、TeX を表示する方法を紹介しました。

参考

react-markdown のリポジトリです。
https://github.com/remarkjs/react-markdown

react-markdown の npm です。
https://www.npmjs.com/package/react-markdown

React における Markdown の比較がされている記事です。
https://chaika.hatenablog.com/entry/2022/12/02/083000

remark-gfm のリポジトリです。
https://github.com/remarkjs/remark-gfm

marked のリポジトリです。
https://github.com/markedjs/marked

markdown-it のリポジトリです。
https://github.com/markdown-it/markdown-it

remark のリポジトリです。
https://github.com/remarkjs/remark

Discussion