本サイトはプロモーションが含まれています

Gatsby.js製のブログでMarkdownからMDXに移行する方法【gatsby-plugin-mdx】

2023年05月07日 22:00

導入

突然ですが、皆さんはMDXを使っていますか?
MDXとは、Markdown(マークダウン)にjsxのコンポーネントが埋め込めるもので、Reactを使っているとReactのコンポーネントを使いまわすことができるので、非常に便利です。


Gatsby.jsでも当然MDXは使えますが、有名なブログテンプレートであるgatsby-starter-blogではそのままの設定で使うことはできず、追加の設定が必要です。


そこで今回は、Gatsby.js製のブログで既にMarkdownを使っている場合に、MDX (MDX2) に移行する方法を紹介します。
移行時につまづいたポイントについても紹介するので、参考にしていただけると嬉しいです。


MDXとは

MDXへの移行方法を説明する前に、まずMDXについて説明します。
MDX(mdx.js)とは、以下のようなMarkdownにjsxを組み合せた形式のことです。

import { Chart } from './chartComponent.js';
export const year = new Date().getFullYear(); // 今年の西暦
# 今年は何年?
今年は {year} 年です。
このように、**JavaScriptを使って変数を埋め込む**ことができます。
また、以下のようにjsxのコンポーネントを埋め込むことができます。
<Chart size="middle" year={year} />
<div className="center">
<img src="./test.jpg" />
</div>

特徴としては、以下のものがあります。

  • Markdownの構文が使える
  • jsxのコンポーネントをimportし、利用することができる
  • exportの後に変数を定義し、利用することができる

このため、jsxを用いるReactと非常に相性が良いです。
MDXを導入することで、Reactでコンポーネントを作成し、MDX上で繰り返し利用することができます。

バージョンによる違い

2023年現在、MDXには大きく分けて2つのバージョンがあります。
1つがバージョン1、もう1つが最新のバージョン2(MDX2)です。


バージョン1からバージョン2への変更では、コンパイルの高速化やコード実行速度の向上、バンドルサイズの低下など多くの点が改良されています。
今回の記事では最新のバージョン2(MDX2)に対応した移行方法を紹介します。

変換の仕組み

変換の仕組みについても、簡単に説明します。


MDXでは、最終的にjsxに変換されます。
通常はこれをブラウザ上でHTMLにレンダリングします。
Gatsby.jsでは、SSGによってビルド時にHTMLに変換されます。


MDXの変換の仕組みについての詳細は、以下のページを参照してください。
https://mdxjs.com/packages/mdx/#architecture

MarkdownからMDXに移行する方法

それでは本題のMarkdownからMDXに移行する方法について紹介します。
いくつか詰まるポイントがあるので、注意が必要です。


移行の手順は以下のステップになっています。

  1. 事前準備
  2. gatsby-plugin-mdxのインストール
  3. gatsby-transformer-remarkからgatsby-plugin-mdxへの置き換え
  4. gatsby-node.jsの更新
  5. 各ページ・テンプレートの修正
  6. MarkdownファイルのHTMLの置き換え
  7. コンソールエラーの修正 (Gatsby4以下の場合)
  8. (オプション) GitHub Flavored Markdown (GFM) の設定
  9. (オプション) RSSフィードの修正
  10. (オプション) gatsby-remark-prismjsからprism-react-rendererの置き換え

ステップ数は多いですが、説明通り進めればエラーなく進めることができると思います。
それでは、順に説明します。


1. 事前準備

今回の手順を進めるにあたって、まずGatsby.jsのバージョンを確認する必要があります。


package.jsonを開き、dependencies内のgatsbyのバージョンが4.21.0以降になっていることを確認します。

"dependencies": {
"gatsby": "^4.25.2",

もしバージョンが4.21.0よりも前の場合は、npmやyarnを使ってgatsbyのバージョンをアップデートします。


<npmの場合>

npm install gatsby@{アップデートしたいバージョン}

<yarnの場合>

yarn upgrade gatsby@{アップデートしたいバージョン}

また、gatsbyのバージョンをアップデートした場合は、gatsby-plugin-から始まるすべてのパッケージも同様に、対応するバージョンにアップデートします。
以下は、gatsby-plugin-canonical-urlsの場合の例です。


<npmの場合>

npm install gatsby-plugin-canonical-urls@{アップデートしたいバージョン}

<yarnの場合>

yarn upgrade gatsby-plugin-canonical-urls@{アップデートしたいバージョン}

最後に、package.jsonにgatsby-transformer-remarkがあるか確認します。
もしある場合は、事前にアンインストールしておきます。(gatsby-plugin-mdxと競合してしまうため)


<npmの場合>

npm uninstall gatsby-transformer-remark

<yarnの場合>

yarn remove gatsby-transformer-remark

これで、事前準備は完了です。

2. gatsby-plugin-mdxのインストール

次に、gatsby-plugin-mdxというプラグインをインストールします。
これはGatsby.jsでMDXを使えるようにするためのものです。


以下のコマンドを実行します。


<npmの場合>

npm install @mdx-js/react gatsby-plugin-mdx

<yarnの場合>

yarn add @mdx-js/react gatsby-plugin-mdx

3. gatsby-transformer-remarkからgatsby-plugin-mdxへの置き換え

次に、gatsby-config.js内の設定をgatsby-transformer-remarkからgatsby-plugin-mdxに置き換えます。
以下のように、-とある行をその下の+とある行に変更します。

{
- resolve: `gatsby-transformer-remark`
+ resolve: `gatsby-plugin-mdx`
options: {
- plugins: [
+ gatsbyRemarkPlugins: [

さらに、拡張子を.mdから.md.mdxに変更します。
これによって、.md.mdxのファイル両方を変換することができます。

{
resolve: `gatsby-plugin-mdx`,
options: {
extensions: [`.md`, `.mdx`], //変更する行
},
},

4. gatsby-node.jsの更新

続いて、gatsby-node.js内のMarkdownRemarkをMdxに置き換えます。
それぞれ以下のように置き換えます。

gatsby-transformer-remark=>gatsby-plugin-mdx
allMarkdownRemarkallMdx
MarkdownRemarkMdx
FrontmatterMdxFrontmatter

具体的には、以下のように-の行を削除し、+の行を追加します。

+ const indexTemplate = path.resolve("src/pages/index.js")
// Get all markdown blog posts sorted by date
const result = await graphql(
`
{
- postsRemark: allMarkdownRemark(
+ postsRemark: allMdx(
sort: { fields: [frontmatter___date], order: ASC }
limit: 2000
) {
totalCount
nodes {
id
fields {
slug
}
frontmatter {
tags
}
+ internal {
+ contentFilePath
+ }
}
}
- tagsGroup: allMarkdownRemark(limit: 2000) {
+ tagsGroup: allMdx(limit: 2000) {
for (let i = 0; i < pageCount; i++) {
createPage({
path: i === 0 ? `/` : `/pages/${i + 1}/`,
- component: path.resolve("src/pages/index.js"),
+ component: indexTemplate,
context: {
index: i,
// Create blog posts pages
// But only if there's at least one markdown file found at "content/blog" (defined in gatsby-config.js)
// `context` is available in the template as a prop and as a variable in GraphQL
if (posts.length > 0) {
posts.forEach((post, index) => {
const previousPostId = index === 0 ? null : posts[index - 1].id
const nextPostId = index === posts.length - 1 ? null : posts[index + 1].id
createPage({
path: post.fields.slug,
- component: blogPost,
+ component: `${blogPost}?__contentFilePath=${post.internal.contentFilePath}`,
context: {
id: post.id,
previousPostId,
exports.onCreateNode = ({ node, actions, getNode }) => {
const { createNodeField } = actions
- if (node.internal.type === `MarkdownRemark`) {
+ if (node.internal.type === `Mdx`) {
- type MarkdownRemark implements Node {
- frontmatter: Frontmatter
+ type Mdx implements Node {
+ frontmatter: MdxFrontmatter
- type Frontmatter {
+ type MdxFrontmatter {

これで、gatsby-node.jsの置き換えは完了です。

5. 各ページ・テンプレートの修正

src/pages/index.jsなどの各ページやsrc/templates/blog-post.js、src/templates/tags.jsなどの各テンプレートについても、以下のように置き換えます。

gatsby-transformer-remark=>gatsby-plugin-mdx
allMarkdownRemarkallMdx
markdownRemarkmdx

また、記事のテンプレートであるblog-post.jsのpropsにchildrenを追加します。

- const BlogPostTemplate = ({ data, location }) => {
+ const BlogPostTemplate = ({ data, location, children }) => {

加えて、GraphQLの部分でhtmlフィールドを削除します。

export const pageQuery = graphql`
query BlogPostBySlug(
$id: String!
$previousPostId: String
$nextPostId: String
) {
site {
siteMetadata {
title
}
}
mdx(id: { eq: $id }) {
id
excerpt(pruneLength: 100)
- html

そして、記事の本文の部分articleBodyを以下のように置き換えます。

- <section
- dangerouslySetInnerHTML={{ __html: post.html }}
- itemProp="articleBody"
- />
+ <section itemProp="articleBody">
+ {children}
+ </section>

最後に、GraphQLのexcerptにtruncateがある場合、削除しないと以下のエラーが出るため、削除します。

Unknown argument "truncate" on field "Mdx.excerpt".
- excerpt(truncate: true, pruneLength: 100)
+ excerpt(pruneLength: 100)

6. MarkdownファイルのHTMLの置き換え

MDXではjsxを用いているため、Markdown内にHTMLを書き込んでいる場合は、jsxに直す必要があります。


置き換えずにビルドしようとすると、おそらく以下のようなエラーが出ます。
ここが一番つまづいたポイントでした。

ERROR
Module build failed (from ./node_modules/gatsby/dist/utils/babel-loader.js):
SyntaxError: C:\Users\{UserName}\{Your path to repository}\content\blog\js-transform-image-web-animations-api\index.md: Invalid left-hand side in prefix operation.
(1:2)
> 1 | ---
| ^
2 | title: "~"

主な変換ポイントは以下のようなものです。

HTML=>jsx
classclassName
<img src=”~” ><img src=”~” />
<br><br />
<\<
{\{
style=“display:block”style={{ display: ‘block’ }}

特に、文字として<{を使うときにエラーになってしまうのが落とし穴です。
この場合は必ずエスケープ文字\を使って、\<\{とします。
また、閉じタグに/も必ず必要になります。

7. コンソールエラーの修正 (Gatsby4以下の場合)

(Gatsby5以降はgatsby-plugin-mdxにremark-unwrap-imagesが含まれるようになったので、ここにある手順は不要です)


package.jsonに書いてあるGatsbyのバージョンが4系(4.x.x)またはそれ未満の場合、ブラウザのコンソールに以下のようなエラーが出ていると思います。

Warning: validateDOMNesting(...): <div> cannot appear as a descendant of <p>

これは、<p>タグで画像部分の<div>タグを囲ってしまっていることが原因です。
Gatsbyの画像は<div>タグで囲われており、それをMDXの変換時に1行と認識され、<p>タグで囲われてしまっています。


これを解消するためには、まず以下のコマンドでremark-unwrap-imagesというパッケージをインストールします。


<npmの場合>

npm install remark-unwrap-images@2.1.0

<yarnの場合>

yarn add remark-unwrap-images@2.1.0

その後、gatsby-config.jsのmdxOptionsに以下のように追加します。

{
resolve: `gatsby-plugin-mdx`,
options: {
extensions: [`.md`, `.mdx`],
gatsbyRemarkPlugins: [
...
],
+ mdxOptions: {
+ remarkPlugins: [
+ // fix an error
+ require(`remark-unwrap-images`),
+ ]
+ }
},
},

これで、画像が<p>タグで囲われなくなり、エラーが発生しなくなったはずです。

8. (オプション) GitHub Flavored Markdown (GFM) の設定

上記の手順まででも最低限は動くのですが、このままだと表や取り消し線、自動リンクなどが使えないと思います。
実はこれは標準のMarkdownではなく、GitHubで使われているGitHub Flavored Markdown (GFM) というものです。


具体的には、以下のようなものです。


・表

|aaa|bbb|ccc|
|:---:|:---:|:---:|
|ddd|eee|fff|

変換後

aaabbbccc
dddeeefff

・取り消し線

~~取り消し内容~~

変換後
取り消し内容


・自動リンク

https://bel-itigo.com/

変換後
https://bel-itigo.com/


これらを使用したい場合は、まずremark-gfmをインストールします。
ここで、gatsby-config.jsではまだimport文をサポートしていないため、require文を使うことができるバージョン1系をインストールする必要があります。


<npmの場合>

npm install remark-gfm@^1

<yarnの場合>

yarn add remark-gfm@^1

そして、gatsby-config.jsのmdxOptionsに以下のように追加します。

{
resolve: `gatsby-plugin-mdx`,
options: {
extensions: [`.md`, `.mdx`],
gatsbyRemarkPlugins: [
...
],
mdxOptions: {
remarkPlugins: [
+ // Add GitHub Flavored Markdown (GFM) support
+ require(`remark-gfm`),
// fix an error
require(`remark-unwrap-images`),
]
}
},
},

これで設定は完了です。

9. (オプション) RSSフィードの修正

ブログランキングサイトに登録している方など、RSSフィードを使用したい場合は、gatsby-config.jsを修正する必要があります。


詳しくは別の記事に方法を書いたので、気になる方は見てください。

10. (オプション) gatsby-remark-prismjsからprism-react-rendererの置き換え

最後に、シンタックスハイライトについて、gatsby-remark-prismjsとMDXの相性が良くない場合があります。
この場合には、gatsby-remark-prismjsからprism-react-rendererへ置き換えることをおすすめします。


このプラグインの置き換えについては長くなるので、別の記事で後日説明する予定です。


まとめ

今回の記事では、Gatsby.js製のブログでMarkdownからMDXに置き換える方法を紹介しました。


私が途中で遭遇したエラーとその対処方法をすべて入れたため、非常に長くなってしまいましたが、その分充実した記事になったはずです。
MDXへの移行は大変でしたが、これでReactのコンポーネントが再利用できるので、記事中でも様々なことが可能になります。


この記事が少しでも参考になった場合は、周りの方にシェアしていただけると嬉しいです。