Next.js + MDXでブログを自作してみた【後編:実装】
はじめに
本記事は、「Next.js + MDXでブログを自作してみた【前編:技術選定】」の続編です。
前編をご覧になっていない方でなぜNext.jsやMDXを使うのかに興味がある場合、以下のボタンで前編に戻って前編をご一読なさることをおすすめします。
Next.jsの導入
Next.jsのインストール
ターミナルで以下のコマンドを入力し、Next.jsをインストールします。
npm create-next-app
色々質問に答えると、ディレクトリが作成されます。
作成されたディレクトリに移ってから、ターミナルでnpm run dev
と入力すると、開発用サーバーが立ち上がります。
リンクをクリックすると、ブラウザで以下の画面が表示されます。
ディレクトリ構造
初期状態では、以下のようなディレクトリの構造になっています。
pages
Next.jsでは、pagesディレクトリにJavaScriptのファイルを作成するだけで自動でルーティングされます。
例として、当サイトは以下のような階層構造になっています。
├about.html
├apps.html
├blog.html
├contact.html
├index.html
├privacy-policy.html
└blog/
├20220523.html
├20220527.html
└20230123.html
それに対して、当サイトのビルド前のpagesディレクトリは以下のような階層構造になっています。
├about.tsx
├apps.tsx
├blog.tsx
├contact.tsx
├index.tsx
├privacy-policy.tsx
└blog/
├20220523.mdx
├20220527.mdx
└20230123.mdx
JavaScriptのファイルをpagesディレクトリに置くだけで、それと対応してルーティングされるので非常に便利です。
なお、私は以前はReact Routerを使って自分でルーティングをしていました。
public
このフォルダには画像などの静的なアセットを置きます。
このフォルダのファイルはビルド後にそのまま出力されます。
初期状態ではfaviconやsvgファイルなどが格納されており、これらのアセットをindex.tsx
で参照しています。
移行作業
元々Reactで当サイトを作成していたため、既存部分の移行はほとんどコピペで済みました。
ただ、既存のサイトのHTMLの構造が良くなく、そのままコピペしたらHydration Errorが起きてしまい、その解消に少し時間がかかりました。
HTMLの構造が良くない例としては、<p>
タグの中に<div>
タグを入れてしまうことなどが挙げられます。
これによりHydrationが失敗することがHydration Errorです。
エラーメッセージにしたがってHTMLの構造を修正すれば解消されます。
MDXの導入
公式ドキュメント
Next.jsの公式サイトにMDXの導入方法があります。
必要なライブラリと、next.config.js
の設定方法について解説されています。
今回は、最低限必要なパッケージである
- @next/mdx
- @mdx-js/loader
- @mdx-js/react
に加えて、
- remark-slug
- rehype-pretty-code
- remark-gfm
をインストールし、next.config.js
で設定しました。
remark-slug
MarkdownのHeading要素(##
など)をHTMLの<h>
タグに変換するときにidを付与してくれます。
後述のTable Of Contentsの作成で役に立ちます。
rehype-pretty-code
Markdownのコードブロックにシンタックスハイライトを加えてくれます。
remark-gfm
GFMとはGithub Flavored Markdownの略で、その名の通りGithub風のマークダウン記法が使えるようになります。
表やToDoリストを簡単に記述できるようになるので便利です。
記事のページの作成
記事のMDXファイルの構造
前編で紹介したように、MDXではMarkdownにJSX記法を混ぜて使うことができます。
それに加え、通常のjsxファイルと同じようにimport
やexport
を使うことができます。
当サイトでは、記事一覧などに使うためのタイトルなどのメタ情報をmeta
としてexport
して他のファイルでも参照できるようにしています。
例えば、この記事のMDXファイルの構造は以下のようになっています。
export const meta = {
title: "Next.js + MDXでブログを自作してみた【後編:実装】",
tags: ["javascript", "html"],
imgSrc: "/blog/20230126/next_mdx.png",
date: new Date(2023, 1 - 1, 27), //月に関しては0-11で指定することに注意!
outline: "Next.jsとMDXでブログを自作してみました。本記事は後編にあたります。後編では、Next.jsとMDXを用いて具体的にどのようにしてブログを構築するのかについて解説します。",
};
# {meta.title}
## はじめに
本記事は、「Next.js + MDXでブログを自作してみた【前編:技術選定】」の続編です。
前編をご覧になっていない方でなぜNext.jsやMDXを使うのかに興味がある場合、以下のボタンで前編に戻って前編をご一読なさることをおすすめします。
//以下本文
共通レイアウトの作成
タイトルやサムネイル画像などのOGPの情報やSNSシェアボタンなどの共通部分を各記事のMDXファイル内にいちいち記述するのは面倒ですし、管理も大変です。
そこで、MDXで記述した記事全体をexport default
でエクスポートして、共通部分のレイアウトをする<PostLayout>
に渡して表示させることにしました。
export const meta = {
title: "Next.js + MDXでブログを自作してみた【後編:実装】",
tags: ["javascript", "html"],
imgSrc: "/blog/20230126/next_mdx.png",
date: new Date(2023, 1 - 1, 27), //月に関しては0-11で指定することに注意!
outline: "Next.jsとMDXでブログを自作してみました。本記事は後編にあたります。後編では、Next.jsとMDXを用いて具体的にどのようにしてブログを構築するのかについて解説します。",
};
//追加
export default ({ children }) => <PostLayout meta={meta}>{children}</PostLayout>
# {meta.title}
## はじめに
本記事は、「Next.js + MDXでブログを自作してみた【前編:技術選定】」の続編です。
前編をご覧になっていない方でなぜNext.jsやMDXを使うのかに興味がある場合、以下のボタンで前編に戻って前編をご一読なさることをおすすめします。
//以下本文
const PostLayout = ({children, meta}) => {
return(
// 共通部分1
// Headタグなどを設定することで、OGPに対応する
//記事本文
{ children }
// 共通部分2
// SNSシェアボタンなど
)
}
export default PostLayout
Table Of Contentsの作成
PCなどの広い画面で記事が表示されるとき、画面右端にTable Of Contentsが表示されるようにしたいです。
そこで便利なのがTocbotというライブラリです。
なお、Tocとはtable of contentsの略です。
Tocbotは、<h1>
などのHeadingタグを読み取って自動でTable Of Contentsを作成してくれます。
ターミナルでnpm i tocbot
と入力してインストールした後で、以下のようにして使います。
import tocbot from "tocbot";
const PostLayout = ({children, meta}) => {
useEffect(() => {
tocbot.init({
tocSelector: '.auto-toc', //auto-tocクラスの子要素としてTable Of Contentsを作成する
contentSelector: '.post-main-content', //post-main-contentクラスの子要素のheadingを走査する
headingSelector: 'h2, h3, h4', //h2, h3, h4を対象としてTable Of Contentsを作成する
hasInnerContainers: true,
orderedList:false,
});
return () => tocbot.destroy()
}, [])
return(
// 共通部分1
// Headタグなど
//tocbotが作成したTable Of Contentsの表示先
<div className="auto-toc"></div>
//記事本文
//ここのHeadingタグを走査してほしいのでpost-main-contentクラスの子要素にする
<div className="post-main-content">
{ children }
</div>
// 共通部分2
// SNSシェアボタンなど
)
}
export default PostLayout
このため、前述のremark-slugを使うなどしてHeadingタグにidを付与する必要があります。
記事一覧のページの作成
blog.tsx
ファイルを編集し、記事一覧を作成します。
その際に、各記事のタイトルやサムネイル画像などの情報が必要になります。
流れとしては
- blogフォルダに入っている全てのMDXファイルを走査して各記事の
meta
の情報を収集する - 収集した
meta
の情報を<Blog/>
に渡す - 記事の一覧を表示する
という順番になります。
ただし、1.の工程で収集するMDXファイルはビルドされると静的なHTMLファイルになってしまうため、ビルド前に収集する必要があります。
そこで、getStaticProps
という特別な関数を使います。
getStaticProps
関数でmeta
の情報の配列をreturn
すると、<Blog/>
のpropsとして渡されます。
import path from 'path'
import fs from 'fs'
import Card from "Cardのパス"
const Blog = ({ metas }) => {
return (
<>
{
{/* 3.記事の一覧を表示する */}
{/* 各記事のレイアウトをCardというコンポーネントで定義しておき、それにmetaを渡す */}
metas.map(meta =><Card meta={meta} />)
}
</>
)
}
export const getStaticProps = async (ctx) => {
//1.blogフォルダに入っている全てのMDXファイルを走査して各記事の`meta`の情報を収集する
const postDir = path.join(process.cwd(), 'pages/blog/')
const pathList = fs.readdirSync(postDir)
const contentsPromise = pathList.map(async (p) => {
const myModule = await import("../pages/blog/" + p)
return {
title: myModule.meta.title,
tags: myModule.meta.tags,
imgSrc: myModule.meta.imgSrc,
date: myModule.meta.date,
outline: myModule.meta.outline,
fileName: path.parse(p).name
}
})
const metas = await Promise.all(contentsPromise)
// 2.収集したmetaの情報を<Blog/>に渡す
return { props: {metas}} ;
}
export default Blog
完成!
package.json
のscripts
のbuild
の箇所を以下のように書き変え、ビルド後にエクスポートされるようにします。
{
...
scripts:{
...
build:"next build && next export"
...
}
...
}
そしてターミナルでnpm run build
と入力して実行することで、outディレクトリに静的なHTMLファイルがエクスポートされ、完成です!
おわりに【後編】
後編では、ブログを具体的にどう実装するのかという点について解説しました。
Next.jsもMDXも非常に強力なツールだと思うので、ブログ以外にもドキュメントの作成など様々なことに使えそうだと感じました。
当面の間、この体制で当サイトを運営していきたいと思います。
前編および後編を併せてかなりの文量になってしまいましたが、ここまでご覧いただきありがとうございました!