株式会社Infigate

Blog

Next.jsのAppルーターにおけるレンダリング


投稿日2024/6/22 更新日2024/6/28 システム

はじめに

この記事の対象者

・Next.js初心者の方

・Next.jsの環境が整っている方

レンダリングとコンポーネント

Appルーターのレンダリングで重要となるのは「ページ」ではなく「コンポーネント」です。Next.jsで作成されるコンポーネント類はサーバーコンポーネントまたはクライアントコンポーネントに分かれます。

ページは各種のコンポーネントを作成し、それらを組み合わせて作られます。この一つ一つのコンポーネントごとにそれがサーバー側で実行されるか、クライアント側で動くかが決められています。

もちろん中には両者が組み合わされたページもあるかと思います。コンポーネントごとにサーバー側でレンダリングされるか、クライアント側でレンダリングされるかが決まっているということを理解しておきましょう。

レンダリングの明示的な指定

そのコンポーネントがサーバーコンポーネントかクライアントコンポーネントかはあらかじめ決めておく必要があり、以下の文を冒頭に書きましょう。

クライアントコンポーネントの場合

"use client"

サーバーコンポーネントの場合

"use server"

ただし、これらをつけただけで自動的にクライアントコンポーネント、サーバーコンポーネントになるというわけではありません。これらはこのコンポーネントがどちらに属するかを指定するものです。

例えば’use client’をつけたなら、サーバー側で動く機能が使われているとエラーが発生するようになります。逆も然りです。これらを指定することによってこのコンポーネントはどちらでレンダリングされて動くのかを明示し、それに応じたコードが記述できるようになります。

それでは実際に「app-sample」という名前のプロジェクトを作成してみます。インストールの際に「Would you like to use App Router? (recommended)」と聞かれますが、Yesを選択してください。

npx create-next-app@latest app-sample

プロジェクトのディレクトリに移動して、開発サーバーを起動します。

cd app-sample
npm run dev

サーバーコンポーネントを利用するページはビルド時にレンダリングされ静的ページとして生成されるものと、クライアントがアクセスした際にサーバー側でレンダリングされるダイナミックレンダリングページに分かれます。

まず完全に静的なページとして生成される例を紹介します。これはクライアント側での処理やサーバー側で動的に値を処理するようなコンポーネントを持たないページです。このようなページはビルド時に静的ページにコンパイルされます。

まずタイトルとメッセージのスタイルクラスを用意します。global.cssに以下を記述します。

h1.title {
  @apply text-2xl font-bold m-0 p-5 text-white bg-blue-800 text-center;
}
p.msg {
  @apply text-lg m-5 text-gray-900;
}

次にappディレクトリ内のpage.tsxを以下のようにします。

"use server"
import { Metadata } from 'next'

export async function generateMetadata() {
  return {
    title: 'index page',
  }
}

const defaultProps = {
  title:"Static page",
  msg:"This is static page sample."
}

export default async function Home() {
  return (
    <main>
      <h1 className="title">{defaultProps.title}</h1>
      <p className="msg">{defaultProps.msg}</p>
    </main>
  )
}

以下のようなページになります。

これは静的ページのサンプルです。冒頭に”use server”と記載したためこのコンポーネントはサーバー側で実行されます。

また、generateMetadataという関数があります。これはMetaデータを用意するためのもので以下のような形で記述します。

export async function generateMetadata(){
  return 値
}

MetaデータというのはHTMLの<head>部分に用意される各種の設定情報のことです。ここではtitleというキーの値だけが記述されています。これは<title>の値になります。

また、以下のようにdefaultPropsという定数を定義してます。

const defaultProps = {
  title:"Static page",
  msg:"This is static page sample."
}

JSXではここにある値を使ってページの表示を作成しています。このdefaultPropsはこのページで使われる値をあらかじめ用意しておいたものです。このように定数などを用意してJSXに埋め込むような処理を記述してもこのページは静的ページとしてビルド時にページが生成されます。

サーバーコンポーネントを作成するときもう一つ注意しておきたいのが「関数はすべて非同期で定義する」という点です。generateMetadata関数もHomeコンポーネントの関数もasyncをつけ忘れるとサーバーコンポーネントとして問題があると判断されエラーになるため注意が必要です。

ダイナミックレンダリング

サーバーコンポーネントのページはビルド時に完全に静的ページとして生成されるものばかりではありません。ビルド時には静的ページに変換されず、クライアントからのアクセスがあった際にサーバー間でレンダリングする「ダイナミックレンダリング」のページもあります。

ダイナミックレンダリングが使われるのはダイナミックルーティングを利用したページです。パラメーターが渡されときにそのパラメーターの値に応じた表示を作成するため、静的ページではなくダイナミックレンダリングされるページとして扱われます。

ただし、パラメーターとして渡される値が限られている場合、あるいはよく使われる値などがある場合は、特定のパラメーターの表示を静的ページに変換して使うこともできます。これは「generateStaticParams」という関数を定義することで行えます。

export async function generateStaticParams() {
  return パスの配列
}

generateStaticParamsはアクセスされるパスのstring値を配列にまとめたものを返します。これにより返された配列のパスはビルド時に静的ページに変換し、それ以外のパスへのアクセスについてはダイナミックルーティングを使ってサーバー側でレンダリングをします。

特定ページのアクセスを静的ページ化する

実際の実用例です。appディレクトリ配下にnameディレクトリ、nameディレクトリ配下に[name]ディレクトリを作成し、[name]ディレクトリ配下にpage.tsxを作成し、以下のように記述します。

"use server"

const paths = [
    {name:'yusuke'},
    {name:'nagaharu'},
    {name:'hisanori'}
]

export async function generateStaticParams() {
    return paths
}

export default async function Name({ params }) {
    const result = paths.some(path=>path.name===params.name)
    return (
        <main>
            {result ?
            <>
                <h1 className="title">Name = {params.name}</h1>
                <p className="msg">{params.name}さん、こんにちは</p>
            </>
            :
            <>
                <h1 className="title">{params.name}</h1>
                <p className="msg">「{params.name}」は使えません</p>
            </>
            }
            <div>
                <a href="/">go back!</a>
            </div>
        </main>
    )
}

登録されている名前でアクセスしたら以下のような表示となります。

登録されていない名前でアクセスすると以下のようになります。

ここではあらかじめpathsという定数にパラメーターの情報を用意してあります。パラメーターの値をオブジェクトにまとめたものを配列として用意しています。今回はnameパラメーターしかないのでそれだけを値として用意しています。この値をそのままgenerateStaticParams関数でreturnしています。そしてコンポーネントの関数は以下のような形で定義をしています。

export default async function Name({ params }) {...

サーバー側で実行するためasyncで非同期関数にしています。そして引数には{ params }を指定しています。これによりparamsにパラメーターとして渡された値が保管されます。このparamsからnameの値を取り出しpaths内に値があるかどうかを調べます。

const result = paths.some(path=>path.name===params.name)

someメソッドは配列の各要素ごとに引数のアロー関数を呼び出し、結果がtrueのものがあるかどうかをチェックします。アロー関数ではpath.name===params.nameをチェックしpath.nameとparams.nameが等しいかを調べています。これにより等しいものがあればtrue、なければfalseが得られます。

クライアントコンポーネント

サーバー側でレンダリングできないものはクライアントコンポーネントとして扱われます。これはクライアント側でしか動作しない昨日を利用するコンポーネントです。クライアント側でしか動作しない機能というのはJavaScriptでクライアントを操作するような機能を使う場合にはもちろんですがそれ以外に以下のようなものがインポートされていて使われていると自動的にクライアントコンポーネントとみなされます。

import { useState } from 'react'

これはReactのステートフックです。Reactのステートを利用している場合、自動的にクライアントコンポーネントとなります。

import { useRouter } from 'next/navigation'

useRouterはNext.jsの機能でルーティングに関する情報を扱うためのものです。ルーターによるページ移動を利用して前のページに戻ったり次のページへ進んだりあるいはルーターにページのパスを追加したりする機能が用意されています。

imort { useSearchParams } from 'next/navigation'

useSearchParamsはクエリーパラメーターを扱うためのものです。アクセスしたパスからクエリーパラメーターの情報を取り出す機能を提供します。

クエリーパラメーターを利用する

これらの中でも利用することの多い「useSerchParams」について簡単に触れておきましょう。これはクエリーパラメーターを取得するためのものです。クエリーパラメーターというのはパスの後に?をつけて記述されるパラメーターのことです。例えば/hoge?abc=100&xyz=200というようにして記述されるのがクエリーパラメーターです。キー(パラメーター名)と値をイコールで繋いだ形をしており、それぞれのパラメーターは&で接続されます。このuseSearchParamsは関数であり以下のようにしtオブジェクトを取得します。

変数 = useSearchParams()

これでクエリーパラメーターを扱うための機能をまとめたオブジェクトが得られます。ここから必要な値を取り出して利用します。値の取得には「get」メソッドを使用します。パラメーターのキーを引数に指定して呼び出せばその値が得られます。

では実際にクエリーパラメーターを利用する例を挙げていきます。まず値の表示に使用するスタイルクラスを追加します。global.cssに以下を追記します。

ul,ol {
  @apply text-xl m-5 font-bold list-disc;
}

li {
  @apply mx-10 font-normal text-blue-700;
}

「app」ディレクトリ内にあるpage.tsxを以下のように書き換えます。

"use client"
import { useSearchParams } from 'next/navigation'

export default async function Home() {
  const searchParams = useSearchParams()
  return (
    <main>
      <h1 className='title'>Index page</h1>
      <ul>
        <li>ID: {searchParams.get('id')}</li>
        <li>PASS: {searchParams.get('pass')}</li>
      </ul>
    </main>
  )

}

トップページに/?id=◯◯&pass=◯◯というような形でアクセスしてみます。すると以下のような画面となります。

ここではまずuseSearchParams関数でオブジェクトを取り出しています。

const searchParams = useSearchParams()

あとはこのsearchParamsから必要なパラメーターの値を取り出して表示するだけです。returnするJSX内で以下のようにして利用しています。

 <li>ID: {searchParams.get('id')}</li>
 <li>PASS: {searchParams.get('pass')}</li>

これでidパラメーターとpassパラメーターの値が表示されます。

このようにクライアント側でしか動かない機能というのはアクセスした時の状況に応じて値を取り出したり操作をするようなものと言えます。

さいごに

Appルーターにおけるレンダリングについてでした。従来のpagesルーターとは異なることがわかったと思います。現在はAppルーターが推奨されているため、Appルーターを使用するようにしましょう。

この記事を書いた人

azuma

お問い合わせ

Contact

お見積ご相談は無料です。
どうぞお気軽にご相談くださいませ。