💻Next.js App Router

チャンク分け
- Route Segument
- Suspense
サーバーモジュールグラフ(サーバー側のみ使用するモジュール)とクライアントモジュールグラフに分ける。
使い分ける(サーバーモジュールグラフを多くすること)とどうなるか?
→JSのバンドルサイズが小さくなる
どうするか?
→"use client"をうまく使うこと
- Shared Components
- Slot
Route Segment
- layout.js
- template.js
- error.js
- loading.js
- not-found.js
- page.js
<Layout>
<Template>
<ErrorBoundary fallback={<Error />}>
<Suspense fallback={<Loading />}>
<ErrorBoundary fallback={<NotFound />}>
<Page />
</ErrorBoundary>
</Suspense>
</ErrorBoundary>
</Template>
</Layout>
use client
「use clientが付いたコンポーネントがクライアントコンポーネント」ではなく、それ以降のコンポーネントがクライアントコンポーネントである。
import文も見て量が多いと、クライアントモジュールグラフが大きくなる可能性がある。
大きくなるとサーバーからブラウザに送るJSが大きなってしまう。
Shared Components
slot

ナビゲーション
Webアプリケーションにおけるナビゲーションには2種類ある。
- ハードナビゲーション:ブラウザが画面を再読み込みする画面遷移
- ソフトナビゲーション:ブラウザが画面を再読み込みしない画面遷移
App Routerでは、変更されたRoute Segmentのみが再レンダリングされる。
ソフトナビゲーションの優性性は再レンダリングのほかに、一度ブラウザで確保した値を、画面をリロードするまで保持できる(ブラウザ上のキャッシュ)。

Client Componentからimportされるコンポーネントや関連ファイルもブラウザ向けにバンドルされる。

動的データ取得と静的データ取得
APIから取得するデータは、更新頻度によって2種類に分類できる。
静的データは「更新頻度が低く、誰もが共有できるデータ」と言い換えることができる。
Next.jsがfetch関数に施している拡張は「何も指定しなければ、静的データ取得と扱われて結果はキャッシュされる」
一方、頻繁に更新されるデータなどの動的データは、常に最新のものでなければ困るので、{cashe: "no-store"}
を指定する。これを指定するとデフォルト設定は解除され、リクエストのたびにデータ取得が発生する。
fetch("https://...", { cashe: "no-store" });

Next.jsはビルド時に、すべてのRouteにおいてレンダリングを施行します。「このRouteには動的データ取得が含まれている」という情報がビルド時に収集されるため、動的レンダリングRouteとしてマークできる。
⚪︎:静的Route
λ:動的Route

動的関数
「動的関数」とはHTTPリクエストの内容を参照する関数のこと。
- Cookieの参照:Server Component で cookies()を使用する
- リクエストヘッダーの参照:Server Component で headers()を使用する
- URL検索パラメーターの参照1:Server Component で PropsのsearchParamsを参照する
- URL検索パラメーターの参照2:Client Component で PropsのuseSearchParamsを参照する
ビルド時にこれらの動的関数が使用されているかをすべてのRouteで検証し、使用が確認されるとRouteは動的レンダリングへと切り替わる。
静的レンダリングRoute
動的レンダリング要因が含まれない場合、Next.jsは「静的」レンダリング結果をキャッシュファイルに出力し、Routeのレスポンスとして使用する。キャッシュファイルへの出力は以下のタイミングである。
- ビルド時
- リクエスト時
- Revalidate時

App Routerの規約
Segment構成ファイルはReact Suspenseを前提としたコンポーネントツリーを構築する。
SuspenseとError Boundaryの概要
React Suspenseは、子が読み込みを完了するまでフォールバックUIを表示する機能。
Error Boundaryは、子コンポーネントでErrorがスローされた場合にフォールバックUIを表示する実装。
route
Next.jsは画面だけでなくWeb APIも提供できる。route.tsはWeb APIを提供するためのファイルである。
App Routerで実装するこのWeb APIを「Route Handler」と呼ぶ。
Parallel Routes と Intercepting Routes
Parallel Routesの概要
Parallel Routesは「Slot」と呼ばれるものを使用して作成します。
Slotは@folderというフォルダ名規約を使用する。
Slotは、Layoutにおいて、childrenと同じようにPropsとして渡されるので、子ノードとしてレンダリングする。
type Props = {
children: React.ReactNode;
users: React.ReactNode;
};
export default function Layout({ children, users }: Props) {
return <div className={styles.container}>{children}</div>;
}
Intercepting Routesの概要
名前の通りRouteを横取りする(インターセプトする)ためのRoute定義。
Routeのメタデータ
動的メタデータの基本
動的メタデータを定義するにはPageファイルやLayoutファイルから非同期関数であるgenerateMetadata関数をexportする。
export async function generateMetadata({ params }: Props): Promise<Metadata> {
const photo = await getPhoto(params.photoId);
return {
title: photo.title,
description: photo.description,
};
generateMetadataにおけるRequestのメモ化
generateMetadata関数を使用すると、API実行が2回呼ばれることになると思うが、
Next.jsでは、この課題に対して「Requestのメモ化」という最適化を内部的に行なっている。
この「Requestのメモ化」とは、同じとみなせるデータ取得は一つにまとめるというもの。
継承されたメタデータの参照
export async function generateMetadata(
{ params }: Props,
parent: ResolvingMetadata
): Promise<Metadata> {
const photo = await getPhoto(params.photoId);
const title = await parent;
return {
title: `${photo.title} ${title}`,
description: photo.description,
};
}

Route Handlerの定義
定義するにはSegment構成フォルダにroute.tsファイルを配置する。
例えばapp/api/hello/route.tsという定義ファイルは/api/helloのリクエストを処理する。
このファイルからexportする関数は、HTTPリクエストメソッドにそれぞれ対応する。(GET, POST, PUT, DELETE, HEAD, OPTIONS)
もし、HTTPリクエストのメソッドに対応する関数がexportされていない場合、Next.jsは405Method Not Allowedレスポンスを返す。
Web標準のFetch APIに基づくNextRequest/NextResponse
import { NextResponse } from "next/server";
import type { NextRequest } from "next/server";
export async function POST(request: NextRequest) {
return NextResponse.json({ liked: true });
}
Statusコード指定の場合
if (!session) {
return new NextResponse.json({ message: "Unauthorized" }, { status: 401 });
}
動的Route Handler
画面のRouteと同様に、ビルド時にRouteのレンダンリングを試行します。
以下の要因が検出されると、「動的Route Handler」とみなす。
- Dynamic Segment値の参照
- Requestオブジェクトの参照
- 動的関数の使用
- GETとHEAD以外のHTTP関数のexport
- Segment Config Optionsの指定

Time-based Revalidation
Next.jsには「Incremental Cache」という組み込みのキャッシュシステムが存在する。
Incremental Cacheはデータ取得結果をキャッシュし、必要に応じて更新するメカニズムを持つ。(Next.jsではこのキャッシュ機構はデフォルトで有効になっている。)
fetch関数を使用したキャッシュは更新されない限り、キャッシュされた内容を返し続ける(有効期間はデフォルトで1年間)。
fetch("https://..."); // 1年間のキャッシュ
任意の有効期間にしたい場合。
fetch("https://...", { next: { revalidate: 60 * 60 } }); // 1時間のキャッシュ
next.revalidateオプションは、キャッシュの有効期間を秒数で指定するオプション。
有効期間が過ぎるとキャッシュを破棄し、新たにデータを取得する。
また、キャッシュを更新するプロセスを「Revalidate(再検証)」と呼ぶ。
next.revalidateオプションのように有効期間で指定するRevalidateを「Time-based Revalidation」と呼ぶ。

.envファイルについて
Node.jsで読み込むためには別途dotenvなどのライブラリが必要になるが、Next.jsでは読み込みを標準でサポートしているため、特別なセットアップは不要である。
Next.jsでは、.envファイルを含め標準で以下のファイルを読み込む。
- .env
- 常時読み込まれる
- .env.$(NODE_ENV)
- 該当のNODE_ENVが有効な時に読み込まれる
- .env.local
- ローカル開発時で常時読み込まれる
- .env.$(NODE_ENV).local
- ローカル開発で、かつ該当のNODE_ENVが有効な時に読み込まれる
$(NODE_ENV)にはdevelopment, production, testのいずれかの文字列が入る
- development:Next.jsを開発モードで起動している時
- production:Next.jsをプロダクションモードで起動している時、またはビルド時
- test:自動テスト実行時
.envファイルのロード順
ローカルの開発モードで起動しているNext.jsでprocess.env.〇〇がどのように設定されるか。
- 環境変数がprocess.envに設定される
- .env.development.localファイルが読み込まれてprocess.envに設定される
- .env.localファイルが読み込まれてprocess.envに設定される
- .env.developmentファイルが読み込まれてprocess.envに設定される
- .envファイルが読み込まれてprocess.envに設定される
1から順番にprocess.envのプロパティが設定されていくが、すでにプロパティが存在する場合は上書きされない。つまり、上から順番に優先順位が高いということ。

環境変数の公開範囲
Publicな環境変数
「NEXT_PUBLIC_」という接頭辞がついた環境変数をNext.jsは、ブラウザ向けにインライン化しても問題ない「Public」な環境変数とみなす。
注意点は、この環境変数は「ビルド時の値に凍結される」ということ。
HTML/JSにインライン化するため、一度出力された静的ファイル内の環境変数を変えるには、再ビルドする必要がある。
Privateな環境変数
「NEXT_PUBLIC_」接頭辞のない環境変数は「Private」なものとして扱われる。
Next.jsはブラウザ向けのコード(クライアントコンポーネント)とサーバーサイドのコード(サーバーコンポーネント)の垣根が低く、間違えてPrivateな環境変数がブラウザ向けバンドルに含まれることがある。
もし、Privateな環境変数がブラウザ向けバンドルに含まれていることを見つけると、Next.jsは空文字列に置き換えるため、心配はない(ただし、その場合は漏洩リスクのあるコードが含まれていることになるため、サーバーでのみ実行されるコードになるように見直しする)。

NextAuth.jsをNext.jsへ導入する手順
- 環境変数NEXTAUTH_SECRETの設定
- Middlewareの設置
- NextAuthOptionsの設定
- Route Handlerの設置
- getServerSessionの利用

Server Action
概要
Server Action をひとことで言うと「Formからサーバーの非同期関数を直接呼び出せる機能」。
- 中間コード(API Client)がなくなる
- Browser向けにバンドルされていたAPI Clientが少なくなる
- ハイドレーションが完了する前に実行できる
"use server"ディレクティブの宣言
Server Actionを使用するためには"use server"ディレクティブを宣言する。
宣言する場所は
- 非同期関数スコープの先頭
- 「actions.ts」といったServer Action専用ファイルを作成し(ファイル名は自由)、ファイル先頭
Formのaction属性に渡されたServer Actionは、第一引数にWeb標準のFormDataオブジェクトを取る。
引数のバインド(Binding Arguments)
関数オブジェクトのbindメソッドを使用して Server Action に引数をバインドできる。
いくつかの引数がすでにバインドされた新しいServer Actionが作成できる。
Progressive Enhancement
今までのForm実装では、Formイベントを処理するために onSubmit イベントハンドラーを使用していた。この方式の場合、JavaScriptがロードされてハイドレーションが完了するまでの間、Formイベントを送信できなかった。
action属性に非同期関数を渡す方式は、「Progressive Enhancement」を有効にする。
「Progressive Enhancement」とは、JavaScriptを使用しない(または無効)状態でも、formの機能を損なわないようにする実装方針のこと。
action属性にServer Actionを渡すことで、ユーザーはハイドレーション前にFormを送信できる。ハイドレーションが完了する前にFormが利用できるため、ユーザーを待たせなくて良い。
ハイドレーション:表示されたHTMLをインタラクティブにする処理ことで、HTMLに対し、JavaScriptのイベントハンドラーをアタッチする。
action属性とonSubmit属性
onSubmitではClientのバリデーションチェックをすることが可能。
onSubmit内でバリデーションに引っ掛かったらevent.preventDefault();を実行することによってaction実行を中止できる。

Revalidateの設計
- rebalidatePath:特定のRouteのパスでキャッシュを無効化する
- revalidateTag:特定のタグ文字列でキャッシュを無効化する
キャッシュタグの設計
revalidateTagは、データ取得にあたえた「任意のタグ文字列」を頼りに、タグ付けされたキャッシュを無効化する。なので、どのような文字列にするかの考慮は以下。
- タグが抽象的であるほど、無効化は楽になる
- タグが具体的であるほど、データソースアクセスが効率が良好になる