tkc310.com

技術学習ログ更新

microCMS Markdownをパース時にコンポーネントに変換する方法 (≒MDX)

目次
  1. 技術構成について
  2. 実装方法について
  3. 実装の概要
  4. mdx文章をhtmlに変換
  5. 描画用のコンポーネントに渡す
  6. 最後に

microCMS Advent Calendar 2021 12日目の記事です。

microCMSに投稿したマークダウン文章をNext.jsのSSGビルド時に、タイトル・リンクなどの要素をコンポーネントに変換する方法について紹介します。

紹介する方法では投稿時の文章にコンポーネントを含む必要がないため ≒MDX と表現しています。

// MDXの場合
<CustomHeading as="h2">title</CustomHeading>
<CustomLink href="https://google.com">link</CustomLink>
// Markdownの場合
## title
[link](https://google.com)

MDX文章として記事を管理する方法と比べて、コンポーネントに変更があった場合に記事の方は # タイトル のようなマークダウン構文のままで良いため、修正しなくて良いというメリットがあります。

e.g. コンポーネント名やpropsのインターフェースを変更
<Heading level={2}>
↓
<CustomHeading as="h2">

技術構成について

  • Next.js (12.0.4)
  • TypeScript
  • microCMS
  • vercel (Node.js Version 14.x)
  • next-mdx-remote (3.0.8)

microCMSへの記事投稿をフックにvercelでNext.js SSGビルドが走る仕組みです。
https://zenn.dev/tkc310/articles/792582ae9ad131

なお、執筆時点でNode.js LTSは16.12.1ですが、vercelは14系までしか対応していないためご注意ください。

コンポーネントへの変換には next-mdx-remote というライブラリを利用しています。
(hashicorp謹製)

実装方法について

ほぼ、next-mdx-remoteの使い方の解説になってしまいます。

実装の概要

README exampleに記載されている方法を利用します。

import { GetStaticProps } from 'next'
import { serialize } from 'next-mdx-remote/serialize'
import { MDXRemote, MDXRemoteSerializeResult } from 'next-mdx-remote'
import ExampleComponent from './example'
const components = { ExampleComponent }
interface Props {
mdxSource: MDXRemoteSerializeResult
}
export default function ExamplePage({ mdxSource }: Props) {
return (
<div>
<MDXRemote {...mdxSource} components={components} />
</div>
)
}
export const getStaticProps: GetStaticProps<MDXRemoteSerializeResult> = async () => {
const mdxSource = await serialize('some *mdx* content: <ExampleComponent />')
return { props: { mdxSource } }
}

大枠の処理の流れは下記です。

  1. 記事の取得
    getStaticProps (SSGビルド時に呼び出される) でmicroCMSからマークダウン文章を取得
  2. mdx文章をhtmlに変換
    serialize で mdxSource に変換する。
    (mdx -> react elementに変換された文字列 + ライブラリ用のapiが生えたオブジェクト)
  3. 描画用のコンポーネントに渡す
    <MDXRemote {...mdxSource} components={components}> のように mdxSource を与えるとhydrateしてくれる。
    このとき components に、各要素に対応するコンポーネントをマッピングしたオブジェクトを指定する。
    e.g. { img: (props) => <CustomImage {...props} /> }

2,3について、詳しく説明していきます。

mdx文章をhtmlに変換

Next.jsの getStaticProps 関数でmicroCMSで投稿したマークダウン文章を取得して、htmlに変換します。

なお、このライブラリは名前の通りMDXに対応しているためMDX文章でもhtmlに変換してくれます。

// getStaticProps
const res = await fetch(url, key);
const article = await res.json();
const mdxSource = await mdx2html(article.body);

変換する処理は mdx2html のように関数に切り出しています。

オプションとしてmdx.jsのAPIが利用できるため、
下記のプラグインも併用しています。

  • rehypePrism - コードのシンタックスハイライトを適用
  • imageSize - 画像のwidth, height属性を利用できるようにする
// mex2html
import { serialize } from "next-mdx-remote/serialize";
import { MDXRemoteSerializeResult } from "next-mdx-remote";
import rehypePrism from "@mapbox/rehype-prism";
import imageSize from "rehype-img-size";
export const mdx2html = async (mdxText: string): Promise<MDXRemoteSerializeResult> => {
const mdxSource = await serialize(mdxText, {
mdxOptions: {
remarkPlugins: [],
rehypePlugins: [
[rehypePrism, {}],
[imageSize, { dir: "public" }],
],
},
});
return mdxSource;
};
export default mdx2html;

描画用のコンポーネントに渡す

前述の mdxSource を <MDXRemote> に渡します。

interface Props {
mdxSource: MDXRemoteSerializeResult
}
export default function ExamplePage({ mdxSource }: Props) {
return (
<div>
<MDXRemote {...mdxSource} components={components} />
</div>
)
}

components={components} には、下記のようにhtml変換後の各要素に対応するカスタムコンポーネントをマッピングしたオブジェクトを渡します。

const mdxComponents: any = {
h2: (props) => <CustomHeading as="h2" {...props} />,
h3: (props) => <CustomHeading as="h3" {...props} />,
img: ...省略,
a: ...省略,
};
type Props = {
as: 'h2' | 'h3' | 'h4' | 'h5' | 'h6';
children: ReactNode;
};
const CustomHeading: VFC<Props> = ({ as, children }) => {
const CustomTag = as;
return <CustomTag>{children}</CustomTag>
}
export default mdxComponents;

なお、MDX文章をソースとする場合は下記のようにコンポーネントも定義できます。

import { FaBeer } from 'react-icons/fa';
const mdxComponents: any = {
<FaBeer />,
h1: ...省略,
};
export default mdxComponents;

解説は以上になります。

実装コードは公開していますので、下記からご確認ください。
https://github.com/tkc310/microCMS_blog

最後に

今回の記事ではmicroCMSについてあまり触れませんでしたが、和製サービスでUIも分かりやすくオススメです。
個人ブログに利用していますが、管理画面から登録したAPIスキーマをJSONとして出力・管理できるためチーム開発する際も便利だと思います。

アップデートも早くキャッチアップしきれていないため、本記事で紹介した内容もスマートに解決できる方法があるかもしれません。