Next.js 13(App Router)をキャッチアップする
背景
ドキュメントを読む
従来のPage RouterではなくApp Routerベースに変わったことでドキュメントが結構新しく書き直されているようなので最初から読み直す。
Setup
とりあえず最新のNext.jsをインストールする。Node.js 16.8以降が必須らしい。まずはnpx create-next-app@latestを立ていてプロジェクトを作る。TypeScript/ESLint/TailwindCSS/App Routerをデフォルトとして推奨してるらしい。
code:sh
% npx create-next-app@latest
Need to install the following packages:
create-next-app@latest
Ok to proceed? (y) y
✔ What is your project named? … app-router-sample
✔ Would you like to use TypeScript with this project? … No / Yes
✔ Would you like to use ESLint with this project? … No / Yes
✔ Would you like to use Tailwind CSS with this project? … No / Yes
✔ Would you like to use src/ directory with this project? … No / Yes
✔ Use App Router (recommended)? … No / Yes
✔ Would you like to customize the default import alias? … No / Yes
VSCodeの設定はどうするか?とりあえず適当にprettierだけ入れてフォーマットされるようにしとく。.prettier.jsonはnext.jsのリポジトリから拝借。 code:sh
% npm i -D prettier
code:json
{
"singleQuote": true,
"semi": false
}
code:vscode/settings.json
{
"editor.defaultFormatter": "esbenp.prettier-vscode",
"editor.defaultFormatter": "esbenp.prettier-vscode"
},
"eslint.validate": [
"javascript",
"javascriptreact",
"typescript",
"typescriptreact"
],
"files.exclude": {
"**/node_modules": false,
"node_modules": true,
"*!test**/node_modules": true }
}
デフォルトで作成されたプロジェクトの中身。
code:sh
% tree . -a -L 2 -I node_modules
.
├── .eslintrc.json
├── .gitignore
├── .prettierignore
├── .prettierrc.json
├── .vscode
│ └── settings.json
├── README.md
├── app
│ ├── favicon.ico
│ ├── globals.css
│ ├── layout.tsx
│ └── page.tsx
├── next-env.d.ts
├── next.config.js
├── package-lock.json
├── package.json
├── postcss.config.js
├── public
│ ├── next.svg
│ └── vercel.svg
├── tailwind.config.js
└── tsconfig.json
いきなりpagesではなくapp/ディレクトリというのがあり謎。まぁおそらく既存のPage Routerとの互換性を考えて別ディレクトリを切っているのだろう...。
Server Components
インタラクティブなコンポーネントはクライアント側で、そうでないものはサーバー側でレンダリングするぜ〜というやつ
初回表示が速くなる、クライアント側のjsのbundleサイズが減らせる、などが利点
App RouterではデフォルトでServer Componentを使っているのでシームレスにServer Componentによる恩恵を受けられる
Server Componentで出来ること(Client Componentで出来ないこと)
Fetch Data / バックエンドのリソースへのアクセス / access token とかのセンシティブデータの利用 / 巨大な依存関係をクライアントから逃す(bundle size削減)
Client Componentで出来ること(=> Server Componentで出来ないこと)
onClick/onChangeとかのeventリスナー / useState/useEffectとかのStateとライフサイクル系のフック / ブラウザスペシフィックなAPI
Client Componentが必要になるまでは基本的にServer Componentを使っとけばええ
client-only,server-onlyと書いたmoduleが意図せずclient/serverにimportされると警告が出るように出来るの便利
既存のnpm packageでuseStateとか使ってるやつは著者がuse clientをつけたバージョンを公開してないと、Server Component内で使えなくなる。package側の対応がない場合はuse client付きでwrapしたComponentを用意して対応するしかない。だるそう...
Server ComponentにおけるData fetchingの仕組みがよくわからん
Contextはどうなるか?
Client Component間では引き続き使える
Server Component内ではContextは使えない
じゃあどうやってServer ComponentとClient Componentの間でデータを共有すればいいのか?
module scopedなシングルトンのグローバル変数を使い回すようなやり方で共有させる。riverpodみたいな感じ。
複数のコンポーネントからの同一パスへのfetchリクエスト(GET)はキャッシュされるので重複リクエストはされない(useSWRっぽい)
App Router
Next.js 13で導入されたRSC(React Server Component)で構築されたRouting機構
app/とpages/は共存可能。同名のURL Pathになるときはビルドエラーになる。
App Routerでは
フォルダ = Route(URL Path)を定義する
フォルダ内のファイル = そのフォルダ(Route)で表示されるUI
ファイルはpage.js/route.js/layout.js/template.js/loading.js/error.js/not-found.jsのような役割ごとに必要に応じて定義する
加えてNav.jsとかNav.test.jsみたいな感じで同階層にコンポーネントファイルなどを配置できる
色々なRouting
デフォルトの挙動
Link Componentを使ったページ遷移ではRailsやLaravelのようなMPAと違い、ページが都度リロードされるわけではなくURL Pathと変更のあった部分だけ更新される(Hotwireとかもこんな感じじゃなかったっけ?)。 要は変化のない部分に関してはそのままに更新のあった部分だけfetch&再レンダリングし直す。ページ遷移ごとに全ページ再レンダリングを走らせなくてよくなって効率良くレンダリングできて良いね〜という話。
複数のページを同じlayoutに同時にレンダリングする
@analyticsや@teamみたいな@付きでディレクトリを作成する
ユースケース
SNSなどでフィードページとアナリティクスページを同じページに表示するようなやつ(Twitterの広告管理画面みたいな)
ログインしてるかどうかで表示するページを切り替える
AuthModalとか
うまいこと説明できないけど例えばTwitterで画像をクリックするとモーダルが立ち上がるが、そのモーダルをシェアできるようにURLを与えて、再度そのURLに直アクセスしたらモーダル無しの画像詳細ページを表示する、みたいなことをうまくやれるようにするやつ
実際に実装して試さないとピンとこなそう...
その他にもRailsのscopeとかmoduleとかあの辺のグルーピングRouteみたいな細かいRouting制御みたいなことができるようになってるっぽい
Rendering
Static Rendering
従来のPages RouterにおけるSSGやISRのこと
デフォルトの挙動
Static Data Fetching
デフォルトではfetchのリクエストがキャッシュされる
optionでrevalidate: 10やno-cacheなどを渡すと再レンダリングするタイミングを調整できたりする
Dynamic Rendering
従来のPages RouterにおけるSSRのこと
cookieやheaderやsearch paramsをServer Component内で扱っている場合はDynamic Data Fetchになる(リクエストがキャッシュされない)のでDynamic Renderingを使う
Data Fetching
基本的にはSWRが良い感じにやってくれてたようなことをやってくれるっぽい
Next.jsにおけるfetchはリクエストの重複排除/キャッシュ/revalidateを可能にするために拡張されている
出来るだけServer Componentでfetchを使うことが推奨されてる。理由としては通常のMPA的なサーバーでの利点を得られるから。Client ComponentからはSWRとかを使うのが推奨されているっぽい。将来的にはuseHookというのも導入されるかも。 Layoutレベルでfetch管理(キャッシュとか)がされてるから親Layoutに子Layoutのデータを渡したりできない
重複排除(複数のコンポーネントから同一パスへのリクエストがあった時に1回で済むようにする仕組み)
キャッシュのライフタイム
clientでは全てのページがリロードされるまでのセッションで保持される
serverではLayoutから各種Component等の全てのレンダリングプロセスが完了するまで保持される
POSTには適用されない
キャッシュ
Next.jsのキャッシュはHTTP CacheなのでCDNを通してグローバルに分散される。Server Componentでのfetchもこのキャッシュが有りや無しやを確認してリクエストの制御を行う。
Streaming
SSRだと全ページがサーバーで完成してからクライアントに表示されるのでファーストビューが表示されるまで時間がかかる。Streamingを使うとページの各要素を部分的にクライアントに表示できるので、例えば静的なComponent部分はすぐクライアントに表示され、生成に時間のかかるComponent部分はLoadingを表示しておいて処理が完了したら表示する、みたいなことができるようになるのでユーザー体験が上がる。<Suspense>というやつがその時間のかかる動的なComponentの部分のLoading/Completedな表示を制御するのに使われる。
Server Actions
まだアルファ版の機能
<form>のactionやボタンのsubmitで発生したイベントからサーバーサイドで実行される関数(=Server Action function)を呼び出せる
サーバーサイドでServer Actionで実行するようにして、クライアントサイドのJSを減らす目論み。
next.config.jsにでexperimental: { serverActions: true },を設定する必要あり
Client Componentだと実行できないがClient Componentにimportして利用することは可能
実際にコードをいくつか書く
TBD
リソース
まだApp Routerで書き直されてないようだ
サンプルアプリ。リポジトリを読むとApp Routerベースのコードが理解できる。