缓存与刷新
了解 Nuxt 数据获取的缓存机制和刷新策略。掌握缓存,就能避免重复请求、提升页面加载速度;掌握刷新,就能在数据变化时及时更新。
Key 机制——缓存的基础
useFetch 和 useAsyncData 通过 key 来管理数据缓存。Key 是数据的唯一标识。
// 自动生成 key(useFetch 根据URL + 选项哈希生成)
const { data } = await useFetch('/api/users')
// key 自动为类似 '/api/users::6h2k3' 的哈希值
// 手动指定 key(useAsyncData 必须手动指定)
const { data } = await useAsyncData('my-users', () => $fetch('/api/users'))
// useFetch 也可以手动指定 key
const { data } = await useFetch('/api/users', {
key: 'my-users', // 覆盖自动生成的 key
})Key 的生成规则
useFetch:自动基于 URL + 请求选项(method、query、body 等)生成唯一 keyuseAsyncData:必须手动提供 key- 如果两个
useFetch的 URL 和选项完全相同,它们的 key 也相同,会自动去重
Key 的三大作用
| 作用 | 说明 | 示例 |
|---|---|---|
| 去重 | 相同 key 的请求只执行一次 | 多个组件请求同一 API |
| SSR 传递 | 服务端数据通过 key 传递给客户端 | nuxtApp.payload.data[key] |
| 缓存访问 | 通过 key 获取已缓存的数据 | useNuxtData(key) |
数据共享——多个组件使用相同数据
多个组件使用相同 key 时,只有第一个会发请求,后续的直接使用缓存:
// 组件 A:首次请求
const { data } = await useFetch('/api/config')
// 发起请求,获取数据
// 组件 B(同页面中)
const { data } = useNuxtData('/api/config')
// 直接使用缓存,不再请求useNuxtData
通过 key 获取已缓存的数据引用,返回 { data, pending, error }。如果 key 对应的数据不存在,data 为 undefined。
典型场景
根组件获取全局配置,子组件通过 useNuxtData 直接使用:
// app.vue — 根组件获取配置
const { data: config } = await useFetch('/api/config', { key: 'app-config' })
// 任意子组件 — 直接使用
const { data: config } = useNuxtData('app-config')refresh / execute —— 手动刷新数据
const { data, refresh, execute } = await useFetch('/api/users')
// refresh 和 execute 功能完全相同
await refresh()
await execute()refresh vs execute
两者完全等价,是同一个函数的别名。refresh 语义更明确,execute 在 immediate: false 场景下更自然。
带选项刷新
// 强制刷新(忽略去重)
await refresh({ dedupe: false })
// 设置超时
await refresh({ timeout: 5000 })refresh({ dedupe: false })
默认情况下,如果在短时间内多次调用 refresh(),Nuxt 可能会跳过重复请求。dedupe: false 强制每次都发起新请求。
何时需要手动刷新?
| 场景 | 示例 |
|---|---|
| 用户执行了操作后 | 删除文章后刷新列表 |
| 用户点击"刷新"按钮 | 手动刷新页面数据 |
| 轮询更新数据 | 定时刷新实时数据 |
| 表单提交成功后 | 提交评论后刷新评论区 |
// 删除文章后刷新列表
async function deletePost(id: number) {
await $fetch(`/api/posts/${id}`, { method: 'DELETE' })
await refresh() // 刷新文章列表
}clearNuxtData —— 清除缓存
清除指定 key 的缓存数据,将 data 重置为默认值:
// 清除单个
clearNuxtData('/api/users')
// 清除多个
clearNuxtData(['users', 'posts'])
// 清除所有
clearNuxtData()clearNuxtData 的完整行为
- 将
dataref 重置为default值(有default时)或undefined - 将
errorref 重置为undefined - 将
status重置为'idle' - 从
nuxtApp.payload.data中删除该 key 的数据
Nuxt 4.4+ 的改进
Nuxt 4.4+ 中,clearNuxtData 会将 data 重置为 default 值而非 undefined:
const { data } = await useFetch('/api/count', {
default: () => 0,
})
clearNuxtData('/api/count')
// Nuxt 4.4+:data 重置为 0(default 值)
// Nuxt 4.3-:data 重置为 undefinedrefreshNuxtData —— 刷新指定数据
从任何地方刷新指定 key 的数据(不需要访问组件的 refresh 函数):
// 刷新单个
await refreshNuxtData('/api/users')
// 刷新多个
await refreshNuxtData(['users', 'posts'])
// 刷新所有
await refreshNuxtData()refreshNuxtData vs refresh
refresh():组件内的方法,只能刷新当前组件的数据refreshNuxtData(key):全局方法,可以在任何地方刷新指定 key 的数据
典型场景
在 A 页面修改了数据,需要刷新 B 页面的缓存:
// 在设置页修改了用户信息
async function updateProfile() {
await $fetch('/api/profile', { method: 'PUT', body: formData })
// 刷新其他页面的用户信息缓存
await refreshNuxtData('/api/profile')
}refreshCookie —— 刷新 Cookie
当服务端修改了 cookie 后,客户端的 useCookie 值可能不同步。refreshCookie 可以手动刷新:
const token = useCookie('auth-token')
// 从浏览器 cookie 中刷新 token 的值
refreshCookie('auth-token')
// 通常在登录/登出后使用
async function logout() {
await $fetch('/api/logout', { method: 'POST' })
refreshCookie('auth-token') // 刷新 cookie 值
navigateTo('/login')
}何时需要 refreshCookie?
在服务端 API 中通过 setCookie 或 deleteCookie 修改了 cookie 后,客户端的 useCookie 返回的 ref 值不会自动更新。调用 refreshCookie 可以从浏览器中读取最新值。
useNuxtData —— 访问缓存数据
通过 key 获取已缓存的数据引用:
// 在组件 A 中获取数据
const { data } = await useFetch('/api/config', { key: 'app-config' })
// 在组件 B 中直接访问(不再请求)
const { data: config } = useNuxtData('app-config')INFO
️ useNuxtData 返回的是 ref:在 <script> 中要用 .value 在 <template> 中自动解包
如果 key 对应的数据不存在
data 为 undefined。确保在使用 useNuxtData 之前,已有组件通过 useFetch 或 useAsyncData 获取了该 key 的数据。
服务端数据传递——SSR 缓存流程
SSR 时,服务端获取的数据通过 Payload 传递给客户端,避免重复请求:
1. 服务端执行 useFetch → 获取数据,按 key 缓存到 nuxtApp.payload.data
2. 数据序列化到 HTML 中的 __NUXT__ 对象
3. 客户端 Hydration → 从 Payload 读取数据(不再请求)
4. 后续客户端导航 → 在客户端重新请求为什么客户端导航需要重新请求?
Payload 数据只在 Hydration(首次加载)时使用。当用户从 A 页面导航到 B 页面时,B 页面的数据需要重新获取。但 B 页面如果之前访问过,可能有 static.data 缓存。
Payload 提取
将 Payload 提取为单独的 JSON 文件,提高缓存效率:
export default defineNuxtConfig({
experimental: {
payloadExtraction: true, // 生成 _payload.json
// payloadExtraction: 'client', // v4.4+ 内联 Payload + 生成文件
},
})payloadExtraction: 'client'(v4.4+)
export default defineNuxtConfig({
experimental: {
payloadExtraction: 'client',
},
})| 模式 | HTML 中的 Payload | _payload.json | 适用场景 |
|---|---|---|---|
true | 不内联,引用外部文件 | ✅ 生成 | 纯静态站点 |
'client'(v4.4+) | 内联完整 Payload | ✅ 生成 | 通用(推荐) |
false | 内联 | ❌ 不生成 | 无 CDN 缓存需求 |
payloadExtraction: 'client' 的优势
- 初始 HTML 中内联完整 Payload → 首屏更快(不需要额外请求)
- 同时生成
_payload.json→ 客户端导航时可以缓存利用 - 运行时 LRU 缓存 → 避免重复渲染
缓存策略实战
策略 1:Hydration 时用 Payload,后续用静态缓存
const nuxtApp = useNuxtApp()
const { data } = await useFetch('/api/data', {
getCachedData(key) {
if (nuxtApp.isHydrating) {
// Hydration 时:使用 Payload 数据
return nuxtApp.payload.data[key]
}
// 客户端导航时:使用静态缓存
return nuxtApp.static.data[key]
},
})策略 2:基于时间的缓存
const ONE_HOUR = 60 * 60 * 1000
const { data } = await useFetch('/api/articles', {
getCachedData(key, nuxtApp) {
const cached = nuxtApp.static.data[key]
if (!cached) return undefined
// 检查缓存是否过期
const cachedData = nuxtApp.payload.data[key]
if (cachedData?.timestamp && Date.now() - cachedData.timestamp < ONE_HOUR) {
return cachedData
}
return undefined // 缓存过期,重新获取
},
})策略 3:只在首次加载时请求
const { data } = await useFetch('/api/config', {
getCachedData(key, nuxtApp) {
// 只要有缓存就用,永远不重新请求
return nuxtApp.payload.data[key] || nuxtApp.static.data[key]
},
})INFO
️ getCachedData 返回 undefined 才会重新请求 如果返回了任何值(包括 null),都不会重新请求
封装缓存组合式函数
// app/composables/useCachedFetch.ts
export function useCachedFetch<T>(url: string, options: Record<string, any> = {}) {
const nuxtApp = useNuxtApp()
return useFetch<T>(url, {
...options,
key: options.key || url,
getCachedData(key) {
if (nuxtApp.isHydrating) {
// Hydration 时使用 Payload 数据
return nuxtApp.payload.data[key]
}
// 客户端导航时使用静态缓存
return nuxtApp.static.data[key]
},
})
}使用:
const { data } = await useCachedFetch('/api/articles')完整示例——带缓存的列表页
<script setup>
const page = ref(1)
const nuxtApp = useNuxtApp()
const { data, pending, refresh } = await useFetch('/api/articles', {
query: { page },
watch: [page],
key: computed(() => `articles-page-${page.value}`),
default: () => ({ items: [], total: 0 }),
getCachedData(key) {
if (nuxtApp.isHydrating) {
return nuxtApp.payload.data[key]
}
return nuxtApp.static.data[key]
},
})
// 删除文章后刷新当前页
async function deleteArticle(id: number) {
await $fetch(`/api/articles/${id}`, { method: 'DELETE' })
await refresh()
}
</script>
<template>
<div v-if="pending">加载中...</div>
<div v-else>
<ArticleCard v-for="article in data.items" :key="article.id" :article="article" />
<Pagination v-model="page" :total="data.total" />
</div>
</template>知识脉络
useFetch → useAsyncData → $fetch → 懒加载获取 → 数据获取选项
│
└─→ 你在这里:缓存与刷新
│
├─→ 下一步:SSR 数据传递(Payload 详解)
│
└─→ 相关:useFetch(key / dedupe 选项)