進化が止まらない!Next.js13の基本機能をしっかり理解しよう

Next.jsは現在最も人気のあるReactベースのフルスタックのJavaScriptフレームワークです。バージョンがアップする毎に新しい機能が次々に追加されNext.js13からServer ComponentsなどReactの最新機能を利用したApp Routerが登場しました。App Routerはファイル名でルーティングを設定していた既存のPage Routerとは全く異なる機能で設定方法も一から学び直す必要があります。新たにプロジェクトを作成するのであればApp Routerを利用することが推奨されていますが同時に両方の機能を利用することも可能です。
次々に新しい機能が追加される反面、ネット上に公開されている記事もすぐにOutDatedなものになっています。この文書もすぐにOutdatedなものになってしまうと思いますが現在(2023年5月)の最新バージョン13.4のドキュメントを参考に実際にNext.js13を利用しながら基本的な機能について説明を行っています。2024年3月の最新バージョンは14.1.4です。
目次
プロジェクトの作成
実際に公式ドキュメントを参考に手を動かしながら説明を進めていくため最初にNext.jsのプロジェクトの作成を行います。プロジェクト名は任意の名前をつけることができるのでここではnext-js-13としています。npx create-next-app@latestコマンドを実行するとTypeScript, ESLintをプロジェクトで利用するかどうか聞かれますがすべてデフォルトの値を選択しています。 App Routerを利用するかどうかも選択することができますがrecommededと表示されているため新しくプロジェクトを作成する場合にApp Routerを利用することが推奨されています。
プロジェクトの作成が完了後、プロジェクトフォルダnext-js-13に移動してpackage.jsonファイルでインストールしたパッケージのバージョンを確認しておきます。nextパッケージのバージョンが13.4以上であることを確認しておきます。
package.jsonファイルを確認後、npm run devコマンドを実行して開発サーバを起動してブラウザからアクセスを行い、初期ページが表示されることを確認します。初期画面に表示されている内容はappディレクトリの直下に保存されているpage.tsxファイルに記述されています。

Routingの設定
これまで利用してきたファイルシステムベースのルーティングはPages Routerと呼ばれNext.jsからはApp Routerという新機能が登場しました(Next.js 13.4からStable)。Pages RouterとApp Routerでは設定方法が異なります。どちらの機能も利用できるためNext.jsのドキュメント上ではApp Router、Pages Routerを切り替えることでそれぞれのRouterの機能と設定方法を確認することができます。App Routerのドキュメントを確認する場合は”Using App Router”を選択して読み進めてください。

”Routingの設定”では新機能のApp Routerについて説明を行なっていきますがPages Routerとの設定方法の違いを確認するため最初にPages Routerでのルーティング設定方法も確認しておきます。

Pages Router
インストール時にApp Routerを利用することを選択しましたがPages Routerも引き続き利用することができます。Pages Routerではpagesディレクトリの下にルーティングのファイルを作成していくため新たにプロジェクトディレクトリ直下にpagesディレクトリを作成することから始めます。pagesディレクトリの作成が完了したらabout.tsxファイルを作成します。
ファイルを作成するだけでNext.jsが自動でルーティングが設定してくれるのでファイル名がそのままURLとなります。ブラウザからhttp://localhost:3000/aboutにアクセスするとabout.tsxファイルで記述した内容がそのままページに表示されます。このようにPages Routerではファイル名とURLが連動します。/about/index.tsxファイルと設定することも可能です。

App Router
App Routerではappディレクトリの下にファイルを作成していきます。プロジェクト作成時にApp Routerの利用を選択しているためデフォルトでappディレクトリは作成されています。
Pagesの設定
/aboutのルーティングを設定するためにはファイルではなくaboutディレクトリをappディレクトリの下に作成する必要があります。その後aboutディレクトリの下にpage.tsxファイルを作成します。ファイル名はTypeScriptであればpage.tsxまたはpage.ts、JavaScriptではpage.jsxまたはpage.jsとする必要があります。
ファイルを保存するとApp RouterとPage Routerで別々に設定したaboutがコンフリクト(衝突)しているということでnpm run devコマンドを実行したターミナルにはエラーメッセージが表示されます。

コンフリクトの問題を解消するためにappディレクトリの設定かpageディレクトリの設定のどちらかを変更する必要がありますがここではpagesディレクトリを削除します。pagesディレクトリのabout.tsxファイルのファイル名を変更することでもエラーは解消されます。エラーの解消後はappディレクトリで設定したabout/page.tsxファイルの内容がブラウザに表示されます。

Layoutの設定
ブラウザ上に表示されたaboutページの画面にはグラデーションが入っていますがpage.tsxファイルにはCSSによるスタイルを設定していません。ではなぜpage.tsxファイルにCSSが設定されていないにも関わらずCSSによるスタイルが設定されているのでしょう。その理由はappディレクトリ下のlayout.tsxファイルの設定が反映されているためです。layout.tsxは名前の通りレイアウトに関するファイルでApp Routerではappディレクトリ直下に保存されたlayoutx.tsxファイルがすべてのpage.tsxに適用されます。layout.tsxの中身を確認します。html, bodyが利用されており、childrenの部分にpage.tsxのコンテンツが挿入されます。
layout.txsファイルではglobals.cssがimportされていることがわかります。globals.cssファイルの中でbodyタグに対してCSSが設定されているのでCSSを削除するとグラデーションは消えます。
appディレクトリ直下のlayout.txsファイルは必須ファイルのためlayout.tsxファイルの名前を変更したり削除するとpage.tsxファイルにlayoutが存在しないためNext.jsが自動でlayout.tsxファイルを作成します。
Nested Layoutの設定
Layoutファイルはappディレクトリ直下だけではなくPageディレクトリの配下にあるpage.tsxファイに共通のLayoutを適用したい場合にPageディレクトリの直下にlayout.tsxを作成することができます。
aboutディレクトリの下にlayout.tsxファイルを作成します。Tailwind CSSのUtility Classを利用して中央に表示するように設定しています。

aboutディレクトリの中にさらに別のPageディレクトリを作成することができます。例えばaboutディレクトリの下にinfoディレクトリを作成してpage.tsxファイルを作成するとaboutディレクトリに作成したlayout.tsxファイルが適用されます。aboutディレクトリだけではなくappディレクトリ直下にあるlayout.tsxファイルも適用されます。
Route Groupの設定
App Routerではappディレクトリにディレクトリを作成することでルーティングを作成していきますがルーティングのパスに影響を与えないディレクトリを作成することができます。その方法の一つにRoute Groupがあります。Route Groupを設定するディレクトリは()で囲む必要があります。()で囲んだディレクトリ下にはパスに影響を与えることはありませんがlayout.tsxファイルを作成することができるのでRoute Group以下のすべてのページに作成したlayout.tsxファイルを適用することができます。言葉よりも実際に設定した方が理解が進むので任意の名前のmarketingディレクトリを作成します。
(marketing)ディレクトリの下にはlayout.tsxファイルを作成して以下のコードを記述します。
(marketing)ディレクトリの直下にaccountディレクトリを作成してpage.tsxファイルを作成します。
ブラウザからアクセスする場合は()で囲んだディレクトリはルーティングのパスに影響を与えないのでmarketingを省いて/accountでアクセスを行うことができます。

(marketing)ディレクトリの下にはaccount以外にもPageディレクトリを作成してpage.tsxファイルを作成すると(marketing)ディレクトリの下のlayout.tsxファイルが適用されます。
Dynamic Routesの設定
ここまでの設定ではaboutやaccountなどルーティングが静的でURLが変わらないページの設定を行いました。しかし実際のアプリケーションでは静的なURLばかりではなく例えばブログの記事を表示したい場合には/blog/1, /blog/2, /blog/what-is-next.js…などのように動的に変わるルーティングに対応させる必要があります。
Dynamic Routesを設定するためにappディレクトリ直下にblogディレクトリを作成します。/blog/1, /blog/2でアクセスするためにblogディレクトリの下にさらにディレクトリを作成しますがDynamic Routesの場合は[]でディレクトリ名を囲みます。ここでは[id]ディレクトリを作成します。idは任意なのでslugやblogIdと設定することもできます。名前は任意ですが後ほどこの名前はコードの中で利用するので役割に応じた適切な名前をつけてください。
[id]ディレクトリの下にpage.tsxファイルを作成して以下のコードを記述します。
/blog/1, /blog/2, … /blog/100でアクセスすることが可能になり、/blog/以下にどのような文字列を設定してもpage.tsxファイルに記述した内容が表示されます。


Dynamic Routesのページでは/blog/以下に指定した値はPropsを利用して取得することができます。最初はどのような値がPropsに含まれているかわからないのでconsole.logを利用してpropsの値を取得します。
ブラウザから/blog/100にアクセスするとブラウザのデベロッパーツールのコンソールではなく開発サーバを起動したターミナルにpropsの値が表示されます。ターミナルに表示されることからpageファイルのコードがサーバ側で実行されていることがわかります。
idはディレクトリ名に設定した名前と一致し[slug]という名前にした場合はidではなくslugプロパティとして保存されます。
propsに含まれるオブジェクトがわかったのでTypeScriptを利用している場合はPropsの型を指定しparamsに含まれるidをブラウザ上に表示させます。

/blog/以下の値を変更するとその値がブラウザに表示されます。1, 100などの数値ではなくwhat-is-nextjsなどの文字列でも表示されます。
ここではURL/blog/以下の値を取り出し表示させるだけでしたが実際のアプリケーションではこの値を利用してデータベースにアクセスしてレコードを取得したり、さらに別のサーバにアクセスを行いデータを取得してページを表示するといった設定を行います。
catch-all-segmentsの設定
/blog/1, /blog/2, …, /blog/100でアクセスを行うことができましたが/blog/1/2/3でアクセスが行われた場合にはどのような方法で対応するのか確認していきます。
/blog/1/2/3でアクセスした場合にpropsのparamsどのような値が含まれるかconsole.logを利用してparamsの中身を確認します。
ブラウザから/blog/1/2/3にアクセスすると404ページが表示されるためparamsの値を確認することはできません。

1,2,3の値を取得するためにはディレクトリ名を[id]から[…id]に変更します。設定後、再度ブラウザからアクセスすると配列の形で1,2,3の値を取得することができます。
paramsの型も下記のように配列で設定します。
こちらは通常の設定方法ですが、/blog/1/2/3でページを表示させるためには[id]ディレクトリの下に[userId]、さらにその下に[categoryId]を作成して[categoryId]の下にpage.tsxファイルを作成します。useIdとcatagoryIdは任意の名前をつけることができますがid, userId, categoryIdと異なる名前をつけてください。同じ名前をつけた場合には”Error: You cannot have the same slug name “id” repeat within a single dynamic path”のメッセージが表示されます。
ターミナルにはオブジェクトとして下記のように表示されます。
paramsの型は下記のように設定します。
Linkの設定
Linkコンポーネントを利用することでページ間をスムーズに移動することができます。aboutページから/(ルート)ページに移動できるようにLinkコンポーネントを利用して設定を行います。Linkコンポーネントを利用するためにはnext/linkのimportが必要となります。Linkコンポーネントではhref propsに移動先のページのURLを設定します。
aboutページにアクセスを行い、表示されているHomeの文字列をクリックすると/(ルート)へ移動します。

appディレクトリのpage.tsxファイルにもLinkコンポーネントを設定してaboutページへ移動できるように設定を行っておきます。
Aboutのリンクをクリックするとaboutページに移動できません。

prefetchの設定
Linkコンポーネントではデフォルトからprefetchの機能が設定されています。開発環境ではリンクにカーソルを当てるとリンク先のページに関するJavaScriptファイルなどがバックグランドで自動でダウンロードされます。本番環境ではViewportに入っているリンク先のファイルがバックグランドで自動でダウンロードされます。
prefect機能を利用したくないという場合はLinkコンポーネントのprefetch propsをfalseにすることで無効化できます。
Parallel Routesの設定
Parallel Routingを利用することで1つのLayoutで複数のPageコンポーネントを表示させることができます。
appディレクトリの下に@analytics, @teamの2つのディレクトリを作成します。Route Groupでは()を利用しましたがParallel Routingでは@をディレクトリの先頭につけることでルーティングのパスに影響されないPageコンポーネントとなります。それぞれのディレクトリの下にpage.tsxファイルを作成して以下のコードを記述します。
追加したPageコンポーネントはappディレクトリ直下のlayout.tsxファイルで設定を行います。childrenと同様にteamとanalyticsをpropsで設定します。
以上で設定は完了です。
/(ルート)にアクセスするとchildretに対応するapp/page.tsxと@analytics, @teamのpage.tsxファイルの内容がブラウザ上に表示されます。

Aboutページのリンクをクリックしても引き続き複数のPageコンポーネントがブラウザ上に表示されます。

ここまでは設定通りに動作しましたが/aboutページでリロードを行ってください。リロードを行うと404ページが表示されます。

Linkコンポーネントによるページ移動(Soft Navigation)では設定通りに動作しますがリロードのように直接ページにアクセスするような場合(Hard Navigation)には設定通りには動作しません。
404ページを表示させないためには@analytics, @teamディレクトリの下にdefalut.tsxファイルを作成する必要があります。
defalut.tsxファイルを作成後、/aboutのページでリロードを行うとdefault.tsxファイルに記述した内容が表示されます。

@analyticsディレクトリのdefault.tsxファイルでのみ同じディレクトリにあるpage.tsxをimportして表示されるか確認を行います。
importしたPageコンポーネントが表示されることが確認できます。

Loadingの設定
外部からのデータ取得
外部のデータリソースからfetch関数を利用してデータを取得するために無料で利用することができるJSONPlaceHolderを使います。JSONPlaceHolderが提供するURLにアクセスするとJSONデータを取得することができるので開発など外部のリソースを利用した動作確認に活用できます。
usersページを作成してユーザ一覧を表示させるためにappディレクトリの下にusersディレクトリを作成してpage.tsxファイルを作成します。
App Routerを利用した場合デフォルトですべてのコンポーネントはServer Componentsとして動作するためサーバ側ですべての処理が行われます。Server Componentsではサーバ側でfetch関数が実行されるためconsole.logを利用した場合は開発サーバを実行しているターミナルに取得した情報が表示されます。クライアント(ブラウザ)でfetch関数を実行するた場合はブラウザのデベロッパーツールのコンソールには取得したデータが表示されますがServer Componentsの場合には表示されません。
ブラウザ側ではサーバで処理が完了したデータを受け取り描写することが確認できます。

Loading設定の動作確認のため、aboutページからusersページを移動できるようにaboutページのリンク先をHomeからUserに変更します。
aboutページからリンクを利用してuserページに移動できるようになりました。
遅延処理の追加
Server Componentsではサーバ側ですべての処理が行われるためデータの取得に時間がかかっている場合にどのような動作になるのか理解しておく必要があります。動作を確認するためPromiseとsetTimeoutを利用して意図的に遅延を作ります。遅延の処理を追加する前にaboutページからのusersページへのリンクをクリックすると即座にユーザ一覧が表示されることを確認しておきます。
usersディレクトリのpage.tsxファイルでfetch関数を実行する前に5秒間の遅延を追加します。
遅延処理を追加後にaboutページのusersページへのリンクをクリックします。クリックして5秒間何も画面に変化はありません。5秒経過するとユーザ一覧が表示されます。このことからServer Componentでの処理が完了するまでページが表示されないことがわかりました。
loading.tsxファイルの設定
処理が完了するまでページ上で何も変化がないのはユーザにとって気持ちのいいものではなく離脱につながります。ページが表示されない問題を解決するためにサーバ側での処理中にブラウザ上に現在データのローディング中であることを伝えるメッセージを表示させるためusersディレクトリにloading.tsxファイルを作成します。
aboutページのusersページへのリンクをクリックするとloading.tsxに設定した内容がブラウザ上に表示されます。loading.tsxファイルを設定することで現在データを取得中であることがわかるようになりました。

UserListコンポーネントの作成
手動で行うLoading設定の準備としてusers/page.tsxファイルからユーザ一覧の処理部分を取り出すためusersディレクトリにUserList.tsxファイルを作成します。
作成したUserListコンポーネントをusers/page.tsxファイルでimportします。
page.tsxファイルのUserListタグの箇所にTypeScriptに関するメッセージがに表示されます。エラーの表示される原因はUserListコンポーネントがasyncを利用した非同期のServer Componentのためです。asyncを利用していないコンポーネントの場合にメッセージは表示されません。

メッセージの表示を止めるためドキュメントに記載されている内容を元に一時的な対応策として”{/* @ts-expect-error Async Server Component */}
“のコメントをUserListコンポーネントの上に追加します。追加するとTypeScriptに関するメッセージは表示されなくなります。
動作確認を行うとUserListコンポーネントを作成する前と変わらずaboutページからUserページに移動するとブラウザ上に”ローディング中”の文字が表示されます。
page.tsxファイルはデフォルトでSever Componentとして動作しますがUserList.tsxファイルもどのようにデフォルトでServer Componentとして動作します。
手動でのSuspenseの設定
loading.tsxファイルを作成することでサーバでの処理中にLoadingのメッセージが表示されてるようになりましたがこれはReactが持つSuspenseの機能を利用して行われています。Next.jsがSuspenseの設定を自動で行ってくれるためSuspenseが利用されていることを意識することはありませんがloading.tsxファイルを利用せずSuspenseを明示的に利用して設定を行うことができます。
usersディレクトリに作成したloading.tsxファイルを削除するか別名で保存してください。
次にUserListタグをSuspenseタグでラップしてfallback propsにサーバ処理中にブラウザ上に表示させたいメッセージを設定します。Suspenseはreactからimportします。
Suspenseの設定後、aboutページからusersページに移動します。先程とは異なりユーザ一覧の文字列は即座に表示されSuspenseでラップしたUserListコンポーネントの箇所にのみ”Loading…”の文字が表示されます。

loading.tsxを利用した場合にはページ全体に対して自動でSuspenseが設定されているのでページ内のいずれかのコンポーネントのデータ取得処理が行われている場合はページ全体に対してloading.tsxファイルの内容が表示されます。Suspenseをコンポーネント単位でラップすることでそのコンポーネントに対するLoading設定を行うことができます。
別々のSuspeseタグでラップした複数のコンポーネントを1つのページに設定して異なる時間で遅延を行った場合にどのような動作になるか確認します。usersディレクトリにOtherUserList.tsxファイルを作成してUserList.tsxファイルの内容をコピーして遅延の時間のみ変更を行います。遅延時間を2秒に設定しています。
users/page.tsxファイルに作成したOtherUserListコンポーネントを追加します。UserListとOtherUseListコンポーネントには別々のSuspenseタグを設定して、fallbackのメッセージの内容を変更してUserListのfallbackには文字にカラーを設定しています。
aboutページからusersページに移動直後はどちらもサーバ上で処理が行われているためfallbackのメッセージが表示されます。

2秒経過するとOtherUserListのサーバ上での処理が完了してユーザ一覧が表示されます。UserListは引き続きサーバ上で処理を行っているのでfallbackのメッセージが表示されています。

5秒経過するとUserListのサーバ上での処理も完了するのでUserListコンポーネントの処理で取得したユーザ一覧が表示されます。
このように Suspenseタグを利用することですべてのページ上の処理が完了して一括でブラウザ上に表示されるのではなく処理が完了したコンポーネント毎にサーバからデータを受け取りブラウザ上に表示させることができます。この機能をStreamingと呼びます。
Error Handling
users/page.tsxファイルとUserList.tsxファイルを利用してError Handlingの動作確認を行います。
users/page.tsxファイルではUserListコンポーネントをimportしています。
UserList.tsxファイルではfetch関数によるデータ処理に失敗した場合にエラーをthrowさせるためresponse.okを使って分岐を行います。fetch関数の処理に失敗した場合にresponseオブジェクトのokプロパティにはfalseが入ります。
fetch関数の引数で設定しているURLを存在しないhttps://jsonplaceholder.typicode.com/userに変更します。”users”から”s”を削除して”user”としています。存在しないURLにアクセスを行うとresponse.okの値がfalseになるためErrorがthrowされます。
Userページ(/users)にブラウザからアクセスするとエラーによりUnhandled Runtime Error画面が表示されます。

右上に表示されている”X”ボタンをクリックすると画面の左下にボックスが表示され再度”X”をクリックすると先程表示されていた”Unhandled Runtime Error”の画面が表示されます。

Unhandled Runtime Error画面ではなくエラーメッセージのみを表示させるようにError Handlingの設定を行なっていきます。
usersディレクトリにerror.tsxファイルを作成して以下のコードを記述します。
Pageディレクトリの直下にerror.tsxファイルを作成するとNext.jsが自動でUnhandled Runtime Errorが発生した場合にエラーをハンドリングするためerror.tsファイルが利用されます。エラーハンドリングにはReactのErrorBoundaryを利用しておりErrorBoundaryタグでpage.tsxをラップする形で設定が行われます。
App RouterではデフォルトではすべてのコンポーネントがServer Componentでサーバ側で処理が行われます。error.tsファイルはクライアント(ブラウザ)側で処理を行う必要があるためファイルの先頭に”use client”を追加する必要があります。”use client”を明示的に設定することでコンポーネントがServer ComponentからClient Componentに変わります。
error.tsxファイルを設定後にusersにアクセスするとerror.tsxファイルで設定したメッセージがブラウザ上に表示されます。左下に表示されているボックスの”X”をクリックするとUnhandled Runtime Errorの画面が表示されます。

エラー画面からの復帰
ここでの設定では存在しないURLを設定しているので何度/usersにアクセスしてもエラーが発生します。しかし本番では一時的にエラーが発生しているため再度/usersにアクセスするとエラーが解消に正常に動作することもあります。エラーが解消した場合にエラー画面から復帰するためにreset関数が準備されているのでreset関数の処理をerror.tsxファイルに追加します。
/usersにアクセスするとエラーメッセージと”Try again”ボタンが表示されます。

“Try again”ボタンをクリックしても現在の設定では引き続きエラーが発生するので同じ画面が再表示されます。
Client Component
App RouterではすべてのコンポーネントはデフォルトでServer Componentです。クライアント(ブラウザ)側でインタラクティブな操作を行うためにはClient Componetとして設定を行う必要があります。
ユーザがボタンをクリックするとカウンターの数字が増えるCounterコンポーネントを利用してClient Componentについて確認していきます。
Counterコンポーネントの作成
appディレクトリの直下にCounter.tsxファイルを作成して以下のコードを記述します。これまでのコンポーネントとは異なりuseState Hookを利用しています。ボタンをクリックするとuseStateで定義した状態countの値が増えるだけのシンプルなコードです。
作成したCounterコンポーネントをappディレクトリ直下にあるpage.tsxファイルからimportします。
Failed to compileのエラー画面が表示されます。エラーの原因も丁寧に表示されています。”You’re importing a component that needs useState. It only works in a Client Component but none of its parents are marked with “use client”, so they’re Server Components by default.”

デフォルトではServer Componentsとして動作するためuseState Hookを利用するためには’use client’の設定を行いClient Componentとして動作させる必要があります。
指摘通り、Counter.tsxファイルの先頭に’use client’の1行を追加します。’use client’はimport文よりも前のファイルの先頭に記述する必要があります。
‘use clinet’を設定したことでエラーが解消されブラウザ上にはカウンターが表示され”increment”ボタンをクリックするとCountの値が増えていきます。

Client Component
‘use client’を追加することでServer ComponentからClient Componentになりクライアント(ブラウザ)側でuseState Hookを利用した処理が行えるようになりました。
Counter.tsxファイルの親コンポーネントであるapp/page.tsxファイルに’use client’を設定しても動作するか確認します。Counter.tsxの’use client’は削除しておきます。
親コンポーネントに’use client’の設定を行なってもCounterは動作します。Pageコンポーネントに含まれているコンポーネントがClient Componentの場合は親コンポーネントに’use client’に設定することで各コンポーネントで’use client’を設定する必要がなくなります。
動作確認が完了したらpage.tsxから’use client’を削除してCounter.tsxファイルの先頭に’use client’を戻してください。
Server ComponentをClient Componentで利用
Server ComponentをClient Component内で利用したい場合にはpropsを利用します。
Server Componentとしてusersディレクトリに作成済みのUserLst.tsxファイルを利用します。サーバ側でのみ実行されるか確認するためにconsole.logを設定しています。
Counter.tsxファイルではServer Componentを受け取れるようにchildren propsを設定します。
最後にappディレクトリのpage.tsxファイルでUserListコンポーネントをimportしてCounterタグの間に挿入します。
console.logの内容は開発サーバを起動したターミナルに表示されることが確認できpropsを利用することでClient Componentの中でServer Componentが利用できることが確認できました。

Client Componentのpre-rendered
Next.jsのドキュメントには”Client components are pre-rendered on the server as HTML to produce a faster initial page load”(最初のページ読み込みを高速化するために、サーバー上で HTMLとしてプリレンダリングされます。)と記載されているので動作確認を行います。
/(ルート)のページにアクセスを行い最初のページ読み込みを行うためリロードします。リロード後にClient Componetが事前にプリレンダリングされているか確認するためネットワークタブを確認します。
ブラウザ側ではサーバからのResponseとしてプリレンダリングされたHTMLとして受け取っていることが確認できます。

Server Components vs Client Components
Server ComponentsとClient Componentsの使い分けについてはNext.jsの公式ドキュメントに掲載されているので参考にしてください。

Fetch data, バックエンドリソース(データベースなど)への直接なアクセス, アクセストークンやAPIキーの利用、大きさサイズのパッケージを利用する処理についてはServer Components、onClickなどユーザとのインタラクティブがあるもの、useStateなどのReact Hook, ブラウザのAPIなどについてはCliet Componentsを利用することを推奨しています。Fetch dataなどはクライアントでも行えますし、アクセストークンなども利用できますがアクセストークンをクライアントコンポーネント内で利用するとアクセストークンの中身がユーザから閲覧できてしまうのServer Componentsを利用することになります。
Server Componentのみで利用するパッケージはブラウザ側でダウンロードされることがないのでJavaScriptのバンドルサイズを小さくすることができます。そのため大きなサイズのパッケージはServer Componentで利用することが推奨されているのでKeep large dependencies on the serverはServer Component側にチェックが入っています。大きなサイズのパッケージがClient Componentで利用できないわけではありませんがダウンロードするJavaScriptのバンドルが大きくなのでパフォーマンスへの影響が出てきます。
Context
コンポーネント間でデータを共有したい場合にContextを利用することができます。先ほど作成したCounterの処理をContextを利用して書き換えます。
ContextはClient Componentでしか利用することはできません。そのためClient Componentの中で定義する必要があります。
プロジェクトディレクトリの直下にcontextディレクトリを作成してConterProvider.tsxファイルを作成します。Client Componentの設定を行うためファイルの先頭には’use client’を設定します。それ以外については通常のTypeScriptのコードと違いはありません。
作成したConterProviderをappディレクトリ直下のlayout.tsxファイルでimportします。
Contextの設定は完了です。RootLayoutでContextを行ったのでapp下のClient ComponentであればContextを利用することができます。ここではappディレクトリ直下に作成済みのCounter.tsxファイルで利用します。Client Compnentでしか利用できないため’use client’をファイルの先頭に記述しています。ConterProviderからuseCounter関数をimportすることでcount, setCountをコード内で利用することができます。

表示される内容はContextを利用する前と変わりませんがincrementボタンをクリックするとCountの数が増えます。

Route Handlers
App RouterのRoute HandlersはPages RouterのAPI Routesと同等の機能です。Route Handlersを利用することでGET, POSTなどのHTTPメソッドを利用してアクセスすることでAPIエンドポイントを設定するができます。Route Handlersも
appディレクトリの中に任意の名前のディレクトリを作成します。ここではapiという名前のディレクトリを作成します。Pageコンポーネントの名前がpage.tsxで決められているようにRoute Handlersではroute.jsまたはroute.tsという名前をつける必要があります。
GETリクエストの動作確認
apiディレクトリにroute.tsファイルを作成して以下のコードを記述します。
/apiに対してGETリクエストを送信するとJSONで{“name”:”John Doe”}が戻されるというもっともシンプルなコードです。
GETリクエストであればブラウザからアクセスすることで動作確認できます。

Route Handlersからのデータ取得
JSONPlaceHolderからデータを取得することもできます。
ブラウザからもアクセスできますがUserListコンポーネントからアクセスを行ってみます。
ブラウザから/usersにアクセスするとRoute Hadlersを経由してデータの取得が行われ、ユーザ一覧が表示されます。
URLパラメータの取得
検索などURLパラメータを利用した場合のURLパラメータの取得方法を確認します。
UserLists.tsxファイルではfetch関数で指定するURLにパラメータを追加します。App Routerでfetch関数はWeb APIのfetch関数を拡張しているためオプションを設定することができます。デフォルトでは一度fetch関数が実行されるとキャッシュされるためその後fetch関数を実行するとキャッシュしたデータが利用されるためリクエストが行われません。ここでは動作確認のまたキャッシュ機能を無効にします。
api/route.tsではURLパラメータを取得するためrequestオブジェクトを利用します。requestオブジェクトにはさまざまな情報が含まれていますがURLパラメータはrequest.urlを利用して取得します。
ブラウザから/usersにアクセスすると開発サーバを起動したターミナルに”John”が表示されます。URLパラメータを取得することができるようになりました。
headers, cookies関数を利用することでそれらの情報を取得することができます。
Dynamic Routesの設定
apiのように静的な APIのエンドポイントではなくapi/1, api/2,…api/100などのようにURLが動的に変わる場合の設定方法を確認します。
apiディレクトリの下に[]で囲んだ[id]ディレクトリを作成してその下にroute.tsファイルを作成します。
ブラウザから/api/100にアクセスすると”100″が戻されます。
実際のアプリケーションではこの値を利用してデータベースにアクセスしてレコードを取得したり、さらに別のサーバにアクセスを行いデータを取得してページを表示するといった設定を行います。
POSTの設定
POSTリクエストによって送信されてきたデータを取得する方法を確認します。
api/route.tsファイルでPOSTリクエストで送信されてきたデータを取り出すためのコードを追加します。関数の名前はPOSTとなります。request.json()で取得したデータをそのままクライアントに戻しています。通常はデータベースなどへのデータの挿入などを行います。
POSTリクエストを送信するためには入力フォームを作成する必要がありますがここではfetch関数を利用してPOSTリクエストでデータを送信するコードのみusers/page.tsxファイルに追加します。
/usersにアクセスを行い、bodyプロパティに送信したオブジェクトの中身が開発サーバを起動しているターミナルに表示されればRoute Handlersで正しくPOSTリクエストを受け取り、受け取ったデータをブラウザに戻していることになります。
以下のように表示されれば正しく動作しています。
実際のアプリケーションではPOSTリクエストから送信されてきたデータをデータベースに登録するといった処理を行います。
Fetching
これまで触れてきませんでしたがfetch関数を実行すると開発サーバを起動したターミナルに下記のようなメッセージが表示されていました。
注目したいところはcacheの値で上記ではMISSと表示されています。これはcacheにデータが存在しないためデータを取得するためにfetch関数を実行しています。その後再度ページを開くとcacheの値がHITになっています。これはcacheの値を利用したのでfetch関数を実行されません。
このようにデフォルトの設定ではcacheを利用するような設定になっています。
GETの行にはアクセスのあったURLとステータスコード、経過した時間が表示されています。cacheがHITした場合には時間がかなり短くなっていることがわかります。
fetch関数のcacheオプション
このようにApp Routerでfetch関数を利用すると自動でcacheを利用する設定になっています。これはWeb APIのfetch関数を拡張しているためでオプションを利用してキャッシュの設定を変更することができます。
デフォルトではforce-cacheが設定されています。force-cacheのほかにcacheを無効にするno-storeがあります。
どちらも値の名前でどのような設定か想像できると思いますがcacheプロパティの値にno-storeを設定します。
設定後は何度/usersにアクセスしてもcacheの値が”MISS”のままです。
http://localhost:3000/apiにGETリクエストを送信しているので実際にリクエストが送信されているのか確認するためにconsole.logを設定します。
設定後は毎回アクセスするたびに”GET Request”が表示されます。つまりapi/route.tsにGETリクエストが送信されてきていることになります。
cacheの設定を削除してデフォルトの状態に戻します。削除するとデフォルトのforce-cacheとなります。
cacheの値がHITとなり、fetch関数が実行されなくなり/api/routes.tsにGETリクエストが送信されなくなるためGET Reqeustのメッセージが表示されることはなくなりました。
fetchのnext.revalidateの設定
cacheオプションではcacheを利用するかどうかの設定でしたがnext.revalidateオプションを設定することでcacheのライフタイムを指定することができます。設定したライフタイムを経過するとfetch関数が新たに実行されます。
revalidateの値で秒数を設定することができるので下記では5秒を設定しています。fetchでアクセスを行い5秒間の間はcacheの値を利用しますがその時間を過ぎると再度fetch関数が行われます。
Automatic Request Deduping
Automatic Request Dedupingは複数のコンポーネントで同じURLに対して同時にリクエストを送信する際にリクエストの重複をなくしてリクエストを最適化する機能です。
下記はNext.jsのドキュメントに記載されているイメージ図ですが複数のコンポーネントの重複したリクエストを最適化しているのが理解できるかと思います。この機能により重複したリクエストの数を減らすことができます。

Database
Prismaと手軽に利用できるSQLiteデータベースを利用してServer CompoentからPrismaを経由してSQLiteデータベースにアクセスを行い、データが取得できるかを確認します。
Prismaのインストール
Prismaを設定してSQLiteデータベースに接続するためにprismaパッケージのインストールを行います。
Prismaの設定
Prisma用の設定ファイルを作成するためにnpx prisma initコマンドを実行します。実行するとプロジェクトディレクトリ下にはprismaディレクトリと.envファイルが作成されます。prismaディレクトリにはPrismaの設定ファイルであるschema.prismaファイルが作成されています。.envファイルはデータベースに接続するために必要となる環境変数を設定するために利用します。SQLiteデータベースを利用するのでオプション–datasource-providerにsqliteを設定します。オプションを指定しない場合はデフォルトでデータベースにはpostgresqlが設定されています。
schema.prismaファイルでは–datasource-providerを指定した実行した場合はSQLiteデータベースに関する設定は完了しているのでモデルの設定を行います。モデルにはpostテーブルを作成するためのスキーマ(テーブルの構成情報)を追加します。
schema.prismaファイルでのモデルの完了したらSQLiteデータベースにpostテーブルを作成するためにnpx prisma db pushコマンドを実行します。
コマンドを実行すると.envファイルのDATABASE_URLで指定した場所にSQLiteのデータベースファイルが作成されます。schema.prismaファイルのmodel以外の設定を変更していない場合にはprismaフォルダにdev.dbファイルが作成されます。
Prisma Studioからのデータベース接続
PrismaにはPrisma StudioというPrisma専用のツールを利用してデータベースにアクセスを行うことができます。Prisma Studioを起動するためにnpx prisma studioコマンドを実行します。
ブラウザからhttp://localhost:5555にアクセスするとPostテーブルをブラウザ上から確認することができます。

ブラウザからデータを挿入することができるので動作確認用に2件のデータを追加します。

Prisma Clientの設定
Next.jsからデータベースに接続するためにPrisma Clientの設定を行う必要があります。設定を行うためにlibディレクトリをプロジェクトフォルダ直下に接続を行い、prisma.tsファイルを作成します。
Next.jsからデータベースに接続する場合はこのファイルからPrisma Clientのimportを行います。これでPrismaの設定は完了です。
データの表示
Prismaを経由してSQLiteのpostテーブルに保存されているデータを取得して表示するためにappディレクトリの下にpostディレクトリを作成してpage.tsxファイルを作成し以下のコードを記述します。
ブラウザから/postsにアクセスするとpostテーブルに保存したtitleが表示されます。

page.tsxファイルはServer Componentとして動作しているのでpage.txsファイルにデータベースへの接続処理のコードを記述することができます。
Route Handlersを利用した場合
Route Handlersを利用してAPIエンドポイントを作成した場合の動作確認も行っておきます。
appディレクトリ下にapiディレクトリを作成します。さらにpostsディレクトリを作成route.tsファイルを作成して以下のコードを記述します。
posts/page.tsxファイルはfetch関数を利用して作成した/api/postsからデータを取得します。
ブラウザから/postsにアクセスすると先ほどと同じ画面が表示されます。

データの登録
フォームを利用してデータベースにデータを登録する方法を確認します。
Next.js 13.4で新たに”Server Actions: Mutate data on the server with zero client JavaScript”が登場しました。Server Actionsについては現在アルファなので今後も仕様の変更が考えられるため下記の記事で紹介しています。そのため本文書ではServer Actionsを利用していません。
入力フォームに2つのinput要素を持ち、Postモデルで定義したtitleとcontentを入力してsubmitボタンをクリックするとhandleSubmit関数が実行され、Router HandlersにPOSTリクエストが送信されるシンプルなフォームです。ファイル名をAddPost.tsxファイルとしてpostsディレクトリの下に作成します。
作成後、page.tsxファイルでimportを行います。
送信されてきたPOSTリクエストを受け取りデータベースにデータ登録できるようにRoute Handlersの設定も追加します。更新するファイルはapp/api/posts/route.tsです。
Route Handlersの設定完了後、ブラウザから/postsにアクセスを行うとuseState Hookを利用しているのでエラーメッセージが表示されます。”You’re importing a component that needs useState. It only works in a Client Component but none of its parents are marked with “use client”, so they’re Server Components by default.”。デフォルトではappディレクトリ以下のコンポーネントはServer Componentとして動作するためuseState Hookを利用することができません。Client Componentとして動作させるためファイルの先頭に’use client’を追加します。
‘use client’を追加するとエラーは解消されるので記事一覧とフォームが表示されます。

titleとcontentに文字列を入力して”Submit”ボタンをクリックしてください。”Submit”ボタンをクリックしても入力したデータは表示されません。Prisma Studioを利用してデータが登録されているか確認するとPostテーブルには入力したデータが登録されていることが確認できます。
ページのリロードを行っても新しいデータが表示されることはありません。理由はfetch関数のオプションのcacheの値を”no-store”に変更していないためキャッシュに保存されたデータが表示されます。開発サーバを起動したターミナルにも”cache: HIT”が確認できます。
page.tsxファイルでfetch関数のオプションにcache:’no-store’を追加します。
no-storeを設定すると”cache: MISS”になるためキャッシュではないデータの再取得が行われます。

この状態で再度データの登録を行います。入力フォームに文字列を入力して”Submit”ボタンをクリックします。しかし画面には入力したデータは表示されません。Prisma Studioを確認すると問題なくデータは登録されています。ページをリロードすると入力したデータが再表示されるようになります。
“Submit”ボタンを押してもページのリフレッシュが行われないためデータの再取得が行われないことが原因です。登録が完了した後にページのリフレッシュが行えるようにuseRouterのrefreshメソッドを利用します。useRouterはnext/navigationからimportします。
useRouter.refreshを追加後に入力フォームから入力後に”submit”ボタンをクリックすると入力したデータが自動で反映されます。
App Routerの環境でのデータの登録方法を確認できました。
Optimizing
Metadata の設定
headタグに中に挿入するtitleやdescriptionなどのMetaタグを設定する方法にはStaticとDynamicな2つの設定方法が提供されています。設定はlayout.tsまたはpage.tsファイルで行います。
staticな設定方法はappディレクトリのlayout.tsxファイルにデフォルトから設定されています。
この設定が行われているため/usersにアクセスしてもブラウザのタブに設定したtitleが表示されています。

Static Metadataの設定
ユーザページに表示されるtitleを変更したい場合には下記のように設定を行うことができます。
設定した値がブラウザのタブに反映されます。descriptionについてはページのソースで確認できます。

Dynamic Metadataの設定
Dynamic Metadataを設定するためユーザ一覧に表示したユーザ名をクリックすると詳細ページに移動できるようにDynamic Routesを利用して設定を行います。リンクにはLinkコンポーネントを利用しています。
現在の設定ではユーザ名をクリックするとDynamic Routesの設定を行なっていないので404ページが表示されます。
Dynamic Routesを設定するためusersディレクトリに[id]ディレクトリを作成してその下にpage.tsxファイルを作成します。
設定後、ユーザ名をクリックするとユーザの詳細画面が表示されます。

titleにユーザ名を設定したい場合はページの内容が動的に変わるためStaticな方法でMetadataを設定することができません。そのためgenerateMetadata関数を利用してDynamicにMetadataを設定します。
titleを設定する際にもfetch関数を利用してデータを利用するためgetUser関数を作成しています。画面に表示されるユーザ情報とMetadataに利用するユーザ情報のため1つのPageコンポーネントの中で同じURLに対してfetchの処理を行っていますがAutomatic Request Dedupingにより同じURLへのリクエストは最適化されます。
ブラウザから確認するとDynamicにtitleが設定されていることがわかります。

全ページのタイトルにアプリケーションの名前等(XXXX | Next App)を設定したい場合があります。その場合はlayout.tsxファイルのmetadaの設定でtemplateを利用することができます。
ブラウザで確認するとページのタイトルの後に”| App”が追加されています。

faviconの設定
デフォルトからappディレクトリの下にfavicon.icoファイルが存在していますがlayout.tsxでもfavicon.icoの設定を行っていません。appディレクトリの下にfavicon.icoを配置するだけで自動でheadタグの中に追加されます。もしfavicon.icoの名前を変更したり削除するとタグは消えます。
opengraph-imageの設定
OGPの画像を設定したい場合はopengraph-imageというファイル名でサポートされている.jpg, .jpeg, .png, .gifの拡張子を持つファイルをappディレクトリに設定するだけで自動で設定を行ってくれます。ここではopengraph-image.pngファイルをappディレクトリに保存しました。下記のコードがheadタグに追加されます。
appディレクトリにopengraph-image.pngファイルを保存すると/usersに上記のタグがheadの追加されます。opengraph-image.pngファイルをappディレクトリからusersディレクトリに移動すると/(ルート)にアクセスしてもOGP画像のmetaタグは設定されませんが/usersではmetaタグが表示されます。
動的なOGP画像の作成
/users/1にアクセスした場合に動的にOGPの画像を作成することができます。/users/[id]ディレクトリにopengrap-image.tsファイルを作成した以下のコードを設定します。
ブラウザのデベロッパーツールの要素を利用してog:imageのmetaタグを見つけます。
このURLをブラウザのURLのバーに貼り付けて確認します。以下のようにOGP画像が動的に作成されることが確認できます。

Sitemap
サイトマップを設定したい場合にはappディレクトリの下にsitemap.xmlファイルを保存します。sitemap.xmlファイルの内容はNext.jsのドキュメントからコピーしています。
http://localhost:300/sitemap.xmlにアクセスするとappディレクトリに保存したsitemap.xmlファイルの内容が表示されます。

静的にsitemap.xmlを作成しましたが動的に作成することができます。appディレクトリにsitemap.tsファイルを作成して以下のコードを記述します。
ブラウザからhttp://localhost:3000/sitemap.xmlにアクセスするとnew Date()を設定しているのでアクセスした日が表示されます。
通常はデータベースまたはリモートサーバからページ情報を取得するためここではJSONPlaceHolderを利用して取得したデータを利用してsitemapを作成します。
ブラウザからsitemap.xmlにアクセスすると以下のように表示されます。

robots.txt
robots.txtはクロール可能なページを検索エンジンからのクローラーに対して伝えるファイルです。
appディレクトリにrobots.txtファイルを作成して保存します。/privateディレクトリに対してのみクロールを拒否する設定を行っています。User-Agentではすべてのクローラーに設定を伝えています。
ファイルをappディレクトリに保存後にhttp://localhost:3000/robots.txtにアクセスするとrobots.txtに記述した内容が表示されます。
robots.txtではなくrobots.tsファイルでも設定を行うことができます。
http://localhost:3000/robots.txtにアクセスするとrobots.tsで設定した内容が表示されます。
もしrobots.txt, robots.tsファイルがappディレクトリに存在する場合は”Duplicate page detected. app/robots.ts and app/robots.txt resolve to /robots.txt”のメッセージがnpm run devコマンドを実行しているターミナルに表示されます。
canonical
複数のページで同じコンテンツが表示される場合に検索エンジンにオリジナルコンテンツのURLを伝える必要があります。またhttp://www.localhost:3000、http://localhost:3000ののようにwwwあり、なしどちらもでもアクセス可能な場合に重複したコンテンツではないようにどちらかをcanonicalで設定します。
appディレクトリのpage.tsxファイルで設定を行います。metaBaseを設定することでcanonicalに”/”と設定しても自動でhttp://localhost:3000/となります。
ブラウザで確認すると以下のタグが設定されます。
metadataBaseを設定していない場合の動作確認を行います。
hrefの値が”/”となるためmetadataBaseを利用していない場合はcanonicalにフルパスのURLを設定する必要があります。
Fontの設定
next/fontを利用することでFontの最適化を行ってくれます。Google Fontを利用することができ、デフォルトでもGoogle FontのInterが設定されています。
設定はlayout.tsxファイルで行っています。
別のフォントに変更したい場合にも簡単に行うことできます。フォントが変更したことがすぐにわかるようにDancing Scriptに変更してみましょう。
ブラウザで確認するとフォントが変わっていることがわかります。

フォントはページのソースを見ると下記のlinkタグで設定が行われています。拡張子がwoff2のフォントファイルで_next/static/media/に保存されています。ローカルへフォントファイルがダウンロードされることもわかります。
ビルド
npm run buildコマンドを実行すると本番環境用のビルドを行うことができます。ビルドを行うことによってファイルサイズや作成されるファイルだけではなくApp Routerで設定した各ルーティングがランタイムにサーバサイドでレンダリングするのかStatic HTMLとしてビルド時に作成されるのか確認することができます。
ルーティングの左に表示されているλマークかoかによってServer Side RenderなのかStaticなのかがわかります。aboutやusersなどはStaticですが、Dynamic Routesを利用している/users/[id]はSever Side Renderとなっています。
ビルド後.next/server/appディレクトリを確認するとStaticと表示されたaboutやusersについてはabout.html、users.htmlとHTMLファイルの形として保存されています。
users.htmlファイルの中身を確認するとユーザ情報を含んだHTMLであることがわかります。fetch関数で取得したユーザ名なども確認することができます。

cacheオプションの設定
Staticとして静的ファイルを作成するかどうかはfetch関数のcacheオプションの値にも関係しておりusers/UserList.tsxファイルでcacheの値を’no-store’にしてビルドを実行してみます。
cacheの値を’no-store’にする前は/usersは○からλに変わっていることがわかります。
.next/server/appディレクトリを確認してもusers.htmlは作成されていません。
generateStaticParams
/user/[id]はビルドを作成するとλとなり、静的ファイルではなくリクエストが来た時にサーバサイドレンダリングによりページが作成されます。しかし、/user/[id]のidは動的に変わりますが同じidでアクセスがあれば同じ内容が表示されます。
generateStaticParams関数を利用することでDynamic Routesもビルド時に静的なファイルとして作成することができます。generateStaticParamsを利用してidの値をusers/[id]/page.tsxファイルでNext.jsに教えてあげる必要があります。
generateStaticParams関数を設定後、npm run buildコマンドでビルドを行います。ビルドのメッセージを見るとこれまでの○,λとは異なる●となっていることがわかります。
●はSSG(Sever Site Generator)の略で静的なファイルが作成されます。
.next/server/appのusersディレクトリを確認すると1.html, …., 10.htmlまでHTMLファイルが作成されていることがわかります。このようにgenerateStaticParamsを利用することでDyamic Routesから静的なファイルを作成することができます。
Dynamic Routesの一部のidだけ静的ファイルを作成したい場合にはgenerateMetadata関数で静的なファイルを作成するidのみ渡すことで実現することができます。
ビルドを行うと指定した数のみ静的ファイルが作成されていることがわかります。.next/server/appのusersディレクトリを確認すると1.html, 2.html, 3.htmlのファイルのみ作成されています。
関数を利用せず下記のように記述することもできます。指定した/users/1, /users/4, /users/8のみ静的ファイルが作成されます。
Revalidating Data
npm run buildコマンドで静的ファイルを作成することができましたが現在の設定ではビルドコマンド実行時に取得したデータが更新された場合再度ビルドを行うまでページに反映されません。
アプリケーション全体を再ビルドすることなく更新した内容を反映させるためにfetch関数のnext.revalidateを利用します。指定した時間が経過するとバックグランドでページの更新を行ってくれます。
設定方法はfetchingの章で確認済みですがuser/[id]/page.tsxファイルで以下の設定を行います。
ページの更新が本当に行われているかどうか確認するためにrandamメソッドを利用し乱数をブラウザ上に表示させこの値が更新されるかどうかチェックを行います。next.revalidateで設定した時間が経過して値が更新されればページの更新が行われていることがわかります。
設定後はnpm run buildコマンドを実行します。
5秒経過後にアクセスをするとその時のアクセスでは同じ内容が表示されます(バックグランドでデータの再取得が行われページが更新される)がその次のリクエストでは更新された乱数が表示されます。
.next/server/app/usersディレクトリに保存されているhtmlの内容を確認していると設定した時間を経過するとファイルが再作成されていることがわかります。
このように静的ファイルもfetch関数のnext.revalidateを利用することで更新を行うことができます。
現在の引き続き更新中です。