Next.jsを少しずつ理解していく10【getStaticPaths】

今回はNext.jsの動的パラメーターを使用してページを作成する方法を紹介します。例えばブログ記事の一覧ページから任意の記事を選択すると、その記事(固有のID)に応じたページに遷移する。といった仕組を構築したいときに利用できます。

 

今回は上記の例の通り、簡単なブログサイトを作成しながら動的パラメータの使い方を解説していきます。大まかな仕様は次の通りです。

  • /postsページにアクセスすると記事一覧が表示される
  • /postsページで任意の記事を選ぶとそれに応じた個別ページに遷移する
  • /posts/id(idは記事固有のもの)で個別ページが表示される

Next.jsでブログの「一覧ページ」を作成する

localhost:3000/postsにアクセスすると記事の一覧が表示されるようにしていきます。

 

まずはpagesフォルダの中にpostsディレクトリを用意して、その中にindex.jsファイルを作成します。

Terminal

mkdir posts
touch posts/index.js

pages/posts/index.js

function PostList() {
    return(
        <>
        <h1>List of Posts</h1>
        </>
    )
}

export default PostList

 

次に、以前学習したgetStaticProps関数を使ってブログ記事の一覧データをフェッチして取得します。ブログ記事のデータは以前にも使用したJSONPlaceholderを利用します。

取得したデータはpropsに渡し、PostList()で受け取れるように設定します。また、現在は機能しませんが個別ページへ遷移するためのLinkも設置しています。

pages/posts/index.js

import Link from "next/link"

function PostList( {posts} ) {
    return(
        <>
        <h1>List of Posts</h1>
        {
            posts.map((post) => {
                return(
                    <div key={post.id}>
                        <Link href={`/posts/${post.id}`} passHref>
                            <h2>{post.id} {post.title}</h2>
                        </Link>
                        <hr/>
                    </div>
                )
            })
        }
        </>
    )
}

export default PostList

export async function getStaticProps() {
    const res = await fetch('https://jsonplaceholder.typicode.com/posts')
    const data = await res.json()

    return {
        props:{
            posts:data.slice(0, 3),
        },
    }
}

 

 

それと、TOPページ(/)から簡単にpostsページへ遷移できるように、index.jsファイルに/postsへのLinkを設置します。

pages/index.js

import Link from 'next/link'
function Home() {
  return(
    <>
    <h1>Next JS pre-rendering</h1>
    <ul>
      <li>
        <Link href='/users'>
          <a>Users</a>
        </Link>
      </li>
      <li>
        <Link href='/posts'>
          <a>Posts</a>
        </Link>
      </li>
    </ul>
    </>
  )
}

export default Home

 

postsページを開くとJSONPlaceholderから取得した3つの記事データが一覧で表示されているはずです。

Next.jsでブログの「個別ページ」を表示する

続いては、一覧ページのリンクをクリックした後に遷移する「個別ページ」を作成します。

 

postsディレクトリ内に[postId].jsファイルを作成します。

また、今回はgetStaticProps関数にcontextパラメータを渡しています。contextパラメータはいくつかのキーを含むオブジェクトで、今回はparamsキーを取り出した後に、params.postIdで動的に生成されるページのパス名を取得しています。

pages/posts/[postId].js

function Post({ post }) {
    return(
        <>
        <h2>{post.id} {post.title}</h2>
        <p>{post.body}</p>
        </>
    )
}

export default Post

//contextパラメータを渡す
export async function getStaticProps(context) {
    //contextパラメータのparamsを取り出す
    const { params } = context
    //params.postIdでパスを取得
    const res = await fetch(`https://jsonplaceholder.typicode.com/posts/${params.postId}`)
    const data = await res.json()

    return {
        props: {
            post: data,
        },
    }
}
[postId].jsとは?|ダイナミックルーティング

以前も解説した通り、ファイル名を[]で囲った場合は、リクエストされたパスに応じて動的にページを生成する「ダイナミックルーティング」を行うファイルになります。

つまり、postsディレクトリの中にダイナミックルーティングファイルを用意するということは、/posts/1/posts/2など/postsパスの後に続くパスに応じて動的にページが生成されるということになります。

contextパラメータとは?

contextパラメータとはparamsやpreviewなどのキーが含まれたオブジェクトです。例えばparamsキーには、ダイナミックルーティングを使用するページのルートパラメータが含まれています。ページのファイル名が[postId].jsの場合、paramsキーの中身は{postId: ...}のようなオブジェクトになっているため、params.postIdとすることで動的に生成されたページのパスを取得することができます。

詳しくは公式のドキュメントを参照ください。
getStaticProps|Nextjs.org

 

さて、ここで試しにlocalhost:3000/posts/1を確認してみるとエラーが表示されていることが分かります。

ダイナミックルーティングを行うページでgetStaticPropsを使用する場合は、一緒にgetStaticPaths関数を使用して「生成されるパスのリスト」を定義する必要があります。Next.jsは事前レンダリングを行う際にgetStaticPathsで指定されたすべてのパスに対応するファイルを生成します。

今回はgetStaticPaths関数のpathsプロパティを使用します。ダイナミックルーティングを行うファイル名が[postId].jsの場合、paths:[params:{postId: 'パスの名前'}]という風に定義します。

それでは、[postId].jsファイルに以下のコードを追加してください。

/pages/posts/[postId].js

export async function getStaticPaths() {
    return {
        paths:[
            {
                params:{postId: '1'},
            },
            {
                params:{postId: '2'},
            },
            {
                params:{postId: '3'},
            },
        ],
        fallback: false,
    }
}
fallbackプロパティ

上記のコードでしれっと記述しているfallbackプロパティ(fallback: false)についてです。

fallback: falseと記述した場合、Next.jsはgetStaticPathsで指定されたパス(に対応するファイル)のみをビルドするため、そのパス以外にアクセスすると(リクエストされると)404 pageが表示されるようになります。ブログサイトで少数のページを生成したい場合などに有効です。

fallback:trueと記述した場合、Next.jsはgetStaticPathsで指定したパスのみをビルドしますがそれ以外のパスがリクエストされても404 pageは表示しません。その代わりにビルド時に生成された空のデータを含むダミーのHTML(fallback page)を素早く返し、次の方法によりJSONファイル(データ)が届くのを待ちます。

クライアントからのリクエストはサーバーに伝わり、サーバーはパスに応じたデータをgetStaticProps関数内でフェッチした後、HTMLファイルとJSONファイルを自動で生成してデータを格納します。それが終わると、ブラウザは生成されたJSONファイルを受け取って自動レンダリングを行って表示します。つまりHTMLはクライアントサイドでレンダリング(CSR)されます。

このようにして生成されたページは事前レンダリングされたページと同じようにリストに追加され、以後他の(あるいは同じ)ユーザーによって同じパスへのリクエストがあった際には事前レンダリングされたページと同様に即座にページが提供されます。

ECサイトなど、データベースを基に大量のページを生成する必要があり、一度にすべてをbuildすると時間がかかりすぎる場合などに有効です。一方でJSを無効にした場合CSRを実行できないというデメリットもあります。

fallback:'blocking'と記述した場合、Next.jsはgetStaticPathsで指定したパスのみをビルドしますがそれ以外のパスがリクエストされても404 pageは表示しません。trueを指定した時のようにダミーのページを読み込ませるのでもなく、単純にブラウザの動作を一旦停止してパスに対応するHTMLファイルが送られてくるのを待ちます。

クライアントからのリクエストはサーバーに伝わり、サーバー側でパスに応じたHTMLファイルが生成されます。つまりサーバーサイドレンダリング(SSR)を行います。SSRによって生成されたファイルはクライアントに送信されてクライアント(ブラウザ)に表示されます。

blockingとtrueの動作は非常に似ていますが、blockingではダミーのページを表示しませんし、HTMLの生成もCSRではなくSSRで行います。

getStaticPaths|Nextjs.org

 

これで、しっかりと個別ページが表示されるようになりました。

生成されたページを確認する

ここまでで、一覧ページの表示と一覧ページから個別ページへの遷移/表示を行うことができるようになりました。現在は開発サーバーでNext.jsを動かしているので本来buildする必要はありませんが、試しにbuildして、どのようにページが生成されているのかを確認してみましょう。

 

Terminal

npm run build

結果

Terminalの表示を確認すると、/posts/[postId]パスの下に/posts/1 、/posts/2/posts/3という3つのファイルが生成されていることが分かります。

また、buildによって生成されたファイルを確認すると、各個別ページに対応するHTMLファイルとJSONファイルが生成されていることが分かります。JSONファイルにはgetStaticProps関数で取得したデータが保存されています。

/pre-rendering/.next/server/pages/posts

大量のダイナミックルーティングに対応する

現在は個別ページの数を3つに絞って表示していますが、大量の個別ページを生成して一覧ページからルーティングしたい場合はどうすればよいでしょうか?

まずは、一覧ページ(/posts/index.js)ですべてのブログ記事データを取得してリンクを生成できるように、data.slice(0, 3)からsliceメソッドを削除します。

/pages/posts/index.js

export async function getStaticProps() {
    const res = await fetch('https://jsonplaceholder.typicode.com/posts')
    const data = await res.json()

    return {
        props:{
            //slice(0, 3)を削除
            posts:data,
        },
    }
}

結果

3つ以上の一覧データが表示されるようになりましたが、3番目以降の記事(/posts/4など)を表示しようとすると、404 Pageが表示されます。これは、個別ページファイル(/psots/[postId].js)が3つ以上のページ生成に対応していないためです。

既述の通りNext.jsでは、ダイナミックルーティングが行われているページでgetStaticProps関数が使用されている場合はgetStaticPaths関数で指定されたページファイルだけをレンダリング時に生成します。これまではページが3つだけだったのでページIDを1つ1つ指定していても良かったのですが、大量のルーティングに対応させたい場合はこの方法は不適切です。

そこで、次のように変更します。

/pages/posts/[postId].js(変更前)

export async function getStaticPaths() {
    return {
        paths:[
            {
                params:{postId: '1'},
            },
            {
                params:{postId: '2'},
            },
            {
                params:{postId: '3'},
            },
        ],
        fallback: false,
    }
}

/pages/posts/[postId].js(変更後)

export async function getStaticPaths() {
    //記事データを取得
    const res = await fetch(`https://jsonplaceholder.typicode.com/posts`)
    const data = await res.json()

    //mapメソッドで全てのデータを展開して、postIdを指定する配列を作成
    const paths = data.map(post => {
        return{
            params: { postId: `${post.id}`}
        }
    })

    return {
        //pathsに上で作成した配列pathsを指定
        paths: paths,
        fallback: false,
    }
}

結果

まとめ

以上でNext.jsのgetStaticPaths関数についての紹介は終わりです。

getStaticProps関数とgetStaticPaths関数はNext.jsの中でも特に利用する機会の多い機能ですので、しっかり理解してから次の記事に進むと良いでしょう。

 

次はISR(Incremental Static Regeneration)について解説します。

それではまた!

コメントを残す

CAPTCHA