Next.jsのAPP Routerメモ。Pages Routerとの変更点など。

APP Router導入ガイド
既存のNext.jsアプリにAPP Routerを導入するためのガイドを見ると、大まかな違いがわかるので、まずここから確認すると良さそう。

Linkコンポーネント
Link
コンポーネントを使おうと思ったらなんかエラー出たんだけど、エラー内容みたら<a>
を使わなくて良いになってた。12で試験的に導入されてたのね。13で正式導入なのか。
APP Router関係ないかもしれないけど、とりあえずめもめも。

next/headとMetadata
next/head
がいなくなっていた。
直でtitleとかを書くならimport { Metadata } from 'next'
で、動的ならgenerateMetadata
というasync functionを使うらしい。
next-seoとかプラグイン使ってる場合はどうなるのか気になるところ。

サーバーコンポーネントとクライアントコンポーネント
とりあえずAPP Routerを使うとコンポーネントはデフォルトでReact Server Componentsになるらしい。
サーバーコンポーネントではできない機能を持つクライアントコンポーネントを作る場合は、自分でクライアントコンポーネントだよと記述する必要がある。
どういう時にどちらを使えば良いのか
- サーバーコンポーネントを使う時
- データの取得(fetch関数とか)
- バックエンドに直接アクセスするとき
- 機密情報(APIキーとか)をサーバーに保存するとき
- サーバーへの依存度を高く保つ(英訳が難しいけど、できるだけサーバーで処理したい場合ってことだと思う)
- クライアントサイドのJavaScriptを減らす
- クライアントコンポーネントを使う時
- イベントリスナー
- useStateとかのフック
- ブラウザでだけ使えるAPI
- useStateとかを使うカスタムフック
- Reactのクラスコンポーネント
ブラウザでユーザーが何かしたら反応するものや、ブラウザからでないとわからないものに関係するものは、クライアントコンポーネントになる、と思えば良さそう。
参考

データ取得
データ取得は基本的に自分でasync function
を作って、ローカルファイルを開いたり、fetch
を使って外部APIを叩いたりする。
で、何もしなければ「Static Site Generation(getStaticProps)」になるが、キャッシュのオプション設定で { cache: 'no-store' }
とすると「Server-side Rendering (getServerSideProps)」になる。
Pages RouterのgetStaticProps
とかgetServerSideProps
は、ページとして表示されるファイル内(最上位のindex.tsxとか)でしか使えず、propsをコンポーネント間でバケツリレーする必要があったが、App routerではコンポーネント単位でデータを取得することができる。で、さらに自動で重複が避けられるようになっている、らしい。
以下のキャッシュに仕組みが書いてある。
キャッシュの設定と再検証(Revalidation)の設定で、どうやってキャッシュするか、キャッシュの再検証をどうするかを決めることで、以前のISRとかを実現できるようになっている。
// SSR
// キャッシュはされずにリクエストごとにデータ取得
const res = await fetch(`https://...`, { cache: 'no-store' })
// SSG
// ビルドするときにキャッシュされてそのまま
const res = await fetch(`https://...`)
// ISR
// ビルド時キャッシュ、revalidate秒後に最初のアクセスがあると、データ取得
// revalidate秒後の2回目のアクセスから新しいキャッシュのデータを表示
const res = fetch('https://...', { next: { revalidate: 3600 } })
//On-Demand Revalidation
// ビルド時キャッシュ、指定した条件を満たしたアクセスがあるとキャッシュをパージ
const res = await fetch('https://...', { next: { tags: ['collection'] } })
fetchのオプションは以下。

動的パス
Pages RouterのgetStaticPaths
は、APP RouterではgenerateStaticParams
になった。
ぱっと見、基本的な使い方は変わってなさそうだけど、細部は変わっているように見える。
pgae.tsxだけでなく、layout.tsxでも使えるとか。
大きく違うのはgetStaticPaths
の内部で使っていたfallback
か。
dynamicParams
というRoute Segment オプションで設定するらしい。false
だとgenerateStaticParams
で定義したurl以外は404をかえす。
export const dynamicParams = true // true | false,

Tailwind CSSの設定追加
tailwind.config.js
に設定を追加する必要がある。
module.exports = {
content: [
'./app/**/*.{js,ts,jsx,tsx,mdx}', // <-- Add this line
'./pages/**/*.{js,ts,jsx,tsx,mdx}',
'./components/**/*.{js,ts,jsx,tsx,mdx}',
],
}

ルーティングフック
useRouter
が結構変わっている。
import { useRouter } from 'next/navigation'
next/navigation
から呼ぶようになった。前はnext/router
だった。
以前のpathname
に似た機能は独立したフックのusePathname()
になっていたり。query
はuseSearchParams()
になっていたり。

SSGで外部APIにfetchしてて、revalidateしてるというのが前提。その、revalidate後の外部API接続時にフェッチエラーになったら、サイトの表示はどうなるのか、という疑問。
予想としては表示が変わらずそのまま、なんだけど、本当にそうなるのか試してみたところ、そうだったというだけのメモ。
ただ、キャッシュがどうなっているのかとか良くわからない。
フェッチでrevalidateにしている場合、フェッチデータに最初にアクセスすると200、次にアクセスするとステータスコードが304になって、キャッシュにつながる。
で、revalidate時間が過ぎると、裏側でフェッチしてキャッシュのつながり先を更新してくれるということだと思うのだけど、そこでフェッチエラーが出た場合はキャッシュは据え置きっぽい。
挙動を見る限り、304でキャッシュにつながって、サイトでの表示はされるので、多分そういうことっぽい。
以下の画像だと、キャッシュのデータに「stale」とあって、古いということを判定しているだけで消えてないっぽい。
気になったのがオンデマンドほう。
「purge」になってるので、キャッシュ消してるってことよね。
オンデマンドのリクエストがあると、「purge」して、次にアクセスしてもキャッシュは「miss」になって、フェッチ先にデータを取りに行くのがオンデマンド。
手もとで試してないから以下は想像だけど。
この時のフェッチでエラーになったらerror.jsが起動する、で良いのかな。多分そうなりそうな気がするけど、今度また試してみよう。

Next.jsのLink
コンポーネントを使うとプリフェッチされることで、ページ遷移するときに読み込みが早くなって快適というのが、いいことの一つだけれど。
リンク先のページでフェッチを使ってると、プリフェッチが働いてフェッチも動いてるのよね、多分。chromeで見てたら、アクセスしたページでは使わないfetchのアクセスで304がいっぱい出てたし、多分そう。
SSGでrevalidateを使っている時に、全ページに、Linkコンポーネントでリンクを貼っていれば(例えばナビに全部リンクはっとくとか)、再検証設定時間後に、どこか1ページにアクセスがあれば全てのfethcの再検証が動作するわけで、それはそれで便利だなと思った。アクセスが少ないサイトでも、cron jobでどこかのページに一回アクセスしとけばよいわけで。
問題としては、SSRとかの時は困りそう。プリフェッチで毎回APIを叩かれるわけで、アクセス数が多いサイトの場合はすごいことになりそう。
個人で作る分には全然気にならないけど、大規模サイトだとキャッシュ戦略って大変そうだなと思ったり。SSGとSSRが複雑に絡み合うサイトとかになるのかな。管理大変そう。
SSRとSSGのrevalidateを併用すると。fetchがどんな感じになるのか今度試してみよう。
App Routerでは複数のfetchを勝手に裏でまとめてくれて上手いこと処理してくれるとなっているけど、いろんな場所のいろんな階層のコンポーネントに違う設定でfetchが使われていたらどうなるのか。
そういう複雑なことにならないように設計するのが一番いいんだろうなとは思うけど。