もうNext.js のキャッシュから逃げない～データを貯蔵する四つの倉庫 ～

# 1. はじめに
Next.js は App Routerを採用して以降、そのキャッシュ戦略も洗練されたものになりました。

基本的にデータはキャッシュされ、サーバーコンポーネントは静的ページとなり、高いパフォーマンスを発揮するようになっています。

しかし、ここら辺の仕様がわからないまま、「サーバーコンポーネントでデータを取得した方がよい」という程度の理解だけで使っていると、本番環境でなぜかデータが更新されないことに戸惑ったり、裏側の動きがわかっていないために、どの段階のキャッシュが更新を妨げているかがわからなかったりします。

かくいう私も、キャッシュの理解は後回しにし続けてきました。
公式ドキュメントもキャッシュの解説ページに割いている分量が多く、これを読み込むのが億劫だったからです。
しかし、いま進めているプロダクト制作の一環で、サーバー側へのリクエストを絞らなければならないかもしれず、その際にはどうしても適切なキャッシュ戦略が必要です。
サーバーの性能のためにキャッシュを考えなければならない例はあまり多くないかもしれません（ページの初期表示や更新頻度の問題でキャッシュを気にすることが多いと思います）が、分かっておくに越したことはないので、忘備録も兼ねて、自分なりの理解をまとめておきたいと思います。

今回の記事で用いる主要なライブラリーのバージョンは以下の通りです。
```json
 {
    "react": "^18",
    "react-dom": "^18",
    "next": "14.1.1"
  }
```

# 目次
- [1. はじめに](#1-はじめに)
- [2. キャッシュとは](#2-キャッシュとは)
- [3. Next.jsのキャッシュ](#3-nextjsのキャッシュ)
    - [3-1. Data Cache](#3-1-data-cache)
    - [3-2. Request Memoization](#3-2-request-memoization)
    - [3-3. Full Route Cache](#3-3-full-route-cache)
    - [3-4. Router Cache](#3-4-router-cache)
- [4. revalidateする三つの方法](#4-revalidateする三つの方法)
    - [4-1. revalidateオプション](#4-1-revalidateオプション)
    - [4-2. revalidateTag](#4-2-revalidatetag)
    - [4-3. revalidatePath](#4-3-revalidatepath) 
- [5. キャッシュされないとき](#5-キャッシュされないとき)
- [6. おわりに](#6-おわりに)
- [7. 参考](#7-参考)

# 2. キャッシュとは
キャッシュは、データや情報を一時的に保存する仕組みや場所を指します。Webアプリ開発において、キャッシュはウェブページやアプリケーションのパフォーマンスを向上させるために広く利用されています。

まずキャッシュは、ユーザーに個別的に提供されるものかユーザー間で共有されるものかで異なります。
ローカルなキャッシュの保存はブラウザーが行い、そこには共有データのみならずプライベートなデータもキャッシュされています。そこからアプリサーバーに至るまでに、CDNやProxyサーバーにおいてデータがキャッシュされています。
このように、データはクライアントとアプリサーバーの間において何重にも保存され、サーバーコストやアクセス回数の減少を達成しようとしています。
この流れは以下のような図に示せます。

![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/2826164/84ce2c53-142e-fbd6-a6bc-519a2809264d.png)
「HTTPキャッシュ入門の入門」より画像を引用。

また、ブラウザーにもキャッシュの方法が複数あり、例えばメインメモリーに直接保存するインメモリーキャッシュや、ブラウザーがストレージ上にデータを保存するブラウザーキャッシュが存在するようです。

後述しますが、Next.jsはデータを保存する四つの段階・キャッシュのうち、クライアント側はRouter Cacheの一つしかありません。Next.jsはサーバーに比重を置いたキャッシュ戦略をとっており、クライアント側のキャッシュはどうしているかというと、インメモリーだそうです。
ただし、Next.jsがドキュメントで以下のように説明しているように、Router CacheはNext.jsとServer Componentsに特化したインメモリーキャッシュであって、ブラウザーがメモリー上にデータを保存することで実装するback forward cacheとは異なるようです。

>**Related Terms:**
You may see the Router Cache being referred to as Client-side Cache or Prefetch Cache. While Prefetch Cache refers to the prefetched route segments, Client-side Cache refers to the whole Router cache, which includes both visited and prefetched segments. This cache specifically applies to Next.js and Server Components, and is different to the browser's bfcache
, though it has a similar result.

:::note info
JavaScriptのweb apiにおいて、[ブラウザキャッシュを操作できるメソッド](https://developer.mozilla.org/ja/docs/Web/API/Cache)がサポートされているようです。
後述する、キャッシュのopt outやrevalidateをちゃんとやってもデータの更新ができない場合は、ブラウザキャッシュに手を加えるのも一つの手かもしれません。
:::

# 3. Nextjsのキャッシュ
さて、ここからが本題です。
まず、Next.jsにおけるキャッシュのデフォルト状態を説明します。

Next.jsは、デフォルトではパフォーマンスを向上させるためにできるだけデータをキャッシュしておこうとします。Page RouterにおけるSSGがデフォルトの状態であり、そこからfetchメソッドのオプションや、on demandのrevalidateメソッドを利用してデータの更新（ISRやSSGに相当）を行っていくことになります。

次に、Next.jsがデータをキャッシュする詳しいプロセスを説明します。
主に、Next.jsのキャッシュは四つに分けられます。
データソース（データベース）に近い方から順に、以下の通りです。
- Data Cache
- Request Memoization
- Full Route Cache
- Router Cache

このうち、Router Cacheのみがクライアントサイドにてキャッシュされ、他の三つはサーバーにおいてキャッシュされます。

また、複数のユーザーのアクセスやデプロイに対してキャッシュしたデータを返すData Cacheがcross sharedなキャッシュで、Reactのコンポーネントツリーにおいて取得したデータを再利用するRequst Memoization以下のキャッシュがプライベートなキャッシュであると言えます。
Full Route Cacheがレンダリング結果のキャッシュであり、Router CacheがナビゲーションのUXを向上させるためのキャッシュです。

全体の流れは以下の図のようになっています。

![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/2826164/18e73b65-c382-29ef-9ce9-e14d8bbabc2e.png)
Next.js 公式ドキュメントより引用

## 3-1. Data Cache
Data CacheはNext.jsのもつビルドインのキャッシュシステムであり、サーバーへのアクセスやデプロイを跨いでフェッチしたデータを保持します。
キャッシュのopt out機能やrevalidate機能を用いない場合、サーバーでfetchされて表示されるデータはbuild時にfetchされたデータがそのまま映し出されることになります。

実行環境を整えるための`npm run build`をしてみると、`.next/cache/fetch-cache`フォルダー内に、build時に取得してきたデータがJSON形式で保管されていることがわかります。

例えば、ビルドした時に現時点での時間を取得できるapiをfetchしてきたのですが、それは以下のような形で保存されています。
```json
{
    "kind":"FETCH",
    "data":{
        "headers":{
            "access-control-allow-credentials":"true",
            "access-control-allow-origin":"*",
            "access-control-expose-headers":"",
            "cache-control":"max-age=0, private, must-revalidate",
            "content-encoding":"gzip",
            "content-length":"245",
            "content-type":"application/json; charset=utf-8",
            "cross-origin-window-policy":"deny",
            "date":"Sun, 03 Mar 2024 09:34:00 GMT",
            "fly-request-id":"01HR1TKGQR6V58M8H11BFS8W69-hkg",
            "server":"Fly/17ce28b7 (2024-03-02)",
            "vary":"accept-encoding",
            "via":"1.1 fly.io",
            "x-content-type-options":"nosniff",
            "x-download-options":"noopen",
            "x-frame-options":"SAMEORIGIN",
            "x-permitted-cross-domain-policies":"none",
            "x-ratelimit-limit":"1800",
            "x-ratelimit-remaining":"1798",
            "x-ratelimit-reset":"1709460000",
            "x-request-from":"2001:268:c2ce:174a:f02e:a3e2:6d3b:d184",
            "x-request-id":"F7k3ZRx1Vipz3ukLpYHB",
            "x-request-regions":"a/hkg;s/sin",
            "x-response-origin":"178103db41e589",
            "x-runtime":"334Âµs",
            "x-xss-protection":"1; mode=block"
        },
        "body":"eyJhYmJyZXZpYXRpb24iOiJKU1QiLCJjbGllbnRfaXAiOiIyMDAxOjI2ODpjMmNlOjE3NGE6ZjAyZTphM2UyOjZkM2I6ZDE4NCIsImRhdGV0aW1lIjoiMjAyNC0wMy0wM1QxODozNDowMC45NzMxMzUrMDk6MDAiLCJkYXlfb2Zfd2VlayI6MCwiZGF5X29mX3llYXIiOjYzLCJkc3QiOmZhbHNlLCJkc3RfZnJvbSI6bnVsbCwiZHN0X29mZnNldCI6MCwiZHN0X3VudGlsIjpudWxsLCJyYXdfb2Zmc2V0IjozMjQwMCwidGltZXpvbmUiOiJBc2lhL1Rva3lvIiwidW5peHRpbWUiOjE3MDk0NTg0NDAsInV0Y19kYXRldGltZSI6IjIwMjQtMDMtMDNUMDk6MzQ6MDAuOTczMTM1KzAwOjAwIiwidXRjX29mZnNldCI6IiswOTowMCIsIndlZWtfbnVtYmVyIjo5fQ==",
        "status":200,
        "url":"https://worldtimeapi.org/api/timezone/Asia/Tokyo"
        },
    "revalidate":31536000,"tags":[]
}
```
なぜ、様々なユーザーからのfetchリクエストやデプロイを経てもデータが保持され続けるのかについて、ドキュメントの中で明示的な解説を見つけることはできませんでした。
私としては、上のように取得結果をファイルとして保持しておくことで、デプロイ時、同じfetch処理ごとに初期の取得結果を反映させるようにしているのではないかと予想します。
実際、立て続けにビルドしても日付のデータは更新されませんでしたが、`fetch-cache`フォルダを消してからビルドしてみるとみると、データが更新されていました。
このことからもData Cacheシステムは、内部的には、fetch結果をファイルにまとめてサーバーが保管することで実現されているのだろうと考えられます。

## 3-2. Request Memoization
Next.jsはデフォルトで、同一のURLに対して同一の方法(GETメソッドなど)でアクセスするとき、重複するfetchメソッドに対しては保存していたキャッシュからデータを返却するようにしています。
これは一つのレンダーパスにおいても有効であるため、例えばページを表示するルートコンポーネントにおいて、generateMetadataでデータをfetchし、HomePageコンポーネントでまた同じデータをフェッチしたとしても、実際は始めの一回が実行されるだけで何度もサーバーへのアクセスをしなくてもいいようになっています。
図示すると以下の通りです。

![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/2826164/d7e8682d-ef80-b604-bd0c-1cd6381536a1.png)
Next.js 公式ドキュメントより引用

これを確認するために、以下のような構成でNext.jsアプリを作りました。
実行環境時の挙動を見るために、`npm run start`コマンドでアプリを立ち上げています。

```:ディレクトリ構成
C:.
├─app
│  │  favicon.ico
│  │  globals.css
│  │  layout.tsx
│  │  page.tsx
│  │
│  └─date
│          page.tsx
│
├─components
│      formerFetch.tsx
│      latterFetch.tsx
│
└─utils
        definitions.tsx
        serverActions.tsx
        typeGuards.tsx
```

Request Memoizationの動きを確認したいのに、Data Cacheが邪魔してくるので、以下のようにfetchの際に`cache:"no-store"`を指定します。
```typescript:Formerfetch.tsx
import { isWorldTimeAPI } from "@/utils/typeGuards";

export default async function FormerFetch(){
    const resWorldTime = await fetch("https://worldtimeapi.org/api/timezone/Asia/Tokyo",{cache:"no-store"});
    const timeWorldApi = await resWorldTime.json();

    if(!isWorldTimeAPI(timeWorldApi)) return null;

    const date = new Date();
    
    return (
        <div>
            <p>now:{date.toString()}</p>
            <p>fetch WorldTime:{timeWorldApi.datetime}</p>
        </div>
    );
}
```
`cache:"no-store"`の詳細は[第五節](#5-キャッシュされないとき)に譲ることにしますが、端的に言えばData Cacheを無視してデータソースから直接データを持ってくるものです。
ただし、Data Cacheでキャッシュをしようとしなかろうと、Request Memoizationにはキャッシュされます。
それを確認するために、LatterFetchコンポーネントも作っておきます。
これは、10秒待ってからコンポーネントの読込をスタートさせるために、awaitをつけてsetTimeout関数を実行させたものです。
もし本当に同一パス内のfetchが、Request Memoizationにおいてキャッシュされたデータから取得するのであれば、timeWorldAPIの値はformerFetchと同じになるはずです。
```typescript:Latterfetch.tsx
import { isWorldTimeAPI } from "@/utils/typeGuards";

export default async function LatterFetch(){
    await new Promise(resolve => setTimeout(resolve,10000));

    const resWorldTime = await fetch("https://worldtimeapi.org/api/timezone/Asia/Tokyo",{cache:"no-store"});    
    const timeWorldApi = await resWorldTime.json();

    if(!isWorldTimeAPI(timeWorldApi)) return null;

    const date = new Date();
    
    return (
        <div>
            <p>now: {date.toString()}</p>
            <p>fetch WorldTime: {timeWorldApi.datetime}</p>
        </div>
    );

}
```
appディレクトリーのpage.tsxの中身は以下のようになっています。formerFetchとLatterFetchを同一ページ（レンダリングルートパス）内においていることを確認できれば大丈夫です。
```typescript:page.tsx
import FormerFetch from "@/components/formerFetch";
import LatterFetch from "@/components/latterFetch";
import { Suspense } from "react";

export default async function Home() {
  return (
    <main className="flex min-h-screen flex-col items-center justify-around p-24">
      <Suspense fallback={<>Loading...</>} >
        <FormerFetch />
      </Suspense>
      <br />
      <Suspense fallback={<>Loading...</>} >
        <LatterFetch />
      </Suspense>
    </main>
  );
}
```
先程、Request Memoizationは同一のレンダリングルートパスについて適用されると言いました。
即ち、/dateディレクトリー内にあるこのページコンポーネントは、ホームページのfetchより五秒遅れるはずであると予想が立てられます。
```typescript:DatePage.tsx
import { isTimeAPI, isWorldTimeAPI } from "@/utils/typeGuards";

export default async function Home() {
  await new Promise(resolve => setTimeout(resolve,5000));
  const date = new Date();

  const resWorldTime = await fetch("https://worldtimeapi.org/api/timezone/Asia/Tokyo",{cache:"no-store"});    
  const timeWorldApi = await resWorldTime.json();

  if(!isWorldTimeAPI(timeWorldApi)) return null;
    
  return (
    <main className="flex min-h-screen flex-col items-center justify-around p-24">
      <p>now:{date.toString()}</p>
      <br />
      <p>World Time API:{timeWorldApi.datetime}</p>
    </main>
  );
}
```
さて、挙動を確認する下準備はできました。
結果を見てみましょう。
まず、ホームページの内容です。

![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/2826164/d0d546ed-7356-1514-36b2-fc33455ad73a.png)

このように、LatterFetchコンポーネントは、10秒待ってから読み込むようにしているため、Dateクラスのインスタンス化もFormerFetchより10秒遅れて実行されていることがわかります。しかし、fetch WorldTimeの結果（13時18分49秒）は見事に一致しています。
しかし、/dateページにおいては、以下のように、WorldTimeの結果は異なります（13時21分53秒）。

![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/2826164/c9f4cc62-2d61-a83d-b77c-9f515e90b7e0.png)

もし、fetch時に`cache:"no-store"`を指定せず、Data Cacheをそのまま利用していたならば、このページにおいても最初に取得したfetchのデータが再利用され、結果が一致していたことになります。

少し複雑ですが、ここまでの話をまとめると、
1. Data Cacheは共有キャッシュであって、全体（ビルド時）のfetch取得結果をキャッシュしており、これはデータソースへのアクセスを抑制するために機能する。これにより、デフォルトでは静的ファイルの表示を実現している(SSG)。
2. Request Memoizationはプライベートなキャッシュであって、同一のレンダリングルートパス上においてfetchの結果をキャッシュし、fetchの重複を抑制している。これによって、レンダリングサーバーから他のサーバー（CDNやデータサーバー）へのアクセスを抑制する機能を実現している。

ここまでで、かなりキャッシュの整理をつけることができたと思います。

ただ、もうちょっとあります。あと二つです。頑張りましょう。

## 3-3. Full Route Cache
Full Route Cacheは何をしているかと言われれば、RSC Payloadとサーバーで生成したHTMLをキャッシュしているのだそうです(build/revalidation time)。これは、ユーザーがアクセスしたときに素早くページを表示するために役立ちます。

Next.jsがページを表示するプロセスについても、すこし話しておこうと思います。
ユーザーがページを表示するようにリクエストを出した時(request time)、まずサーバーコンポーネントとクライアントコンポーネントのプレビューを示すHTMLファイルが表示され、次にRSC payloadを用いてサーバーコンポーネントを表示し、DOMを更新。最後に、クライアントコンポーネントをハイドレーションしてページの表示ができます[^1]。
そのレンダリング結果は次に解説するRouter Cacheに渡され、ここでもキャッシュされます。

Full Route Cacheは、サーバーコンポーネントのレンダリング過程において介在し、その静的ページを表示する前段階においてレンダリング結果をキャッシュするもの、とまとめることができると思います。
ただし、この機能はStatic Pageかrevalidateメソッドを用いているときにのみ作動することに注意してください。
revalidateが発火した時は、上流部分（Data Cache）で更新したデータが下流のFull Route Cacheにまで流れてきて、自動的に再レンダリング・再キャッシュが行われます。一方で、例えばfetch時に`cache:"no-store"`を指定すると、Full Route CacheはData Cache同様スキップされます。
このため、動的なレンダリングを求める際は、HTMLによる初期表示の恩恵を受けられないのでご注意ください。
[^1]: サーバーコンポーネントのレンダリングプロセスのより詳しい解説は、[ドキュメント](https://nextjs.org/docs/app/building-your-application/rendering/server-components#how-are-server-components-rendered)を参照することをお勧めします。

## 3-4. Router Cache
Router Cacheは、Next.jsがRSC Payloadなどのデータを保存する、唯一のクライアントキャッシュです。ナビゲーションのUXを向上させるために用いられます。

これは主に、ユーザーが訪れたページの初期表示のレンダリング結果と、`<Link>`コンポーネントがユーザーに表示された際に裏側で実行されるprefetchの結果を保持することによって、ページ遷移の際にすべてをレンダリングするのではなく、部分的にレンダリングするだけでページを表示する（partial rendering）ことが可能になります。
クライアントサイドに近いということもあってか、このキャッシュが一番寿命が短くなる設計になっています。
とはいえ、ページのデータはキャッシュしていますし、たとえばユーザーがpostメソッドを用いてアクションを起こした時にそのページをすぐさま更新したいようでしたら、On-demand revalidateメソッドを用いることをお勧めします。

# 4. revalidateする三つの方法
別に、revalidateするのは三つに限らず、あまり見かけないメソッドも含めればもっと多いのですが、使いやすくよく見かけるメソッドを三つほど紹介します。

## 4-1. revalidateオプション
一つ目は、fetchメソッドのinitオブジェクトに渡す、オプションとしてのrevalidateオプションです。具体的には、以下のように指定します。
```typescript:time-based-revalidate.tsx
const res = await fetch("path",{
    method:"GET",
    next:{revalidate:60}
});
```
このように、`next:{revalidate:60}`を指定してあげることで、一分ごとにData Cacheを更新するように指定することができます。これはちょうど、Pages RouterにおけるISRに相当します。

## 4-2. revalidateTag
二つ目は、nextプロパティに対して、revalidateではなく、tagsを指定するものです。
fetchメソッドに対してタグを指定することによって、タグに基づいてrevalidateTagするとき、関連したfetchメソッドがすべて更新されます。
同じようなデータを取得しているfetchメソッドをまとめて更新したいとき、あるいは同一のfetchメソッドだけれども、別のページでも使っている分もまとめて更新したいときなどに有用だと思います。
tagsの指定方法は以下の通りです。
```typescript:revalidateTags.tsx
const res = await fetch("path",{
    method:"GET",
    next:{tags:["date"]}
});
```
ただし、このままではrevalidateしてくれません。
これは、Server ActionsやRoute Handlerなどを利用してサーバー側から更新処理を発火させなければならないからです。
そのための実装例を紹介します。
先程紹介したNext.jsプロジェクトのうち、componentsにrevalidateTagコンポーネントを追加します。
中身は以下の通りです。
```typescript:RevalidateTag.tsx
"use client";

import { serverRevalidateTag } from "@/utils/serverActions";

export default function RevalidateTag(){
    return <button onClick={async()=> await serverRevalidateTag()} className="border border-blue-600 bg-gray-600" >revalidateTag</button>;
}
```
revalidateTagメソッドは、以下のようにサーバーアクションの中において作動させています。
```typescript:serverActions.tsx
"use server";

import { revalidateTag } from "next/cache";

export async function serverRevalidateTag(){
    revalidateTag("date");
}
```
タグの挙動を確認するために、dateページにtagを付けたfetchメソッドと、revalidateTagコンポーネントをつけておきましょう。
```diff_typescript:DatePage.tsx
import RevalidateTag from "@/components/RevalidateTags";
import { isTimeAPI, isWorldTimeAPI } from "@/utils/typeGuards";

export default async function DatePage() {
  const date = new Date();

  const resWorldTime = await fetch("https://worldtimeapi.org/api/timezone/Asia/Tokyo");    
  const timeWorldApi = await resWorldTime.json();

  if(!isWorldTimeAPI(timeWorldApi)) return null;

+  const resTime = await fetch("https://www.timeapi.io/api/Time/current/zone?timeZone=Asia/Tokyo",{next:{tags:["date"]}});
+  const timeApi = await resTime.json();
+
+  if(!isTimeAPI(timeApi)) return null;
    
  return (
    <main className="flex min-h-screen flex-col items-center justify-around p-24">
      <p>now:{date.toString()}</p>
      <br />
      <p>World Time API:{timeWorldApi.datetime}</p>
+      <br />
+      <p>Time API:{timeApi.dateTime}</p>
+      <br />
+      <RevalidateTag />
    </main>
  );
}
```
今回は、日時を取得できる別のページのfetchの際に、tagをつけています。
dateページの初期画面はこのような形です。

![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/2826164/c2d843fd-6b00-843d-c51d-8c03c9b2d05c.png)

ここで、revalidateTagボタンを押してみましょう。
先程、revalidateTagメソッドはtagに紐づいたfetchメソッドが更新されると言いました。ですから、Time APIの時間が変わり、World Time APIは変わらないはずです。

![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/2826164/8051936b-ebc5-0fca-e567-ef272d56f5b9.png)

はい。予想通りの結果になりました。
現在時刻の結果が変わっているのは、revalidateしたならばその新しいデータに基づいて再レンダリングされるためです。
ただし、revalidateTagはすぐには結果が更新されず、ユーザーが再度ページにアクセスした場合に再フェッチが行われることに注意してください。

## 4-3. revalidatePath
最後は、パス、ページ全体をrevalidateするメソッドです。
これは非常にシンプルなrevalidateメソッドで、fetchの際にも何かを指定する必要はありません。
Server ActionsやRoute Handlerなどを用いて、サーバー側でrevalidateしたいパスを指定してあげるだけで大丈夫です。
この挙動を確認するために、dateページ全体をrevalidateするボタンを作って、ページに表示しましょう。
まず、RevalidatePathボタンを作ります。
```typescript:RevalidatePath.tsx
"use client";

import { serverRevalidatePath } from "@/utils/serverActions";

export default function RevalidatePath(){
    return <button onClick={async()=> await serverRevalidatePath()} className="border border-blue-600 bg-gray-600" >revalidatePath</button>;
}
```
revalidatePathメソッドは、以下のようにサーバーアクションの中において作動しています。
```typescript:serverActions.tsx
"use server";

import { revalidatePath } from "next/cache";

export async function serverRevalidatePath(){
    revalidatePath("/date");
}
```
revalidatePathボタンをdateページに表示します。
```diff_typescript:DatePage.tsx
import RevalidateTag from "@/components/RevalidateTags";
import { isTimeAPI, isWorldTimeAPI } from "@/utils/typeGuards";

export default async function DatePage() {
  const date = new Date();

  const resWorldTime = await fetch("https://worldtimeapi.org/api/timezone/Asia/Tokyo");    
  const timeWorldApi = await resWorldTime.json();

  if(!isWorldTimeAPI(timeWorldApi)) return null;

  const resTime = await fetch("https://www.timeapi.io/api/Time/current/zone?timeZone=Asia/Tokyo",{next:{tags:["date"]}});
  const timeApi = await resTime.json();

  if(!isTimeAPI(timeApi)) return null;
    
  return (
    <main className="flex min-h-screen flex-col items-center justify-around p-24">
      <p>now:{date.toString()}</p>
      <br />
      <p>World Time API:{timeWorldApi.datetime}</p>
      <br />
      <p>Time API:{timeApi.dateTime}</p>
      <br />
+     <RevalidatePath />
    </main>
  );
}
```
まず、初期画面は以下の通りです。

![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/2826164/3499854d-c8b5-6f58-7b74-6696c12098ee.png)

ここで、revalidatePathボタンを押します。
先程、revalidatePathボタンはページ全体をrevalidateすると言いました。このため、もしこのボタンを押せば、タグやfetchメソッドに関係なく、このページに表示されてあるWorld Time APIやTime APIが共に更新されるはずです。
それでは見てみましょう。

![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/2826164/3be089cf-9101-e040-41f2-c34f8e93eddc.png)

はい。想定通りの挙動をしてくれています。
すべて時間が更新されていることがわかると思います。
画像は貼りませんが、同様のAPIから日時情報をfetchしているはずのホームページの中では、時間は更新されていませんでした。このことからも、このメソッドは指定されたページをrevalidateし、その他のページやそこで実行されているfetchには干渉しないことが分かったと思います。

# 5. キャッシュされないとき
一番わかりやすいのは、fetchメソッドのinitのなかで、`next:"no-store"`を指定することだとおみます。
また、意図的にopt outしたいときは、ルートセグメントのオプションとして、`const dynamic = "force-dynamic"`を指定したときでしょうか。
しかし、その他の場合においてはやや意図しない挙動として表れるかもしれません。
特に、
・`headers`や`cookies`を利用したあとにfetchメソッドを用いたり
・fetchメソッドのヘッダーに`Authorization`や`Cookie`を指定したり
・コンポーネントツリーの上部にキャッシュをしないリクエストが存在したりすると
自動的にキャッシュが避けられます。
ここら辺の挙動には注意しておいた方がよいと思います。

<br />

最後に、cache, revalidate, opt outの挙動を示した一覧を掲載してこの記事を終わります。

![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/2826164/da377c29-74cf-cc12-a14d-a13393e4c056.png)
Next.js 公式ドキュメントより引用。

# 6. おわりに
Next.jsの鬼門はキャッシュだと思います。
キャッシュを解説したNext.jsの公式ドキュメントは、「この知識は不可欠なわけではない」とは言っていましたし、多少曖昧でもNext.jsを使えるようには設計されてあるのだとは思います。
しかし、SSR、SSG、SIRがApp Routerにおいてどのように実現されているかが曖昧なままだったり、どこでどんな風にデータがキャッシュされているかがわからないままなんとなく使っているのは、不安が残っていました。
この記事をまとめる過程で、概ね理解することができたので、そこはよかったと思います。

Reactの公式ドキュメントもいつの間にか一新していたので、こちらも読んでみたいなって思ったり、TypeScriptの練習ももっとちゃんとやらないとなって思ったり、Dockerコンテイナーが便利そうだから使えるようになりたいなって思ったり、サーバー側にも手を出してみたいなって思ったり。
やりたいことが多いというのは、うれしいような大変なようなって感じですね。

## 7. 参考

### [第二節](#2-キャッシュとは)
[httpキャッシュの入門の入門](http://blog.xcir.net/?p=2806)
[フロントエンドエンジニアが知るべきキャッシュを理解する](https://zenn.dev/kaa_a_zu/articles/f1430cf681b185#serviceworker%E3%82%AD%E3%83%A3%E3%83%83%E3%82%B7%E3%83%A5)

### [第三節以降](#3-nextjsのキャッシュ)
[Building Your Application:Caching|Next.js](https://nextjs.org/docs/app/building-your-application/caching)