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

App Routerの登場で、より強調された[**Caching**](https://nextjs.org/docs/app/building-your-application/caching)について理解を深めたいと思います。

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

1. [Request Memoization]
1. [Data Cache]
1. [Full Route Cache]
1. **Router Cache**

今回は[**Router Cache**](https://nextjs.org/docs/app/building-your-application/caching#router-cache)です。

:::note warn
V14までの仕様です。
V15からは、[Data Cache]と[Router Cache]がデフォルトで無効になります。
詳細は[こちら](https://qiita.com/Leech/items/c93ee489e12252908837)
:::

## 概要

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

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

### How it works

![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/3468974/9efb93ad-f16a-7f50-de2a-2bfdb05b96e0.png)
出典 : [How the Router Cache Works]

1. `/a`への初回アクセス時は、Router Cacheは存在しないので、Server Sideから取得する
1. `'/'(Layout)`と`/a`の情報がRouter Cacheに保持される
1. `/b`への初回アクセス時は、`'/'(Layout)`はRouter Cacheから、その他はServer Sideから取得する
1. `/b`の情報がRouter Cacheに追加で保持される
1. `/a`への２回目以降のアクセスでは、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](https://nextjs.org/docs/app/api-reference/functions/revalidatePath)、[revalidateTag](https://nextjs.org/docs/app/api-reference/functions/revalidateTag)が使用された場合や、Server Actionsにて[cookies.setやcookies.delete](https://nextjs.org/docs/app/api-reference/functions/cookies#cookiessetname-value-options)が使用されているComponentで無効化されます。

- Client side
[router.refresh](https://nextjs.org/docs/app/api-reference/functions/use-router)により、Router Cacheがクリアされ、現在のRouteに対して新しいページリクエストが飛びます。

### Cache無効化

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

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

## 実際に確認

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

### 実装

##### Project

```bash:shell
npx create-next-app@latest --typescript --tailwind
```

##### Component

```tsx: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>
  );
}
```

```tsx: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>
  );
}
```

```tsx: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

```typescript: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サーバー

[こちら](https://qiita.com/Leech/items/b118ae11d348ca37c271#%E3%83%80%E3%83%9F%E3%83%BC%E5%A4%96%E9%83%A8api%E3%82%B5%E3%83%BC%E3%83%90%E3%83%BC)と同様に`extenral-server.js`ファイルを作成してください。


### 外部API、Next.Jsサーバーを起動

```bash:shell
node external-server.js
```

```bash:shell
npm run dev
```

#### 遷移とリクエスト

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

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

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

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

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

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

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



#### Cache Invalidation

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

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


```diff_tsx: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`を開く
1. `To Server Component Page`を押下
1. `Back to Use Router Page`を押下
1. `Refresh`を押下
1. 再度、`To Server Component Page`を押下

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


## 最後に

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

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

[Request Memoization]: https://qiita.com/Leech/items/b118ae11d348ca37c271
[Data Cache]: https://qiita.com/Leech/items/667d5cca203254dabfd5
[Full Route Cache]: https://qiita.com/Leech/items/405b0641d9d30f55a51e

[How the Full Route Cache Works]: https://nextjs.org/docs/app/building-your-application/caching#2-nextjs-caching-on-the-server-full-route-cache

[How the Router Cache Works]: https://nextjs.org/docs/app/building-your-application/caching#router-cache