Back

Nuxt 中的服务端数据获取

Nuxt 中的服务端数据获取

如果你正在构建一个 Nuxt 应用,并且疑惑为什么数据会被获取两次——或者为什么共享相同 key 的组件会出现意外行为——你并不孤单。Nuxt SSR 数据获取有一些特定的规则,即使是经验丰富的开发者也容易踩坑。

本文将解释 useAsyncDatauseFetchNuxt 4 中的工作原理,涵盖 payload 水合(hydration)、导航行为、key 管理,以及最容易引起混淆的陷阱。

核心要点

  • Nuxt 在服务端运行 useFetchuseAsyncData,将响应序列化到 HTML payload 中,并在客户端进行水合而无需重新获取
  • 共享相同 key 的组件会共享相同的响应式状态——为独立的数据实例使用不同的 key
  • 在当前的 Nuxt 4 版本中,某些选项(handler、transform、pick、getCachedData、default、deep)在共享 key 的调用之间必须匹配
  • 使用 useFetchuseAsyncData 进行 SSR 安全的数据获取;将 $fetch 保留给事件处理器和仅客户端代码

Nuxt 如何执行服务端数据获取

当你在页面或组件中调用 useFetchuseAsyncData 时,Nuxt 会在初始请求期间在服务端运行该获取操作。服务器将响应序列化为嵌入在 HTML 中的 payload。当客户端进行水合时,它会读取这个 payload 而不是重新获取——从而消除重复的网络请求。

const { data } = await useFetch('/api/products')

这一行代码在服务端执行,将结果嵌入页面,并在客户端无缝水合。没有双重获取,没有水合不匹配。

阻塞式获取 vs. 懒加载获取

默认情况下,Nuxt 会阻塞导航,直到 awaited 的数据获取完成。这确保你的页面在渲染时数据已经可用。

对于客户端导航,你可以选择懒加载获取:

const { data, status } = useLazyFetch('/api/comments')

使用懒加载获取时,导航默认不会阻塞,获取操作在后台运行。你需要在模板中使用 status ref 来处理加载状态。

理解 Nuxt 数据获取的 Key

Key 是 Nuxt 缓存和去重请求的核心机制。每个 useFetch 调用使用 URL 作为其默认 key。对于 useAsyncData,你需要显式提供 key,或让 Nuxt 为你生成一个确定性的 key。

这里是关键部分:共享相同 key 的组件会共享相同的状态。这包括 dataerrorstatuspending refs。

// 组件 A
const { data } = await useAsyncData('users', () => $fetch('/api/users'))

// 组件 B - 与组件 A 共享状态
const { data } = await useAsyncData('users', () => $fetch('/api/users'))

两个组件接收相同的响应式 refs。改变一个,另一个也会反映出来。

Key 一致性规则

在当前的 Nuxt 4 版本中,当多个调用共享一个 key 时,Nuxt 会强制某些选项保持一致。这些选项必须匹配:

  • Handler 函数
  • transform 函数
  • pick 数组
  • getCachedData 函数
  • default
  • deep 选项

这些选项可以安全地不同:

  • server
  • lazy
  • immediate
  • dedupe
  • watch

违反一致性会触发开发警告和不可预测的行为。

安全的 Key 策略

对于路由特定的数据,在 key 中包含路由参数:

const route = useRoute()
const { data } = await useAsyncData(
  `product-${route.params.id}`,
  () => $fetch(`/api/products/${route.params.id}`)
)

对于不应共享状态的独立实例,使用不同的 key:

const { data: sidebar } = await useAsyncData('users-sidebar', fetchUsers)
const { data: main } = await useAsyncData('users-main', fetchUsers)

Nuxt 数据缓存和去重行为

Nuxt 会自动去重具有匹配 key 的并发请求。如果三个组件同时请求相同的 key,只会触发一个网络请求。

dedupe 选项控制刷新行为:

const { data, refresh } = await useFetch('/api/data', {
  dedupe: 'cancel' // 在启动新请求之前取消待处理的请求
})

在 Nuxt 4.2 及更高版本中,取消支持得到了显著改进。当支持时,在快速导航期间,来自先前路由的过时响应可以被取消或忽略,减少过时数据短暂出现的风险。

更多详情:https://nuxt.com/docs/api/composables/use-fetch

常见陷阱

混淆 Nuxt 的 useFetch 与其他库

Nuxt 的 useFetch 不同于 @vueuse/coreuseFetch 或类似工具。Nuxt 的版本会自动处理 SSR payload 水合。使用其他库的 useFetch 会完全绕过这一机制,导致双重获取和水合不匹配。

在 Setup 中不使用 useAsyncData 而直接使用 $fetch

<script setup> 中直接调用 $fetch 会在服务端和客户端都运行:

// ❌ 获取两次
const data = await $fetch('/api/users')

// ✅ 获取一次,正确水合
const { data } = await useFetch('/api/users')

$fetch 保留给事件处理器和仅客户端交互。

使用冲突选项复用 Key

这会触发警告和 bug:

// ❌ deep 选项冲突
await useAsyncData('users', fetchUsers, { deep: false })
await useAsyncData('users', fetchUsers, { deep: true })

在 server: false 时期望水合前有数据

当你设置 server: false 时,数据在水合完成之前会保持 null——即使你 await 了该组合式函数。

结论

Nuxt 4 的数据获取模型以服务端执行、payload 水合和基于 key 的缓存为中心。保持 key 稳定且每个数据源唯一。在组件之间共享 key 时确保选项一致性。使用 useFetchuseAsyncData 进行 SSR 安全的获取,将 $fetch 保留给客户端交互。

掌握这些模式,你就能避免困扰大多数 Nuxt 开发者的双重获取和状态共享 bug。

常见问题

这通常发生在你在 script setup 中直接使用 $fetch 而不是 useFetch 或 useAsyncData 时。直接的 $fetch 调用会在服务端和客户端都运行。将你的获取操作包装在 useFetch 或 useAsyncData 中以利用 payload 水合,这样只在服务端获取一次并在客户端复用该数据。

当直接从 URL 获取数据时使用 useFetch——它会根据 URL 自动处理 key。当你需要自定义逻辑、组合多个 API 调用或显式控制缓存 key 时使用 useAsyncData。两者都提供相同的 SSR 水合优势。

共享相同 key 的组件会共享相同的响应式状态。要保持数据独立,为每个组件使用唯一的 key。例如,当两个组件获取相同的端点但需要独立状态时,使用 users-sidebar 和 users-main 而不是只用 users。

dedupe 选项控制 Nuxt 如何处理多个刷新调用。将 dedupe 设置为 cancel 会在启动新请求之前中止任何待处理的请求。这有助于避免快速用户交互期间的竞态条件,并确保在支持取消时较新的响应优先。

Gain Debugging Superpowers

Unleash the power of session replay to reproduce bugs, track slowdowns and uncover frustrations in your app. Get complete visibility into your frontend with OpenReplay — the most advanced open-source session replay tool for developers. Check our GitHub repo and join the thousands of developers in our community.

OpenReplay