2021/08/08

Next.jsのダイナミックルーティングを勉強する

@ 酒井悠宇

https://www.youtube.com/watch?v=x0ayN-ysbsI&list=PLX8Rsrpnn3IUGEyanrHYGjY1WOzNe7Jd-&index=7

前書き
勉強しながら書いてて、まだ理解度が激浅いのでデタラメ言ってるかもしれませんのでその辺はご了承くだせぇ!

前回はNext.jsのパフォーマンスを効率する仕組みについて記事を書いたので、
今回はNext.jsのDynamic Routes(動的ルーティング)についてチュートリアルをやりながら勉強していきたいと思います。



Next.jsのチュートリアルを進め、マークダウンファイル形式のブログデータをgetStaticPropsで持ってきて表示するところまで完了しました。
次はブログの文字の部分をクリックするとそのブログの個別のページに遷移するみたいな実装をしていきます。
そのためにDynamic Routesを使用します。

ちなみにブログデータはこんな感じです。これと同じようなのがもう1個あります。
--- で囲まれているところがファイルのmeta情報でそれ以下がブログ本文です。
メタ情報はmatterというライブラリで、マークダウンはremarkというライブラリで解析していきます。

Dynamic Routesを実装する

Next.jsでは、ファイル名に[]を使うとそれが動的ルーティングになります。
動的ルーティングとは、「複数のURLに対応したページファイル」のことです。

具体的な作り方は以下のようになります。

  1. Dynamic Routes用ファイルを作成する
  2. ファイル名からidを取得する関数を作成する
  3. getStaticPathsで静的なファイルを作成する
  4. マークダウンをレンダリングする


それではやっていきましょう。

1, Dynamic Routes用ファイルを作成する。

まずはDynamic Routes用ファイルを作成します。
Next.jsでは、ファイル名に[]を使うとそれが動的ルーティングになるので、pages/postsに[id].jsというファイルを作成します。

2, ファイル名からIDを取得する関数を作成する


ブログの情報を取得して並べ替える関数を定義していたlib/posts.jsに、ブログ情報が格納されてあるファイルの名前からIDを取得する関数を定義します。

Postsディレクトリ(マークダウンのブログの情報ファイルが格納されてあるディレクトリ)を取得

const postsDirectory = path.join(process.cwd(), "posts");


ファイル名からIDを取得する関数を定義

export function getAllPostIds() {
  //ブログ情報のmdファイルの情報を変数に代入
  const fileNames = fs.readdirSync(postsDirectory);

  return fileNames.map((fileName) => {
    return {
      //getStaticPathsを正常に動かすにはキーをparamsにする必要がある。
      params: {
        // .md拡張子を除いた文字列をidとして保存
        id: fileName.replace(/\.md$/, ""),
      },
    };
  });
}

キーにparamsを指定するのは、次に説明するgetStaticPathsを正常に動作させる上で必要だからみたいです。

3, getStaticPathsで静的ファイルを生成


getStaticPathsの特徴

  • pathsとfallbackを返す
  • pathsは事前ビルドするパス対象を指定するパラメーター(情報のオブジェクトが入っている)
  • fallbackは事前ビルドしたパス以外にアクセスした時の動作(指定パス意外なら404を返す)


コードはこのような感じです

import { getAllPostIds } from "../../lib/posts";

export async function getStaticPaths() {
  //ファイル名から取得したidの配列をpathsに代入
  const paths = getAllPostIds()
  return {
    //pathを配列形式で変換する
    paths,
    //pathsに格納されてあるパスにアクセスされた場合404ページを返す
    //trueにするとfalback専用のページを見せることができる
    fallback: false
  }
}


getAllPostIdsでファイル名からidを取得し、getStaticPathsで、取得したパスでアクセスされた場合、それ以外の場合の挙動を設定しています。
これを設定することで、「これらのファイルはビルドした時にHTMLに変換しておいてね(Static Generation)」ってことをNext.jsに伝えています。

次はidに紐づいたブログの情報を表示するために、それに必要なデータを持ってくる関数を定義します。

//idに紐づくファイルからブログの情報を取り出す関数を定義
export function getPostData(id) {
  //ファイルのパスを取得
  const fullPath = path.join(postsDirectory, `${id}.md`);
  //ファイルのコンテンツを取得
  const fileContents = fs.readFileSync(fullPath, "utf8");

  //matterはgray-matterというライブラリに用意されているもの
  //ファイルのmeta情報を解析することができる
  const matterResult = matter(fileContents);

  // Combine the data with the id
  return {
    id,
    //ファイルのメタ情報とコンテンツが格納された配列をスプレッド
    ...matterResult.data,
  };
}

getStaticPathsに指定されてあるファイルがどこにあるのか、中身は何なのかを調べて実際に取り出してくれています。

そしてこの関数をgetStaticProps(本番環境でビルド時に実行される関数)でラッピングすることによって、
複数のURLに対応したページファイル(動的ルーティング)を実現するのに必要なファイルを、ビルド時にHTMLに変換しておく(Static Generation)が実現します。

export async function getStaticProps({ params }) {
  const postData = getPostData(params.id);
  return {
    props: {
      postData,
    },
  };
}


あとはデータを表示します。

export default function Post({ postData }) {
  return (
    <div>
      {postData.title}
      <br />
      {postData.id}
      <br />
      {postData.date}
    </div>
  );
}


該当のurlにアクセスしてみると、タイトル、id、日付がそれぞれ表示されました。




4, Markdownをレンダリングする


前回までの行程で、マークダウンファイルのメタ情報をmatterというライブラリで解析し、表示するところまでは完了しました。
次はいよいよ、ブログ本文(マークダウン部分)をレンダリングしていきたいと思います。

マークダウンをレンダリングするには、remarkというライブラリをインストールします。

npm install remark remark-html


マークダウンをレンダリングする仕組みを記述していきたいと思います。

と、思いましたが、エラーが出ました。

調べてみると、Next.jsがESModuleフォーマットサポートしてないとかどうちゃらこうちゃら。(正直よくわかんない)
https://zenn.dev/dl10yr/articles/b592e36d852e8c

結果的には、チュートリアルのpackage.jsonを見て、それ同じバージョンのremarkをインストールするとエラーが解消されました。
パッケージのバージョンの違いによるエラーは初めて体験したのでバージョン管理の大切さがわかりました。

ファイルのコンテンツを解析して

export async function getPostData(id) {
  const fullPath = path.join(postsDirectory, `${id}.md`);
  const fileContents = fs.readFileSync(fullPath, "utf8");

  // 投稿のメタデータ部分を解析するために gray-matter を使う
  const matterResult = matter(fileContents);

  // マークダウンを HTML 文字列に変換するために remark を使う
  const processedContent = await remark()
    .use(html)
    .process(matterResult.content);
  const contentHtml = processedContent.toString();

  // データを id および contentHtml と組み合わせる
  return {
    id,
    contentHtml,
    ...matterResult.data,
  };
}


getStaticPropsで事前レンダリング

export async function getStaticProps({ params }) {
  const postData = await getPostData(params.id);
  return {
    props: {
      postData,
    },
  };
}


それをいい感じに表示

export default function Post({ postData }) {
  return (
    <div>
      <Head>
        <title>{postData.title}</title>
      </Head>
      {postData.title}
      <br />
      {postData.id}
      <br />
      <Date dateString={postData.date} />
      <br />
      <div
        dangerouslySetInnerHTML={{ __html: postData.contentHtml }}
      />
      <Link href="/">
        <a>back to home</a>
      </Link>
    </div>
  );
}


とういうことで完成しました。
該当のurlにアクセスすると、マークダウンで記述されていた部分も問題なく表示されています。




まとめ

・Dynamic Routesを使用すれば、動的なルーティング「複数のURLに対応したページファイル」を実現することができる。
・Dynamic Routes用ファイルはファイル名に[]をつけて、[id].js のように記述する。
・StaticPathsで事前ビルドするパス対象を指定することができる。対象以外のパスがリクエストされた場合の挙動はfallbackで設定できる。

Next正直よくわからんけど勉強してればいい感じになってくると思うので引き続き勉強していきやす!!
最後までありがとうございました〜!

© 2021 powerd by UnReact