更に MDX 話の続きです。前回までの Articles はこちら。
とりあえず、MDX は React コンポーネントへ変換できました、Front Matter もコンポーネントに渡す変数として読み込むことが出来ました、ということになったとして、その後もう一つ壁が立ちはだかります。
「記事の一覧ページをどうしよう」という問題です。
React ベースの静的サイトジェネレーター、ここからは Gatsby.js のサイトにも掲載されている「modern site generator」という言葉を借りて、従来の静的サイトジェネレーター である Static Site Generator (SSG) に対して MSG という略語で話を進めていきます。MSG は、Markdown ベースのコンテンツの一覧ページとの相性があまりよくありません。React-Static や Gatsby.js 、そして、Next.js と様々な MSG を使ってサイトを構築していますが、いずれも一覧ページについて強力なサポートがない状態です。
一覧ページに関する一つの方向性として、headless CMS を使うという方針があります。headless CMS というのは、これまでの CMS と違って HTML を返すのではなく、JSON を返す仕組みであり、管理画面つきの API サーバーというのが理解しやすい構成と言えるでしょう。 headless CMS であれば、一覧を返す API があるので、それを GET リクエストで取得することで、一覧ページ生成用のデータが手に入ります。
それ以外の方法をとる場合、ローカルの Markdown ファイルを読み込み、それを一覧ページとして生成するもしくは、静的な JSON ファイルとして書き出しておくという方法が考えられます。ただ、この場合も数が増えた場合にどうやってページングするかなど、問題があります。
その点、SSG であれば、カテゴリーやタグなどの機能が用意されており、一覧ページを作成する手間がありません。例えば Hugo では、以下のように、Front Matter を記述すると、categories と tags それぞれに属するページも生成されます。これくらいスムーズに出来るとありがたいですね。詳しくはこちら Taxonomies | Hugo。
+++
categories = ["Development", "VIM"]
date = "2012-04-06"
tags = [".vimrc", "plugins", "spf13-vim", "vim"]
title = "spf13-vim 3.0 release and new website"
+++
React-Static における一覧ページ
React Static を例にちょっと解説してみます。今回は MDX ではなく、Markdown をベースに話します。React Static には、react-static-plugin-markdown というプラグインがあります。これを使うと、React Static で Markdown を扱うことができます。
インストールは以下のようにして行い、
$ npm i -D react-static-plugin-markdown
Markdown 用のレイアウトコンポーネントを例えば ContentPage.js
という名前で src/containers/
に作成します。
import React from 'react'
import { withRouteData } from 'react-static';
import { ContentPage } from 'react-static-plugin-markdown';
export default withRouteData((props) => (
<article>
<h1>{props.markdown.data.title}</h1>
<ContentPage {...props}/>
</article>)
);
あとは、static.config.js
にプラグインの設定をするだけです。renderComponent
には前述のレイアウト用コンポーネントを指定します。
plugins: [
[
'react-static-plugin-markdown',
{ renderComponent: 'src/containers/ContentPage' }
]
]
さて、以上で、React-Static においても Markdown によるコンテンツ記述が出来るようになりました。ただ、これだけだと、個別ページが出来たのみです。
ちなみに、react-static-plugin-markdown が出てくる前は、自前で以下のように static.config.js
に書いて変換を行なっていました。
const files = await glob.promise("contents/**/*.md", {})
const file_contents = await asyncMap(files, async file => {
const file_content = fm(await fs.readFile(file, "utf8"))
const contentPath = filterPath(file)
return {
path: contentPath,
component: 'src/containers/Post',
attributes: file_content.attributes,
name: file_content.attributes.title,
tags: file_content.attributes.tags,
getData: () => ({
post: {
...file_content.attributes,
contents: marked(file_content.body)}})
}
})
一覧ページはどうすればいいのでしょうか。Markdown に含まれているタグなどの一覧を取得する API などはないため、地道にやる場合は自作することになります。
例えばタグ一覧を取得する場合、以下のように、static.config.js
の getRoutes
に記述することで取得することができます。やっていることは、前述の file_contents
に対してタグごとにリストを作成して、それらを Routes 形式のオブジェクトとして生成するという方式をとっています。これをプラグイン形式とかにしておくといいのかもしれませんが、やっていません。
const tags = file_contents.reduce((prev, current) => { return _.union(prev, current.attributes.tags) }, [])
const tags_pages = tags.map((tag) => {
return {
path: `/tags/${slug(tag)}`,
component: 'src/containers/Tag',
name: tag,
getData: () => ({
tag,
page_list: file_contents.reduce((prev, current) => {
return _.indexOf(current.attributes.tags, tag) !== -1 ? _.concat(prev, current) : prev
}, [])})
}
})
上記方法は、実際に自分の別サイト Siy & Diy にて使っています。こちらは、React-Static ベースです。
MDX でも Markdown でも Front Matter 部分は同じなので、同様の方法で MDX の一覧ページを作成できそうです。 ただ、Next.js で実現するには、もう 1 ハードルあります。続きます。
参照
ARTICLES
AUTHOR

原 一浩
カンソクインダストリーズ代表 / グレーティブ合同会社代表
1998年に独立し、同年、ウェブデザイン専門のメールメディア DesignWedgeの発行を開始。Webデザイン業の傍ら、海外のWebデザインに関する情報発信を行う。
雑誌への寄稿多数。主な著書に『はじめてのフロントエンド開発』『プロセスオブウェブデザイン』、『Play framework徹底入門』、『ウェブデザインコーディネートカタログ』など。自社製のWebデザインのクロール&キャプチャシステムvaqumをベースに、様々なリサーチを行っている。Web 検定プロジェクトメンバー。