3
2

Delete article

Deleted articles cannot be recovered.

Draft of this article would be also deleted.

Are you sure you want to delete this article?

【Next.js】4種類の"Cache"を理解したい Part ④ Router Cache

Last updated at Posted at 2024-05-22

App Routerの登場で、より強調されたCachingについて理解を深めたいと思います。

ドキュメントに沿って、4回に分けて記事にしていきます。

  1. Request Memoization
  2. Data Cache
  3. Full Route Cache
  4. Router Cache

今回はRouter Cacheです。

V14までの仕様です。
V15からは、Data Cacheと[Router Cache]がデフォルトで無効になります。
詳細はこちら

概要

React Server Component(RSC) PayloadをRouteごとにClient Sideに保持することで、ページナビゲーションが高速になります。

In-Memory情報のため、ページリフレッシュ等でこのCacheはクリアされます。

How it works

image.png
出典 : How the Router Cache Works

  1. /aへの初回アクセス時は、Router Cacheは存在しないので、Server Sideから取得する
  2. '/'(Layout)/aの情報がRouter Cacheに保持される
  3. /bへの初回アクセス時は、'/'(Layout)はRouter Cacheから、その他はServer Sideから取得する
  4. /bの情報がRouter Cacheに追加で保持される
  5. /aへの2回目以降のアクセスでは、Router Cacheに保持された値を取得する

上図を見て分かるように、StaticDynamicによる違いはありません。

シンプルに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

Cache無効化

Router Cacheは無効化できません。

CacheしたくないRouteに関しては明示的に上記のInvalidation処理を行う必要があります。

実際に確認

RSCで構成されたページへの遷移において、2回目以降はRouter Cacheに保持されたRSC Payloadが使用され、新たなNetwork リクエストが飛んでいないことを確認します。

実装

Project
shell
npx create-next-app@latest --typescript --tailwind
Component
src/app/router-cache/next-link/page.tsx
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>
  );
}
src/app/router-cache/use-router/page.tsx
'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>
  );
}
src/app/router-cache/server-component-page/page.tsx
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
src/utils/app-fetch.ts
// 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サーバーを起動

shell
node external-server.js
shell
npm run dev

遷移とリクエスト

BrowserのDeveloper toolのNetworkタブを開いた状態で以下の手順を確認します。

2回目のServer Component Pageへの遷移でNetworkリクエストが飛んでいないことが分かりますね!

また、表示されるランダム値も当然前回と同じ値であることを確認できます。

  1. http://localhost:3000/router-cache/next-linkを開く
  2. To Server Component Pageを押下
  3. Back to Next Link Pageを押下
  4. 再度、To Server Component Pageを押下

次に、useRouter hookを使用しているページでも同様の手順を踏みます。

こちらも同様にNetworkリクエストに何も発生しないことが確認できますね!

  1. http://localhost:3000/router-cache/use-routerを開く
  2. To Server Component Pageを押下
  3. Back to Use Router Pageを押下
  4. 再度、To Server Component Pageを押下

Cache Invalidation

router.refresh()を使用して、Cacheをクリアしてみます。

以下のようにRefresh処理を追加してみましょう。

src/app/router-cache/use-router/page.tsx
'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>
  );
}

以下の手順を踏みます。

  1. http://localhost:3000/router-cache/use-routerを開く
  2. To Server Component Pageを押下
  3. Back to Use Router Pageを押下
  4. Refreshを押下
  5. 再度、To Server Component Pageを押下

RefreshによりCacheがクリアされ、Networkリクエストが発生していることが分かりますね!

最後に

Dynamic、Static問わずCacheされるので、「Dynamic Server Componentなのに内容が更新されない!」といった現象が発生しそうな感じがします。

Next.jsにおいてはCacheをまず気にしてみると良いですね!

3
2
0

Register as a new user and use Qiita more conveniently

  1. You get articles that match your needs
  2. You can efficiently read back useful information
  3. You can use dark theme
What you can do with signing up
3
2

Delete article

Deleted articles cannot be recovered.

Draft of this article would be also deleted.

Are you sure you want to delete this article?