Closed16

Next.js App Routerのキャッシュのメモ

HTMLGOHTMLGO

今回検証するのは「Request Memorization」と「Data Cache」

準備をする

expressのAPI用のサーバーを用意する

const express = require("express");
const app = express();
const port = 5555;

app.use(express.json());

let getCounter = 0;

app.get('/', (req,res)=>{
  res.setHeader('Access-Control-Allow-Origin', 'http://localhost:3000');
  console.log(`==== get ${++getCounter} =====`)
  res.json({data: `${new Date().getTime()}`})
})

let postCounter = 0;

app.post('/', async(req,res)=>{
  const data = await req.body
  console.log(data)
  res.setHeader('Access-Control-Allow-Origin', 'http://localhost:3000');
  console.log(`==== post ${++postCounter} =====`)
  res.json({data: `${new Date().getTime()}`})
})

app.listen(port, ()=>{
  console.log(`Server is running on port ${port}`)
})

サーバーを起動させておく

http://localhost:5555/

叩いたら、以下が返ってくる(GETメソッド)


{"data": "1708153301754"}

HTMLGOHTMLGO

Fetch & GET

config.ts
export const API_URL = "http://localhost:5555";
app/fetch/page.tsx
import { Child } from "./Child";

import { API_URL } from "./config";

export default async function Page({}: {}) {
  const res1 = await fetch(API_URL, {
    method: "GET",
    headers: {
      "Content-Type": "application/json",
    },
  });
  const res2 = await fetch(API_URL, {
    method: "GET",
    headers: {
      "Content-Type": "application/json",
    },
  });

  const result1 = await res1.json();
  const result2 = await res2.json();

  return (
    <main>
      <h1 className="text-[50px]">fetch</h1>
      <div className="text-red-500">
        <p>{result1.data}</p>
        <p>{result2.data}</p>
      </div>
      <Child />
    </main>
  );
}
app/fetch/Child.tsx
import { API_URL } from "./config";

export async function Child({}: {}) {
  const res1 = await fetch(API_URL, {
    method: "GET",
    headers: {
      "Content-Type": "application/json",
    },
  });
  const res2 = await fetch(API_URL, {
    method: "GET",
    headers: {
      "Content-Type": "application/json",
    },
  });

  const result1 = await res1.json();
  const result2 = await res2.json();

  return (
    <main>
      <div className="text-blue-500">
        <p>{result1.data}</p>
        <p>{result2.data}</p>
      </div>
    </main>
  );
}
HTMLGOHTMLGO

サーバー起動で、ブラウザ閲覧

http://localhost:3000/fetch

Automatic fetch() Request Dedupingという機能のおかげて、
全部で4回コールしているのに(ページで2回+子供コンポーネントで2回)
全部同じ値を流用している。

何回リロードしても変わらない。

API側も一度しかコールされていない。
キャッシュが使われているみたい。

APIサーバーは検証の度に再起動することをお勧めします。

HTMLGOHTMLGO

パラメータを1個だけ変えた

  const res1 = await fetch(API_URL + "?a=1", {
    method: "GET",
    headers: {
      "Content-Type": "application/json",
    },
  });

変えたやつだけ、変わった。
当たり前か。

ただ、また何度リロードしても同じ値。
デフォルトだと、パラメータなどを変えない限り、キャッシュは破棄されないみたい

HTMLGOHTMLGO

revalidatePathを追加

app/fetch/page.tsx

export default async function Page({}: {}) {
  const res1 = await fetch(API_URL + "?a=1", {
    method: "GET",
    headers: {
      "Content-Type": "application/json",
    },
  });
  const res2 = await fetch(API_URL, {
    method: "GET",
    headers: {
      "Content-Type": "application/json",
    },
  });

  const result1 = await res1.json();
  const result2 = await res2.json();

  revalidatePath("/fetch");

  return (
    <main>
      <h1 className="text-[50px]">fetch</h1>
      <div className="text-red-500">
        <p>{result1.data}</p>
        <p>{result2.data}</p>
      </div>
      <Child />
    </main>
  );
}

それぞれ、独立した値になる & リロード度に新しい値になる。

1リロードでAPIも4回叩かれている。毎回。
キャッシュを使っていないようだ。

HTMLGOHTMLGO

今度はrevalidateのテスト。
urlは全部同じにする。
4つの関数コールで、revalidateの値を独立させる

app/fetch/page.tsx

import { Child } from "./Child";

import { API_URL } from "./config";

export default async function Page({}: {}) {
  const res1 = await fetch(API_URL, {
    method: "GET",
    headers: {
      "Content-Type": "application/json",
    },
    next: { revalidate: 10 },
  });
  const res2 = await fetch(API_URL, {
    method: "GET",
    headers: {
      "Content-Type": "application/json",
    },
    next: { revalidate: 5 },
  });

  const result1 = await res1.json();
  const result2 = await res2.json();

  return (
    <main>
      <h1 className="text-[50px]">fetch</h1>
      <div className="text-red-500">
        <p>{result1.data}</p>
        <p>{result2.data}</p>
      </div>
      <Child />
    </main>
  );
}
app/fetch/Child.tsx

import { API_URL } from "./config";

export async function Child({}: {}) {
  const res1 = await fetch(API_URL, {
    method: "GET",
    headers: {
      "Content-Type": "application/json",
    },
    next: { revalidate: 100 },
  });
  const res2 = await fetch(API_URL, {
    method: "GET",
    headers: {
      "Content-Type": "application/json",
    },
    next: { revalidate: 25 },
  });

  const result1 = await res1.json();
  const result2 = await res2.json();

  return (
    <main>
      <div className="text-blue-500">
        <p>{result1.data}</p>
        <p>{result2.data}</p>
      </div>
    </main>
  );
}

リロードしまくる。
それぞれの呼び出し結果は、同じ=> fetchのキャッシュのため。
(下の図だと時間がよくわかりませんが、リロードしまくってます。)

今度は5秒に1度、データが更新される


APIも5秒に1回コールされている。

つまり一番小さい、これが採用されている。

    next: { revalidate: 5 },
HTMLGOHTMLGO

    next: { revalidate: 5 },
    next: { revalidate: 0 },
    next: { revalidate: 100 },
    next: { revalidate: 25 },

1個だけ、revalidateを0にしたら、それだけ、リロード毎に更新されて、
あとはその次に短い5秒で統一されるっぽい。

ただ、ちょっと挙動があやしい。
基本、同じURLで、revalidate の数字を変える等はやらない方がいい。

HTMLGOHTMLGO

Fetch & POST

今度は、postメソッド、APIはさっきと同じ。

app/page.tsx
import { Child } from "./Child";

import { API_URL } from "./config";

export default async function Page({}: {}) {
  const res1 = await fetch(API_URL, {
    method: "POST",
    headers: {
      "Content-Type": "application/json",
    },
    body: JSON.stringify({
      data: "data",
    }),
  });
  const res2 = await fetch(API_URL, {
    method: "POST",
    headers: {
      "Content-Type": "application/json",
    },
    body: JSON.stringify({
      data: "data",
    }),
  });

  const result1 = await res1.json();
  const result2 = await res2.json();

  return (
    <main>
      <h1 className="text-[50px]">fetch</h1>
      <div className="text-red-500">
        <p>{result1.data}</p>
        <p>{result2.data}</p>
      </div>
      <Child />
    </main>
  );
}

app/Child.tsx
import { API_URL } from "./config";

export async function Child({}: {}) {
  const res1 = await fetch(API_URL, {
    method: "POST",
    headers: {
      "Content-Type": "application/json",
    },
    body: JSON.stringify({
      data: "data",
    }),
  });
  const res2 = await fetch(API_URL, {
    method: "POST",
    headers: {
      "Content-Type": "application/json",
    },
    body: JSON.stringify({
      data: "data",
    }),
  });

  const result1 = await res1.json();
  const result2 = await res2.json();

  return (
    <main>
      <div className="text-blue-500">
        <p>{result1.data}</p>
        <p>{result2.data}</p>
      </div>
    </main>
  );
}

HTMLGOHTMLGO

POSTでも同じdataなら、
何回リロードしてもキャッシュで同じ値。

コールも1回

Automatic fetch() Request Deduping は GET でしか動作しないなど、一部制約があります。

って書いてあったけど、POSTでもリクエストが1回だな。
なぜだ。

HTMLGOHTMLGO

それぞれのパラメータを独立

    body: JSON.stringify({
      data: "data1",
    }),
    body: JSON.stringify({
      data: "data2",
    }),
    body: JSON.stringify({
      data: "data3",
    }),
    body: JSON.stringify({
      data: "data4",
    }),

値はそれぞれ独立したが、何回リロードしても変わらない。(それぞれのパラメータでキャッシュ)

APIも4回コールされてるが、そのあとは何度リロードしても変化なし。

HTMLGOHTMLGO

今度は、こんな感じ。
結果は皆さんお察しの通り。


    body: JSON.stringify({
      data: "data1",
    }),
    next: { revalidate: 2 },

    body: JSON.stringify({
      data: "data2",
    }),
    next: { revalidate: 4 },

    body: JSON.stringify({
      data: "data3",
    }),
    next: { revalidate: 6 },

    body: JSON.stringify({
      data: "data4",
    }),
    next: { revalidate: 8 },
  • それぞれ、パラメータが違うので、独立したurlとしてキャッシュされる
  • なので、それぞれ、2秒、4秒、6秒、8秒で更新される。

最初は、4つ呼ばれてるが、
そのあとは、data1が一番短い間隔で、data4 が一番長い間隔で呼ばれている

HTMLGOHTMLGO

最後。

パラメータを一緒にするが、
revalidateは分ける。

   body: JSON.stringify({
      data: "data100",
    }),
    next: { revalidate: 2 },

    body: JSON.stringify({
      data: "data100",
    }),
    next: { revalidate: 4 },

    body: JSON.stringify({
      data: "data100",
    }),
    next: { revalidate: 6 },

    body: JSON.stringify({
      data: "data100",
    }),
    next: { revalidate: 8 },

同じ値でキャッシュされる。
リロードしまくったら、2秒間隔で更新された。
APIも2秒に1回叩かれた。
つまり、revalidateは一番短いものが優先される。

この辺はGETとPOST で変わらないみたい。

HTMLGOHTMLGO

自分なりの結論

  • fetch を使えば、GETでもPOSTでもキャッシュを使ってAPIへの接続を減らせる。
  • 同じURL(パラメータ)毎にキャッシュは生成される(当たり前)
  • しかもそのキャッシュは意図的にrevalidateしないと消えない。
  • 例えば revalidateの設定が無い状態で、(A).api/hoge?v=1 → (B).api/hoge?v=2 → (C).api/hoge?v=1 とすると(C)の時には(A)のキャッシュが再利用されるっぽい。

今回思ったのは、

API毎にfetchにキャッシュさせないか、時間単位でキャッシュさせる設定を指定する
のがベストだと思います。

next: { revalidate: 0 }, // キャッシュさせない

or

next: { revalidate: 10 }, // 10秒キャッシュさせる

こうする事で、他の設定に上書きされることはないからです。
(同じURLのfetchが他に存在しない限り、、)

設定が難解すぎるので、明示的に設定しといた方がいいかと。。

このスクラップは2024/02/17にクローズされました