本記事のゴール
Markdownで書いた項目を、Next.jsプロジェクト内でカンタンに呼び出せるようにしたい。
作業
要件定義
以下のようなMarkdown形式の項目があり、こちらをNext.jsのページでコンテンツとして表示したいです。
# タイトル
内容がここに入ります。
## 見出しレベル2
[リンク](https://moldspoon.jp/blog)は当ブログのトップページに貼ってあります。
.mdxファイル形式を使い、並列でファイルを作っていくことでページを作っていくアプリ構成も取れる のですが、今回はもうちょっと複雑なWebサービスを開発しており、ページの一部分だけマークダウン形式としたいので ちょっと要件がずれてきます。
そのような時のために今回のコンポーネントを作成しました。
markedを使って実装
今回は下記のmarked というmarkdown => html置換ライブラリを使用します。
markedのデモページが結構すごい
markedのデモページはこちら
どのように置換するのかを、リアルタイムでプレビューして見せてくれます。
個人的に便利だなと思うのうは、Optionsを変更するとそれに合わせて出力内容を変えてくれたり
微妙に出力方式を変えたい時に事前に確認できる。
出力後のhtmlソースコードが見れるところですかね。
あとで、カスタマイズをする時に役に立ちます。
詳しいドキュメントはこちらのようです。
下準備〜ライブラリインストール
Next.jsプロジェクトのルートディレクトリで以下のコマンドを実行
yarn add marked
これだけです。2011年からある古いライブラリのようなのですが、typescriptにもしっかり対応しているようです。(自分のユースケースでは特にtypescriptは使いませんでした。)
コンポーネントを作成
下記のように実際のコンポーネントを組んでいきます。
import { marked } from 'marked';
import React from 'react';
var renderer = new marked.Renderer();
renderer.link = function (href, title, text) {
var link = marked.Renderer.prototype.link.call(this, href, title, text);
return link.replace("<a", "<a target='_blank' ");
};
const MarkdownView = ({
markdownStringBody,
}: {
markdownStringBody: string
}) => {
const options = {
gfm: true,
breaks: true,
pedantic: true,
smartLists: true,
smartypants: true,
renderer: renderer
};
const renderMarkdown = (text: string) => {
return marked(text, options);
};
return <div dangerouslySetInnerHTML={{ __html: renderMarkdown(markdownStringBody) }} />
}
export default MarkdownView
呼び出し方は、
<MarkdownView markdownStringBody={markdownStringBody} />
のようにして、マークダウンで入力されたコンテンツをそのままMarkdownViewに引数として渡します。
以下で詳しく解説します。
markedに渡して、単純に出力する部分
クラスの先頭で下記のようにシンプルにstring
形式で受け取り、
const MarkdownView = ({
markdownStringBody,
}: {
markdownStringBody: string
}) => {
//...
}
optionsを定義。marked()
メソッドに内容とオプションを渡してmarkdownをhtmlに変換するように します。
const options = {
gfm: true,
breaks: true,
pedantic: true,
smartLists: true,
smartypants: true,
// renderer: renderer <= あとで解説します。
};
const renderMarkdown = (text: string) => {
return marked(text, options);
};
const MarkdownView = ({
markdownStringBody,
}: {
markdownStringBody: string
}) => {
// ...
return <div dangerouslySetInnerHTML={{ __html: renderMarkdown(markdownStringBody) }} />
}
export default MarkdownView
最後にhtmlとしてそのまま変換後のコンテンツを出力します。dangerouslySetInnerHTML
はCGM(ユーザー投稿があるコンテンツ)ではインジェクションの被害を受ける可能性があるので、この方法は
使えないですが、マークダウン形式でユーザーが入稿することは多分ほとんどないので大きな問題はないかなと思っています。
出力後に、htmlを置換する部分
私のケースでは、aリンクを必ず外部リンクにしたかった ので、全てのマークダウンコンテンツで変換するようにします。
それを行なっているのが以下の箇所です。
import { marked } from 'marked';
// ...
var renderer = new marked.Renderer(); // markedをimportするだけで使えます
renderer.link = function (href, title, text) {
var link = marked.Renderer.prototype.link.call(this, href, title, text); // aリンクを全て取得
return link.replace("<a", "<a target='_blank' "); // aリンクを単純に外部呼び出しするように変換
};
const MarkdownView = ({
markdownStringBody,
}: {
markdownStringBody: string
}) => {
const options = {
gfm: true,
breaks: true,
pedantic: true,
smartLists: true,
smartypants: true,
renderer: renderer // ここでオプションに必ず指定
};
...
}
export default MarkdownView
全ての箇所で置換を行ってくれるのがポイントで、一種のパーサーになると思います。
最終的には、冒頭で挙げたマークダウン形式のファイルが無事表示できました。
補足: mdxファイル形式なら、mdx-components.tsxで一斉置換できる。
なおファイル形式が.mdx
形式に限り、htmlを置換する処理については、Next.jsのmdx-components.tsx
をプロジェクトルートに置く形でも対応が可能です。下記の弊ブログエントリーの中で触れておりますのでよろしければご覧ください。
参考記事: Next.js製ブログにオリジナルの目次を追加する。
まとめ
今回のエントリーを書いていてあらためて、markdownっていいよね、と思いました。 htmlをコーディングするよりずっと早くコンテンツ作れますしね。
それに、太字、色付け...といった感じで」ボタンを押していくCMSよりも、キーボードから手を外さずに素早く装飾できますし、エンジニアならコンテンツ作る時にマークダウンは外せませんね!
この記事が何かのお役に立てれば幸いです。
最後までお読みいただきありがとうございました!