App Routerの登場で、より強調されたCachingについて理解を深めたいと思います。
ドキュメントに沿って、4回に分けて記事にしていきます。
- Request Memoization
- Data Cache
- Full Route Cache
- Router Cache
今回はRouter Cacheです。
V14までの仕様です。
V15からは、Data Cacheと[Router Cache]がデフォルトで無効になります。
詳細はこちら
概要
React Server Component(RSC) PayloadをRouteごとにClient Sideに保持することで、ページナビゲーションが高速になります。
In-Memory情報のため、ページリフレッシュ等でこのCacheはクリアされます。
How it works
出典 : How the Router Cache Works
-
/a
への初回アクセス時は、Router Cacheは存在しないので、Server Sideから取得する -
'/'(Layout)
と/a
の情報がRouter Cacheに保持される -
/b
への初回アクセス時は、'/'(Layout)
はRouter Cacheから、その他はServer Sideから取得する -
/b
の情報がRouter Cacheに追加で保持される -
/a
への2回目以降のアクセスでは、Router Cacheに保持された値を取得する
上図を見て分かるように、Static
、Dynamic
による違いはありません。
シンプルにRouteごとにRSC PayloadのCacheがClient Sideに保持されるという認識を持つと良いです。
Cache生成
ページアクセスだけでなく、<Link>
ComponentによるPrefetch処理によってCacheが生成されます。
保持期間
-
Session
ページリフレッシュで全てのRoute Cacheはクリアされます。 -
自動無効化
<Link>
Component によるPrefetch処理で取得されたCacheは特定の時間が経過するとクリアされます。-
prefetch={null}
や特に指定をしなかった場合
30秒でクリアされます。 -
prefetch={true}
やrouter.prefetch
5分でクリアされます。
-
Invalidation
-
Server side
revalidatePath、revalidateTagが使用された場合や、Server Actionsにてcookies.setやcookies.deleteが使用されているComponentで無効化されます。 -
Client side
router.refreshにより、Router Cacheがクリアされ、現在のRouteに対して新しいページリクエストが飛びます。
Cache無効化
Router Cacheは無効化できません。
CacheしたくないRouteに関しては明示的に上記のInvalidation処理を行う必要があります。
実際に確認
RSCで構成されたページへの遷移において、2回目以降はRouter Cacheに保持されたRSC Payloadが使用され、新たなNetwork リクエストが飛んでいないことを確認します。
実装
Project
npx create-next-app@latest --typescript --tailwind
Component
import Link from 'next/link';
export default async function Page() {
return (
<div>
This page is a server component implementing Link component.
<br />
<Link className="border border-black bg-gray-300" href={'/router-cache/server-component-page'}>
To Server Component Page
</Link>
</div>
);
}
'use client';
import { useRouter } from 'next/navigation';
export default function Home() {
const router = useRouter();
return (
<div>
This page is a client component implementing useRouter hook.
<br />
<button
className="border border-black bg-gray-300"
onClick={() => router.push('/router-cache/server-component-page')}
>
To Server Component Page
</button>
</div>
);
}
import { getRandomNumberWithNoDataCache } from '@/utils/app-fetch';
import { cookies } from 'next/headers';
import Link from 'next/link';
export default async function Page() {
// Make this component dynamic
cookies();
const value = await getRandomNumberWithNoDataCache();
return (
<div>
<h2>{value}</h2>
<div>
<Link className="border border-black bg-gray-300" href={'/router-cache/use-router'}>
Back to Use Router Page
</Link>
</div>
<div>
<Link className="border border-black bg-gray-300" href={'/router-cache/next-link'}>
Back to Next Link Page
</Link>
</div>
</div>
);
}
Fetch Function
// Opt-out data cache
export async function getRandomNumberWithNoDataCache() {
const res = await fetch('http://localhost:3005', { cache: 'no-store' });
const text = await res.text();
console.log(text);
return text;
}
ダミー外部APIサーバー
こちらと同様にextenral-server.js
ファイルを作成してください。
外部API、Next.Jsサーバーを起動
node external-server.js
npm run dev
遷移とリクエスト
BrowserのDeveloper toolのNetworkタブを開いた状態で以下の手順を確認します。
2回目のServer Component Page
への遷移でNetworkリクエストが飛んでいないことが分かりますね!
また、表示されるランダム値も当然前回と同じ値であることを確認できます。
-
http://localhost:3000/router-cache/next-link
を開く -
To Server Component Page
を押下 -
Back to Next Link Page
を押下 - 再度、
To Server Component Page
を押下
次に、useRouter
hookを使用しているページでも同様の手順を踏みます。
こちらも同様にNetworkリクエストに何も発生しないことが確認できますね!
-
http://localhost:3000/router-cache/use-router
を開く -
To Server Component Page
を押下 -
Back to Use Router Page
を押下 - 再度、
To Server Component Page
を押下
Cache Invalidation
router.refresh()
を使用して、Cacheをクリアしてみます。
以下のようにRefresh処理を追加してみましょう。
'use client';
import { useRouter } from 'next/navigation';
export default function Home() {
const router = useRouter();
return (
<div>
This page is a client component implementing useRouter hook.
<br />
<button
className="border border-black bg-gray-300"
onClick={() => router.push('/router-cache/server-component-page')}
>
To Server Component Page
</button>
+ <br />
+ <button className="border border-black bg-green-300" onClick={() => router.refresh()}>
+ Refresh
+ </button>
</div>
);
}
以下の手順を踏みます。
-
http://localhost:3000/router-cache/use-router
を開く -
To Server Component Page
を押下 -
Back to Use Router Page
を押下 -
Refresh
を押下 - 再度、
To Server Component Page
を押下
RefreshによりCacheがクリアされ、Networkリクエストが発生していることが分かりますね!
最後に
Dynamic、Static問わずCacheされるので、「Dynamic Server Componentなのに内容が更新されない!」といった現象が発生しそうな感じがします。
Next.jsにおいてはCacheをまず気にしてみると良いですね!