👩‍❤️‍👨

これってもしかして…cache されてる??!!

2023/12/30に公開

こんにちは。
southan(サウサン)です!

Next.jsとRust(actix-web)を使用した開発中に遭遇した問題点を解決するため、この投稿をしました!
皆さんの助けになれば幸いです。

先に結論

  • Next.jsのfetchはデフォルトでキャッシュされるよ!!
  • webブラウザのfetchはデフォルトでキャッシュされないよ!!

やったこと

会員登録画面を作成しました。

このプロセスは、登録画面、確認画面、そして完了画面という一般的な流れで構成されています。

今回の開発では、登録画面で入力されたデータをRustに渡し、セッションに保存します。確認画面への遷移時には、以下の手順を踏みます:

  1. Rust側でセッションからデータを取得
  2. そのデータをRustからNext.jsへ送信

確認画面の値は静的なデータなので、Next.js 13.4で利用できるようになったサーバーコンポーネントで実装を行なってます。

画面はこんな感じです。

データは試しで↓になってます。

name: '山田太郎'
email: 'test@test.com',
password: 'aaaaaaaa'

完了画面の処理を確認するため↑と同じデータで何回か送信ボタンを押下していたところ、どうも同じデータが返ってきてそうでした。

例えば初回Rust側の処理で

main.rs
pub struct UserData {
    pub email: String,
    pub name: String,
    pub password: String
}

(コード変更前)
#[post("/complete")]
async fn complete(session: Session, user_data: web::Json<UserData>) -> Result<HttpResponse, Error> {

    Ok(HttpResponse::Ok().json(&user_data))
}

のように書いてたとします。
ただ今回はemailだけでいいや!となって下記のように書き換えました。

main.rs
(コード変更後) 
#[post("/complete")]
async fn complete(session: Session, user_data: web::Json<UserData>) -> Result<HttpResponse, Error> {

    Ok(HttpResponse::Ok().json(&user_data.email))
}

コード変更前に送信ボタンを押して返ってくるデータは↓でした。

{ name: '山田太郎', email: 'test@test.com', password: 'aaaaaaaa' }

コード変更後ですが、emailのみ送信にしてるので

{  email: 'test@test.com'}

という値が返ってきて欲しいです。
ただ、実際に返ってくデータは先程と同様で

{ name: '山田太郎', email: 'test@test.com', password: 'aaaaaaaa' }

でした。

はにゃ????!!!!なんでや!!!!????

Rustが得意ではないので、Rustのコードが悪いのかなと思い他の開発したページを見てみましたが正常に動いてるのでそこが問題ではなさそうかなという結論に至りました。

一旦、フォーム上で別のデータで試そうと思い
データを下のよう書き換えました(名前のみ変えてます。スーパーサイヤ人2みたくなってますがそこはお気になさらず)

name: '山田太郎2'
email: 'test@test.com',
password: 'aaaaaaaa'

そして送信ボタンを送りました。
すると返ってくるデータは下記のようになってました。

test@test.com

あれ、正常にデータが返ってきてますね🤔
そこでさきほどのRustのコードを変更前に戻して試してみました。

main.rs
(変更前のコード)
#[post("/complete")]
async fn complete(session: Session, user_data: web::Json<UserData>) -> Result<HttpResponse, Error> {

    Ok(HttpResponse::Ok().json(&user_data))
}

これだと返ってくるデータは

{ name: '山田太郎', email: 'test@test.com', password: 'aaaaaaaa' }

が正しくなります。
ただ、実際に返ってきたデータは先ほど同様

test@test.com

が返ってきました。

この時私の脳裏をよぎったのは

これってもしかして…cache されてる??!!

(記事のタイトルにつながるわけです)

そこで、Next.js側のコードを確認してみました。
最初に説明した通りサーバーコンポーネントを使っています。また送信ボタン押下後の処理はfetchを使い下記のように書いてます。

page.tsx
const complete = async (formData: FormData) => {
  'use server';

  // userの情報を取得
  const userData = {
    name: formData.get('name'),
    email: formData.get('email'),
    password: formData.get('password'),
  };

  const res = await fetch('完了画面のパス', {
    method: 'POST',
    headers: {
      'Content-Type': 'application/json',
    },
    body: JSON.stringify(userData),
  });

  const data = await res.json();
};

fetchのcache仕様について

Next.jsのfetch

ここで一番怪しいfetchの仕様を調べてみました。
https://nextjs.org/docs/app/api-reference/functions/fetch

そして一番最初の行にこのように書かれてます。

Next.js extends the native Web fetch() API to allow each request on the server to set its own persistent caching semantics.
Next.jsは、ネイティブのWeb fetch() APIを拡張して、サーバー上の各リクエストに独自の永続キャッシュ・セマンティクスを設定できるようにしました。

つまり既存のブラウザで稼働するfetchを拡張して、キャッシュできるよう作られてるっぽいですね。

さらにその下にあるコードをみてみます。

app/page.tsx
export default async function Page() {
  // This request should be cached until manually invalidated.
  // Similar to `getStaticProps`.
  // `force-cache` is the default and can be omitted.
  const staticData = await fetch(`https://...`, { cache: 'force-cache' })
 
  // This request should be refetched on every request.
  // Similar to `getServerSideProps`.
  const dynamicData = await fetch(`https://...`, { cache: 'no-store' })
 
  // This request should be cached with a lifetime of 10 seconds.
  // Similar to `getStaticProps` with the `revalidate` option.
  const revalidatedData = await fetch(`https://...`, {
    next: { revalidate: 10 },
  })
 
  return <div>...</div>
}

特にみていただきたいのが

// `force-cache` is the default and can be omitted.
  const staticData = await fetch(`https://...`, { cache: 'force-cache' })

ビンゴ!!!!
fetchのcacheオプションはデフォルトでforce-cacheされてるみたいです。
(※やはり、値が同じ時返ってくるデータがcacheされてたんですね。)

なのでcacheされないようにするには下のようにcache: 'no-store'を適応させれば良さそうです。

     // This request should be refetched on every request.
  // Similar to `getServerSideProps`.
  const dynamicData = await fetch(`https://...`, { cache: 'no-store' })

そうして、自分のコードにもcache: 'no-store'を適応したところ正常にデータが反映されてました。

めでたしめでたし〜

ブラウザのfetchに関して

ネイティブのWeb fetch() APIはいったいどうなってるんでしょうか・・・??
気になった私は再度ジャングルに入っていくのだった。。。

下が公式のページです。
https://developer.mozilla.org/ja/docs/Web/API/Fetch_API/Using_Fetch

少しスクロールして具体的なコードが載ってました
そこにcacheに関する一行発見!!

cache: "no-cache", // *default, no-cache, reload, force-cache, only-if-cached

どうやらweb fetchはデフォルトでは"no-cache"みたいですね!!

まとめ

最後までお読みいただきありがとうございます。

  • Next.jsのfetchはデフォルトでキャッシュされるよ!!
    • cacheされたくなかったら'no-store' を使いましょう。
  • webブラウザのfetchはデフォルトでキャッシュされないよ!!

私自身これで一日くらい無駄にしたので、同じように悩んでいる人の救いになれば嬉しいです!!

Discussion