Next.js + Markdown + microCMSでブログを作る
対象
- Next.jsでブログを作ってみたい方
- MarkdownやmicroCMSでブログ記事を管理してみたい方
- データ取得周りが気になる方
コード
こちらに公開してます!
ブログの構成
基本的な部分はNext.js + TypeScript + TailwindCSSで作成しています。
その他の記事の管理方法やインフラ周りは以下の表の通りです。
記事の管理方法 | 説明 |
---|---|
microCMS | 更新頻度の高い内容や検索したい記事を投稿するために利用 |
Zenn | 独断と偏見で公共性が高めだと感じた記事を投稿 |
GitHub | Zennに投稿するまでもない個人的なメモなどを投稿 |
記事の公開方法 | 説明 |
---|---|
Vercel | Next.jsと相性が良いため利用 (ただしサーバー応答が遅め) |
GCP (Cloud Run) | サーバー応答を早くするために、Vercelからの移行先として検討中 |
microCMS
microCMSは無料枠だと3つまでAPIを作成できるため、「タグ」「スクラップ」「スクラップをまとめたもの」の3つを作成しています。
また、それぞれ記事を更新、追加するとVercelのwebhookを叩くようにしてます。
microCMSを使う理由は最近機能リリースのペースが速く、日本での利用も広がっていると感じたためです。
Zenn
ZennではRSSを取得できるため、これを用いてタイトルやURLなどを取得してます。
GitHub
Contentlayer, mdx, markdown-to-jsxなど選択肢は多いですが、このブログではmarkdown-to-jsxを用いて、MarkdownファイルをJSXにしてます。
実装に時間のかかった部分
いくつか手間取った部分があるので共有します。
microCMSのコンテンツ取得APIの作成
Nuxtで作られているmicroCMSの公式ブログのリポジトリを参考に作成しました。
microCMSで作成できるAPIにはオブジェクト形式とリスト形式がありますが、ブログではリスト形式のみを実装しました。
(ちなみに、オブジェクト形式とリスト形式の違いはJSONを返したいか、JSONの配列を返したいかの違いです)
以下のような感じで作りました。
サイドバーのデータ生成
microCMSで取得したデータからサイドバーを作りたいときに、毎回APIを叩くとRate Limitにかかります。
そのため、全てのページで共通の情報はJSONファイルにし各ページにインポートするようにしました。
JSONファイルはビルド時にts-nodeを動かして作ってます。
// #!/usr/bin/env ts-node
import { createJsonFile } from './common/createJsonFile'
import { getAllListContents } from '../utils/getContents'
const folder = 'fetchData/scraps'
const categoriesDataFile = 'scrapLists.json'
export const main = async () => {
const endpoint = 'scraps'
const contents = await getAllListContents({
endpoint,
queries: {
fields: 'id,date,createdAt',
},
})
createJsonFile({
folder,
filename: folder + '/' + categoriesDataFile,
content: JSON.stringify(Array.from(contents.values())),
})
}
main()
import fs from 'fs'
export const createJsonFile = ({ folder, filename, content }: { folder: string; filename: string; content: string }) => {
if (!fs.existsSync(folder)) fs.mkdirSync(folder)
fs.writeFileSync(filename, content)
}
{
"compilerOptions": {
...
},
...
"ts-node": {
"transpileOnly": true,
"compilerOptions": {
"module": "commonjs",
"isolatedModules": true,
}
}
}
{
...
"scripts": {
...
"build": "yarn fetch:scrapLists && next build",
"fetch:scrapLists": "ts-node ./bin/fetchSidebarDataForNotes.ts",
...
}
}
markdown to jsx
MarkdownファイルからJSXを生成する方法として、Contentlayer, MDX, markdown-to-jsxなどがあります。
Contentlayer
ContentlayerはVercelのエンジニアの方が個人ブログで使用していたため、一度検討してみました (いつの間にか、β版が正式に発表されてました)。
結論からいうと、MarkdownからJSONと型情報が生成され、使い勝手は比較的良さげでした。
ただ試した時期が良くなかったのか、動きはするのものの解決の難しいエラーや警告がターミナルにいくつも出てきたため、採用は諦めました。
安定版が出てきたら、また挑戦してみたいです。
MDX
Gatsbyのテンプレートやプロジェクトでは、これがよく利用されています。
こちらは@mdx-js/runtimeを使う方法 (非推奨) と@mdx-js/reactを使う方法の2つがあります。
@mdx-js/runtimeを使う場合は以下のようになります。
components = {...}
でHTMLタグを上書きし、MDXコンポーネントにMarkdownから取得したデータ (children) を渡して使います。
この方法はaタグにnext/link, imgタグにnext/imageを当てたりできて便利なのですが、バンドルサイズが大きくなります (babelとかも入るため)。
結果ページ読み込みに時間がかかるようになるので、使い物になりませんでした。。
Next.jsのブログ記事でも言及のある@mdx-js/reactを使う方法では、pages配下にmdxファイルを配置する必要があります。
microCMSとの共存が難しいので、こちらの採用も諦めました。
markdown-to-jsx
最終的にはmarkdown-to-jsxというライブラリを利用することにしました。
こちらのライブラリもHTMLタグの上書きができます。
HTMLタグの上書きができるので、コードブロックはPrismを用いてコードハイライトを有効化、imgタグはクリックで拡大できるようにカスタマイズできます。とても良い。
markdownからデータを取得する
まだ改良の余地がありますが、globbyやgray-matterを用いてMarkdownファイルからデータを取得するようにしました。
globbyは拡張子を指定して該当するファイルパス一覧を取得するために使ってます。
gray-matterはMarkdownのyamlヘッダーとコンテンツの中身を分離するために使います。
コードハイライト
コードハイライト部分はprism-react-rendererを用いて実装してます。
基本的には公式のREADMEを読みながらやれば実装できます。
また、prism-react-rendererは一部サポートされてない言語があるため、追加したい言語 (dockerfile, Rustなど) は別途準備しました。
Next.jsでは以下のように対応してます。
今後やりたいこと
SSGのページング
Next.jsのRewritesを活用すれば、SSGでページングも実装できるのですが、React18にすると一部挙動がおかしくなるため、原因調査中です。
(以下のコードを追加してページングを作る想定)
module.exports = {
...
async rewrites() {
return {
beforeFiles: [
{
source: '/notes',
has: [
{
type: 'query',
key: 'pageNumber',
value: '(?<pageNumber>.*)'
}
],
destination: '/notes/pageNumber/:pageNumber'
}
]
}
}
}
検索機能
コンテンツをmicroCMSで管理している場合は、APIを叩けば良いのですが、その他の記事はどうするか考え中です。
algoliaのような有料サービスを使うか、googleのサイト内検索 (hogehoge site:xxxx.comを叩く) を使うか、30-seconds-of-codeさんのようにJSONファイルを検索するようにするか。
JSONファイルを検索する方式は安くて高速になりそうなので、ここら辺のリポジトリもみながらやりたいなと思ってます。
インフラ変更
検索機能をどうするか次第ですが、GCPも触ってみたいので、VercelからGCP (Cloud Run) に移行したいなと考えてます。
以上。Next.jsでブログを作る際の参考になれば幸いです。
Discussion