Nuxt 4 useAsyncData キャッシュの仕組み

purgeCachedData / getCachedData / 依存カウンターによるライフサイクル管理

// 01 — 全体構造
useAsyncData(key, handler)

コンポーネントの setup 内で呼び出し。
内部でキーごとのシングルトンインスタンスを取得または作成する。

Singleton Instance(キーごとに1つ)

data / error / status / pending の ref を保持。
同じキーの全呼び出しで共有される。

依存カウンター

マウント → +1 、アンマウント → -1
カウントが 0 になると nextTick 後にクリーンアップ判定。

getCachedData(key, nuxtApp, ctx)

データ取得時に毎回呼ばれる。
undefined を返すと fetch 実行。値を返すとキャッシュ利用。
ctx.cause で呼び出し理由を判別可能。

purgeCachedData

カウント 0 + nextTick 後に _init が false のままなら、キャッシュを削除。
メモリリーク防止。

// 02 — ページ遷移のタイムライン

ページA: my-data + different-endpoint → ページB: my-data のみ

STEP 1
ページA 初回アクセス
2つの useAsyncData が実行され、それぞれ fetch が走りキャッシュが作られる。
🔑 my-data
カウント: 0 1 FETCH
🔑 different-endpoint
カウント: 0 1 FETCH
STEP 2
ページB のコンポーネントがマウント(setup 実行)
ページBの useAsyncData('my-data', ...) が実行される。
既存のシングルトンインスタンスが見つかるので、カウントが増加。
🔑 my-data
カウント: 1 2 ALIVE
🔑 different-endpoint
カウント: 1 (変化なし)
STEP 3
ページA のコンポーネントがアンマウント
ページAの2つの useAsyncData がそれぞれ解除される。
different-endpoint のカウントが 0 になり、_init = false にマークされる。
🔑 my-data
カウント: 2 1 ALIVE
🔑 different-endpoint
カウント: 1 0 ⏳ クリーンアップ予約
STEP 4
nextTick — クリーンアップ判定
nextTick のタイミングで、カウント 0 のキーについて _init フラグを確認する。
🔑 my-data
カウント: 1 判定スキップ(カウント > 0) ALIVE
🔑 different-endpoint
_init = false PURGED
STEP 5
ページB での my-data の扱い
シングルトンインスタンスは生きているが、実際にキャッシュデータを使うかは getCachedData の戻り値次第。
?
getCachedData が呼ばれる。デフォルト実装はハイドレーション時のみキャッシュを返す。
×
SPA遷移(NuxtLink)の場合 → デフォルトでは undefined を返す → 再 fetch
カスタム getCachedData で payload.data[key] を返す設定にしていれば → キャッシュ利用
// 03 — getCachedData の判定フロー
getCachedData(key, nuxtApp, ctx) が呼ばれる

Nuxt 4 では初回・watch・refresh:manual・refresh:hook のすべてで呼ばれる

↓ ctx.cause を確認
ctx.cause = 'initial'

初回マウント時。SSRハイドレーションやプリレンダリング済みデータがあればキャッシュを返せる。

ctx.cause = 'watch'

watch 対象が変化した時。Nuxt 3 では常に再 fetch だったが、Nuxt 4 では getCachedData が先に評価される。

ctx.cause = 'refresh:manual'

refresh() を手動で呼んだ時。通常はキャッシュをバイパスして再取得したいケース。

ctx.cause = 'refresh:hook'

refreshNuxtData() から呼ばれた時。アプリ全体のデータ更新。

↓ 戻り値で分岐
値を返した → キャッシュ利用

fetch は実行されない。返された値が data.value にセットされる。

undefined を返した → fetch 実行

handler 関数が呼ばれ、新しいデータを取得する。

// 実用的な getCachedData の実装例
const { data } = await useAsyncData('my-data', () => $fetch('/api/data'), {
  getCachedData: (key, nuxtApp, ctx) => {
    // 手動リフレッシュ時は常に再取得
    if (ctx.cause === 'refresh:manual') return undefined

    // それ以外はキャッシュがあれば使う
    return nuxtApp.payload.data[key] ?? nuxtApp.static.data[key]
  }
})
// 04 — Nuxt 3 vs Nuxt 4 の違い

Nuxt 3

• 同じキーでも個別にインスタンス作成
• キャッシュはアンマウント後も永続
• getCachedData はハイドレーション時のみ
• data は深い ref(全プロパティがリアクティブ)
• メモリが蓄積し続ける

Nuxt 4

• 同じキーはシングルトンで共有
• カウント 0 + nextTick でキャッシュ削除
• getCachedData は全 fetch 時に呼ばれる
• data は shallowRef(パフォーマンス向上)
• 使われなくなったデータは自動解放

⚠ 注意: useNuxtData を useAsyncData より前に呼ぶと、依存カウントの管理に不具合が生じる既知のバグがあります(Nuxt 3.17.x)。必ず useAsyncData → useNuxtData の順で呼んでください。