【Next.js】４種類の"Cache"を理解したい  Part ① Request Memoization

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]

今回は[**Request Memoization**](https://nextjs.org/docs/app/building-your-application/caching#request-memoization)です。

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

## 概要

**Component Tree**から発生する同じURL・Optionを持つRequestとその結果のResponseを**メモリ**にCacheします。

つまり、Rendering時に発生する重複したRequestに対して、Cacheを保持してくれます。

![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/3468974/dbd4a50c-dbd4-52a7-4793-d492381f53ed.png)
出典 : [Next.Js Request Memoization Overview]

複数のComponentから同じリクエストが発生した場合も、**Request Memoization**が集約しResponseをCacheしてくれています。


以下のように、わざわざComponentの上位層でData Fetchして、Propsで下位層に渡すということをしなくてよくなります。

```jsx:parent-component.jsx
export default async function ParentComponent(){
    const result = await fetchSomething();

    // わざわざPropsで渡さなくてよい！
    // ChildComponent内で await fetchSomethig()を行う。
    return (
    <div>
        <ChildComponet result={result} />
    </div>
    )
}
```

### How it works

![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/3468974/1b3e2cd5-0e79-11a2-2e7b-f65287b32290.png)
出典 : [How Request Memoization Works]

1. 最初のRequestは、メモリに存在しないので、Cacheは取れない
1. そのため、Data SourceへのRequestが実行され、結果がCacheに格納される
1. 以降に発生する一連のRendaring内での同じRequestは、Cacheから結果が取得される
1. 一連のRendaringが完了し、Routeが完成したタイミングで、メモリのCacheはクリアされる

### ポイント

- **Request Memoization**機能は、Reactの機能だが、Next.JsのCacheメカニズムとも連携するため、４種類のCacheに含まれている
- `fetch` を用いた、`Get` Requestのみ対応
- Server Componentの`fetch` Requestが対象
- Route Handler内の`fetch`は対象外。なぜなら、Component Treeに含まれていない、Rendering外の部分のため

**Server ComponentをRenderingするタイミングで、効果を発揮するCacheということ**ですね。


### 保持期間

Rendering中のみ保持されます。

例えば、Buildを２回行った場合、それぞれRendaringが別なので、１回目のCacheが２回目のRendaring時に使用されることはありません。

### Revalidating

RendaringごとにCacheが生成され、上記で記載したように他のRendaringには影響しません。

そのため、Revalidating処理は存在しません。


### Cache無効化

以下の方法で、無効化します。

```jsx:sample.js
const { signal } = new AbortController()
fetch(url, { signal })
```

## 実際に確認

Rendering時に確認できるCacheなので、`next build`や`next start`の後のページリクエスト時のRequest Memoization Cacheを確認するサンプルを作成します。

### 実装

##### Project

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

##### Component

```tsx:src/app/request-memoization/page.tsx
import Component1 from '@/components/component1';
import Component2 from '@/components/component2';
import Component3 from '@/components/component3';

// Static Rendaing
export default function Home() {
  console.log('Home');
  return (
    <div>
      <Component1 />
      <Component2 />
      <Component3 />
    </div>
  );
}
```

```tsx:src/components/component1.tsx
import { getRandomNumber } from '@/utils/app-fetch';

// Static Rendaing
export default async function Component1() {
  getRandomNumber();
  console.log('Component1');
  return <div>Component1</div>;
}
```

```tsx:src/components/component2.tsx
import { getRandomNumber } from '@/utils/app-fetch';
import Component4 from './component4';

// Static Rendaing
export default async function Component2() {
  getRandomNumber();
  console.log('Component2');
  return (
    <div>
      <div>Component2</div>
      <Component4 />
    </div>
  );
}
```

```tsx:src/components/component3.tsx
import { getRandomNumber } from '@/utils/app-fetch';
import { cookies } from 'next/headers';

// Dynamic Rendaing
export default async function Component3() {
  // Dynamic Functions
  cookies();

  getRandomNumber();
  console.log('Component3');
  return <div>Component3</div>;
}
```

```tsx:src/components/component4.tsx
import { getRandomNumber } from '@/utils/app-fetch';
import { cookies } from 'next/headers';

// Dynamic Rendaing
export default async function Component4() {
  // Dynamic Functions
  cookies();

  getRandomNumber();
  console.log('Component4');
  return <div>Component4</div>;
}
```

##### Fetch Function

```typescript:src/utils/app-fetch.ts
export async function getRandomNumber() {
  // 次回説明するData Cacheの影響を抑えるためのOptionを追加している。
  const res = await fetch('http://localhost:3005', { cache: 'no-store' });
  console.log(await res.text());
}
```

##### ダミー外部APIサーバー

Root配下に作成します。

```javascript:external-server.js
const http = require('http');

const app = http.createServer((req, res) => {
  // Make random numbers
  const random = (Math.random() * 1000).toFixed(0);

  res.writeHead(200, { 'Content-Type': 'text/plain' });
  res.end(random);
});

app.listen(3005, '127.0.0.1');
console.log('Extenral Server is running on port 3005');
```

### 外部APIサーバーを起動

※状況に応じて再起動をしてください。

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

### Static Rendaring

Next.Jsアプリケーションをビルドします。

**上記の外部APIサーバーを起動したShellとは別プロセスで起動してください。**

```bash:shell
npm run build
```

Next.Jsサーバーのログに、Static RendaringされたComponentのログが出ています。

外部APIサーバーに対するRequest（`getRandomNumber()`）も実行されており、同じ値が返ってきているのでCacheが効いていますね！

```bash:Next.Jsサーバーログ
Home
Component1
Component2
875
875
```

### Dynamic Rendaring

ビルドしたアプリを起動し、`http://localhost:3000/request-memoization`にBrowserでアクセスします。

```bash:shell
npm run start
```

**Dynamic Server Componentが存在するRouteは、Route全体がDynamic Renderingになる**ため、`/request-memoization`配下の全てのComponentがページリクエスト時に動的に生成されます。そのため、全てのComponentからログが排出されています。

こちらも`getRandomNumber()`の結果が全て同じなので、Cacheが効いていることがわかります。

```bash:Next.Jsサーバーログ
Home
Component1
Component2
Component3
Component4
917
917
917
917
```


### Cacheを無効化

試しに、Request Memoization機能を無効にしてみます。

```diff_typescript:src/utils/app-fetch.ts
+const { signal } = new AbortController();

export async function getRandomNumber() {
- const res = await fetch('http://localhost:3005', { cache: 'no-store' });
+ const res = await fetch('http://localhost:3005', { cache: 'no-store', signal });
  console.log(await res.text());
}
```

`npm run build`、`npm run start`で確認してみると、`getRandomNumber()`からRequestごとに異なる値が返ってきています。

```bash:Next.Jsサーバーログ
Home
Component1
Component2
Component3
Component4
935
308
746
860
```

## `npm run dev`で起動したときは・・・

`http://localhost:3000/request-memoization`にアクセスした**初回はCacheが期待通り動く**のですが、**２回目以降はCacheが効いていない**です。

この不安定な動きに関しては、[Issue](https://github.com/vercel/next.js/issues/55168)が既に上がっているようなので、何かしらのアクションが起こるはず。


## 最後に

Dynamic Renderingが１つでも発生すると、そのRouteのServer Componentは全てDynamic Renderingされるということを今回初めて知りました。

Cacheだけでなく、全体的な理解も求められてきますね！

[Data Cache]: https://qiita.com/Leech/items/667d5cca203254dabfd5
[Full Route Cache]: https://qiita.com/Leech/items/405b0641d9d30f55a51e
[Router Cache]: https://qiita.com/Leech/items/c9669e7d109651d1106c

[Next.Js Request Memoization Overview]: https://nextjs.org/docs/app/building-your-application/caching#request-memoization

[How Request Memoization Works]: https://nextjs.org/docs/app/building-your-application/caching#request-memoization