【Next.js】４種類の"Cache"を理解したい Part ③ Full Route 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]

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

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

## 概要

Next.jsは、**ビルド/Revalidation時に、静的レンダリングするRouteに対して、**自動でReact Server Component(RSC) PayloadとHTMLをRenderingし、Cacheしてくれます。

このプロセスはいわゆる静的サイト生成や静的レンダリングを指します。

これにより、Client Sideからのリクエスト時にRenderingを行うのではなく、**事前に生成されたRSC Payload、HTMLを利用する**ことで、素早いページロードを実現します。

### Server Side Rendering プロセス

以下のプロセスの成果物である`RSC Payload`と`HTML`がFull Route Cacheに入ってきます。

1. ReactがServer Componentを**RSC Payload**フォーマットに変換
1. Next.jsがRSC PayloadとClient ComponentのJavaScriptを用いて、**HTML**を生成

### How it works

![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/3468974/0e30dea4-01b7-8e82-9468-2a79f6299be0.png)
出典 : [How the Full Route Cache Works]

ビルド/Revalidation時にRSC PayloadとHTMLをRendaringし、Cacheしています。

※`MISS`が入っていますが、Cacheが生成されるビルド/Revalidationのタイミングでは常に`MISS`になると理解しています。Full Route Cacheに`HIT`するのは、Client Sideからページリクエストが来たタイミングとなるはずです。


### Client Side Rendering プロセス

Full Route Cacheは、Client Sideからのリクエストで使用されます。

以下のプロセスにより、完全なページとして動作します。

1. HTMLが初期表示データとして使用される
※この時点では、あくまでUIプレビューとして表示され、イベントなどの処理は紐づけられていません。
1. RSC Payloadを用いて、DOMを再調整する
1. `Hydrate`が行われ、イベント紐づけされ、インタラクティブな状態になる

### 保持期間

永続的に保持されます。

### Revalidating

２種類の方法があります。

- [Data Cache Revalidating](https://qiita.com/Leech/items/667d5cca203254dabfd5#revalidating)
- Build

### Cache無効化

以下のいずれかの方法で無効化できます。

- [Dynamic Function](https://nextjs.org/docs/app/building-your-application/caching#dynamic-functions)
- [Route Segment Config](https://nextjs.org/docs/app/api-reference/file-conventions/route-segment-config)
```js
export const dynamic = 'force-dynamic'
// Or
export const revalidate = 0
```

- [Data Cache Opting Out](https://nextjs.org/docs/app/building-your-application/caching#opting-out-1)

## 実際に確認

`.next/server/app`フォルダに対して、`.rsc`と`.html`の生成有無を確認していきます。


### 実装

##### Project

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

##### Component

```tsx:src/app/full-route-cache/page.tsx
import { getRandomNum } from '@/utils/app-fetch';

export default async function Home() {
  const val = await getRandomNum();
  console.log('Full Route Cache', val);
  return <div>{val}</div>;
}
```

```tsx:src/app/full-route-cache/data-cache-revalidating/page.tsx
import RevalidatePath from '@/components/full-route-cache/revalidate-path';
import { getRandomNum } from '@/utils/app-fetch';

export default async function DataCacheRevalidating() {
  const val = await getRandomNum();
  console.log('DataCacheRevalidating', val);
  return (
    <div>
      {val}
      <br />
      <RevalidatePath />
    </div>
  );
}
```

```tsx:src/components/full-route-cache/revalidate-path.tsx
'use client';

import { revalidatePath } from 'next/cache';

export default function RevalidatePath() {
  const onClick = async () => {
    await fetch('/api/full-route-cache/revalidate-path');
  };
  return <button onClick={onClick}>Revalidate</button>;
}
```

```tsx:src/app/full-route-cache/dynamic-function/page.tsx
import { cookies } from 'next/headers';

export default async function DynamicFunction() {
  cookies();
  return <div />;
}
```

```tsx:src/app/full-route-cache/route-segment/page.tsx
export const dynamic = 'force-dynamic';

export default async function RouteSegment() {
  return <div />;
}
```

```tsx:src/app/full-route-cache/data-cache-opting-out/page.tsx
import { getRandomNumberWithNoDataCache } from '@/utils/app-fetch';

export default async function DataCacheOptingOut() {
  await getRandomNumberWithNoDataCache();
  return <div />;
}
```

##### Route Handler

```ts:src/app/api/full-route-cache/revalidate-path/route.ts
import { revalidatePath } from 'next/cache';

export async function GET() {
  revalidatePath('/full-route-cache/data-cache-revalidating');
  return new Response('', { status: 200 });
}
```

##### Fetch Function

```typescript:src/utils/app-fetch.ts
export async function getRandomNum() {
  const res = await fetch('http://localhost:3005');
  return await res.text();
}

// Opt-out data cache
export async function getRandomNumberWithNoDataCache() {
  const res = await fetch('http://localhost:3005', { cache: 'no-store' });
  console.log(await res.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 build && start
```

### Full Route Cacheは生成されている？

#### Cacheが生成される場合

`/full-route-cache`はシンプルにCacheが生成されます。

`.next/server/app/full-route-cache.html`と`.next/server/app/full-route-cache.rsc`が確認できますね！

<img src="https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/3468974/dbe6b085-e8cd-0a5c-b4dd-b7f5c47ca2ab.png" width=400/>

更に、Build時に出力されるログを確認します。

`Full Route Cache 784`と出力されていた場合、`.next/server/app/full-route-cache.html`には以下のような値が静的に生成されています。

```html:.next/server/app/full-route-cache.html
 <body class="__className_aaf875">
    <!--$-->
    <div>784</div>
    <!--/$-->
```

#### Cache Revalidating

`/full-route-cache/data-cache-revalidating`では、`revalidatePath()`を発火後にCacheが再構築されていることを確認します。

`.next/server/app/full-route-cache/data-cache-revalidating.html`と`.next/server/app/full-route-cache/data-cache-revalidating.rsc`が生成されています。

<img src="https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/3468974/fcd7859b-4206-6e83-1d65-3e87957bd6e8.png" width=400/>

更に、Build時に出力されるログを確認します。

`DataCacheRevalidating 784`と出力されていた場合、`.next/server/app/full-route-cache/data-cache-revalidating.html`には以下のような値が静的に生成されています。

```html:.next/server/app/full-route-cache/data-cache-revalidating.html
 <body class="__className_aaf875">
    <!--$-->
    <div>784</div>
    <!--/$-->
```

`http://localhost:3000/full-route-cache/data-cache-revalidating`にアクセスすると、以下のようなUIが描画されるはずです。

`Revalidate`をクリックし、Reloadしてみましょう。

![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/3468974/b9900e18-1caa-d560-05b1-13a7e6d71ba6.png)

すると、以下のように値が変わっていることが確認できますね！

つまり、Full Route Cacheされていた内容が更新されたということになります。

```html:.next/server/app/full-route-cache/data-cache-revalidating.html
 <body class="__className_aaf875">
    <!--$-->
    <div>523</div>
    <!--/$-->
```

### Cacheを無効化

`/full-route-cache/dynamic-function`、`/full-route-cache/route-segment`、`/full-route-cache/data-cache-opting-out`には、Opting Outする処理が組み込まれており、`.next/server/app/full-route-cache`配下に`.html`と`.rsc`が**生成されていない**ことが分かりますね！

## 最後に

シンプルに**静的生成されるRouteはFull Route Cacheされる**という感覚でOKだと思います！

ただ、`revalidatePath()`などでCacheが再更新されてしまうので、静的生成したRouteに関しては特別な管理をしないとFull Route Cacheの良さを消してしまうことになりそうです。

[Request Memoization]: https://qiita.com/Leech/items/b118ae11d348ca37c271
[Data Cache]: https://qiita.com/Leech/items/667d5cca203254dabfd5
[Router Cache]: https://qiita.com/Leech/items/c9669e7d109651d1106c

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