内置 Composables 全景梳理

Nuxt4 提供了 30+ 个内置 Composables,覆盖应用上下文、配置读取、路由导航、数据获取、错误处理、SSR 感知等方方面面。它们不是简单的工具函数——每一个都深度绑定了 Nuxt 的生命周期和 SSR 机制。本章从设计原理出发,梳理核心 Composables 的内部机制、适用场景和 SSR 陷阱。

1. Composables 的本质:为什么不是普通函数

1.1 Vue 组合式 API 的约束

Vue 的 Composables 必须在 setup()<script setup>同步调用,原因是它们依赖 Vue 内部的"当前组件实例"来注册生命周期钩子、注入/提供依赖。如果在异步回调或普通函数中调用,Vue 无法关联到正确的组件实例。

// ✅ 正确:在 setup 顶层调用
const route = useRoute()
const { data } = await useFetch('/api/videos')
 
// ❌ 错误:在异步回调中首次调用
setTimeout(() => {
  const route = useRoute()  // 可能找不到组件实例
}, 1000)

1.2 Nuxt Composables 的额外约束

Nuxt 的内置 Composables 在 Vue 的基础上多了一层——它们通过 useNuxtApp() 访问 Nuxt 应用实例。这意味着:

  • 必须在 Nuxt 上下文中调用:组件 setup、插件、路由中间件、Nuxt 生命周期钩子内
  • 不能在纯工具函数中调用(除非该函数也在上述上下文中被调用)
  • SSR 与 CSR 行为可能不同:部分 Composables 在服务端返回真实数据,在客户端返回 Payload 中的缓存

1.3 分类全景

类别Composables核心用途
应用上下文useNuxtApp, useRuntimeConfig, useAppConfig访问全局实例和配置
路由导航useRoute, useRouter, navigateTo, abortNavigation路由信息与编程导航
数据获取useFetch, useAsyncData, useLazyFetch, useLazyAsyncDataSSR 安全的数据请求
状态管理useState, useNuxtData, clearNuxtData跨组件/SSR 状态共享
SEOuseHead, useSeoMeta, useServerSeoMeta元数据管理
SSR 感知useRequestHeaders, useRequestEvent, useRequestURL读取服务端请求信息
错误处理useError, clearError, showError, createError全局错误管理
生命周期callOnce, onNuxtReady, onPrehydrateNuxt 特有生命周期
CookieuseCookieSSR 安全的 Cookie 读写
预加载preloadComponents, prefetchComponents, preloadRouteComponents资源预加载

2. useNuxtApp:应用上下文的入口

2.1 设计原理

useNuxtApp() 返回 Nuxt 应用的全局实例,它是所有其他 Composables 的基础——内部几乎都通过它来访问共享状态。这个实例在 SSR 阶段是请求级隔离的(每个请求一个实例),在客户端是全局单例

const nuxtApp = useNuxtApp()
 
// 核心属性
nuxtApp.vueApp        // Vue 应用实例
nuxtApp.ssrContext     // SSR 上下文(仅服务端)
nuxtApp.payload        // Payload 数据(SSR → 客户端的数据桥梁)
nuxtApp.isHydrating    // 是否正在 Hydration
nuxtApp.runWithContext  // 在 Nuxt 上下文中执行异步代码

2.2 payload:SSR 到客户端的数据桥梁

nuxtApp.payload 是 Nuxt SSR 机制的核心——服务端渲染时,所有通过 useFetchuseState 等获取的数据都会序列化到 Payload 中,随 HTML 发送给浏览器。客户端 Hydration 时直接读取 Payload,避免重复请求

理解这一点对调试至关重要:如果 Payload 中的数据和客户端预期不一致,就会出现 Hydration Mismatch

2.3 runWithContext:异步场景的救星

setTimeout、事件回调等异步代码中,Nuxt 上下文会丢失。runWithContext 可以手动恢复:

const nuxtApp = useNuxtApp()
 
// 在异步回调中安全使用 Composables
setTimeout(() => {
  nuxtApp.runWithContext(() => {
    const route = useRoute()  // ✅ 现在可以正常工作
    navigateTo('/dashboard')
  })
}, 1000)

原理runWithContext 内部会将当前 Nuxt 实例设置为 Vue 的 asyncLocalStorage(Node.js)或全局变量(浏览器),让后续的 Composable 调用能找到正确的上下文。

3. useRuntimeConfig vs useAppConfig

这两个 Composable 都用于"读取配置",但设计理念截然不同:

维度useRuntimeConfiguseAppConfig
定义位置nuxt.config.tsruntimeConfigapp.config.ts
是否可在运行时覆盖✅ 通过环境变量 NUXT_*❌ 构建时确定
是否序列化到 Payloadpublic 部分序列化✅ 完整序列化
适合存放API Key、数据库 URL 等环境相关配置主题色、站点名称等应用配置
是否响应式reactive 对象
服务端独有数据✅ 私有 key 仅服务端可见❌ 全部对客户端可见

关键区分:如果配置需要按环境变化(开发/测试/生产不同值),用 runtimeConfig;如果是应用级常量且需要响应式,用 appConfig

// app.config.ts — 响应式,可 HMR 热更新
export default defineAppConfig({
  ui: { primaryColor: '#3b82f6', borderRadius: '8px' },
  site: { name: 'AI 视频平台', logo: '/logo.svg' },
})
 
// 组件中读取(响应式,修改会触发重渲染)
const appConfig = useAppConfig()
appConfig.ui.primaryColor  // '#3b82f6'

4. SSR 感知 Composables

这组 Composables 只在服务端有数据,客户端返回空值。它们的存在意义是让服务端代码能读取 HTTP 请求信息。

4.1 useRequestHeaders

读取客户端请求的 HTTP 头,常见场景是将 Cookie 或 Authorization 传递给后端 API:

// 在 SSR 阶段,useFetch 不会自动携带浏览器 Cookie
// 需要手动转发
const headers = useRequestHeaders(['cookie', 'authorization'])
const { data } = await useFetch('/api/user/profile', { headers })

为什么需要手动转发? 浏览器请求页面时会携带 Cookie,但 SSR 阶段 Nuxt 向自己的 API 发起的 useFetch服务端内部调用(不经过浏览器),不会自动携带原始请求的 Cookie。这是 SSR 新手最常遇到的"用户未登录"问题。

4.2 useRequestEvent

获取底层 H3 Event 对象,可以做更细粒度的操作:

const event = useRequestEvent()
// 仅在服务端有值
if (event) {
  const ip = getRequestIP(event)
  const url = getRequestURL(event)
  setCookie(event, 'visited', 'true')
}

4.3 useRequestURL

获取当前请求的完整 URL,SSR 和 CSR 行为一致:

const url = useRequestURL()
// SSR: 从 H3 Event 解析
// CSR: 从 window.location 解析
console.log(url.hostname)  // 'example.com'
console.log(url.pathname)  // '/videos/123'

这个 Composable 比直接用 window.location 安全——它在 SSR 阶段不会报错。

5. useError 与错误处理体系

5.1 Nuxt 错误分类

Nuxt4 将错误分为两类:

  • 致命错误(Fatal):触发 error.vue 全屏错误页,通过 showError()createError({ fatal: true }) 触发
  • 非致命错误(Non-fatal):不中断页面渲染,通过 <NuxtErrorBoundary> 局部捕获

5.2 useError 读取全局错误

const error = useError()
// error.value 是当前的全局错误(如果有的话)
// 类型:{ statusCode, statusMessage, message, data }

5.3 错误处理策略

页面级错误:在页面组件中用 showError 抛出:

// pages/videos/[id].vue
const { data: video } = await useFetch(`/api/videos/${route.params.id}`)
if (!video.value) {
  showError({ statusCode: 404, statusMessage: '视频不存在' })
}

组件级错误:用 <NuxtErrorBoundary> 局部捕获,不影响整个页面:

<template>
  <NuxtErrorBoundary @error="handleError">
    <VideoPlayer :src="videoUrl" />
    <template #error="{ error, clearError }">
      <div class="error-fallback">
        <p>播放器加载失败:{{ error.message }}</p>
        <button @click="clearError">重试</button>
      </div>
    </template>
  </NuxtErrorBoundary>
</template>

6.1 设计原理

useCookie 是 Nuxt 最精巧的 Composable 之一——它在 SSR 阶段从请求头解析 Cookie,在客户端从 document.cookie 读取,返回一个响应式 Ref。修改它的值会自动设置 Set-Cookie 响应头(SSR)或更新 document.cookie(CSR)。

const token = useCookie('auth-token', {
  maxAge: 60 * 60 * 24 * 7,  // 7 天
  httpOnly: false,             // 客户端需要读取
  secure: true,
  sameSite: 'strict',
})
 
// 登录后设置
token.value = 'xxx'
 
// 退出时清除
token.value = null

6.2 SSR 与 Hydration 的协同

服务端设置 Cookie 后,值会通过 Payload 传给客户端。客户端 Hydration 时直接读取 Payload 中的值,而非立即解析 document.cookie。这避免了 Hydration Mismatch。

6.3 编码与序列化

useCookie 默认用 JSON 序列化复杂值。如果存储对象,需要注意大小限制(Cookie 单条上限 4KB)。大数据应存 useStatelocalStorage

7. callOnce 与 onNuxtReady

7.1 callOnce:只执行一次的逻辑

在 SSR 场景下,setup 代码会在服务端和客户端各执行一次。callOnce 确保逻辑只在首次渲染时执行:

// 只在首次访问时记录
await callOnce('analytics-init', async () => {
  await $fetch('/api/analytics/pageview', { method: 'POST' })
})

它通过 Payload 标记已执行状态——服务端执行后,客户端检查 Payload 发现已执行过,就会跳过。

7.2 onNuxtReady:Hydration 完成后

在客户端 Hydration 完全完成后执行,适合初始化不影响首屏渲染的逻辑:

onNuxtReady(() => {
  // Hydration 完成,可以安全操作 DOM 和启动非关键任务
  initAnalytics()
  registerServiceWorker()
  loadChatWidget()
})

8. 数据获取 Composables 速览

useFetchuseAsyncData 在第 14 章已经深入讲过,这里补充它们在 Composables 体系中的定位:

Composable底层特点
useFetch(url)useAsyncData + $fetch最常用,自动推导 key
useAsyncData(key, fn)基础版,需要手动指定 key 和数据源
useLazyFetchuseFetch(&#123; lazy: true &#125;)不阻塞导航,适合非关键数据
useLazyAsyncDatauseAsyncData(&#123; lazy: true &#125;)同上

关键机制:这四个 Composable 都会将数据存入 nuxtApp.payload,实现 SSR → CSR 的无缝数据传递。客户端导航时(SPA 模式),它们会直接发起客户端请求。

useNuxtData:读取缓存

useNuxtData(key) 可以读取 useFetch / useAsyncData 缓存的数据,无需重新请求:

// 页面 A 中请求了视频列表
const { data } = await useFetch('/api/videos', { key: 'videos' })
 
// 页面 B 中直接读取缓存(不发起请求)
const { data: cachedVideos } = useNuxtData('videos')

这是跨页面共享数据的轻量方案——不需要 Pinia,只要知道 key 即可。

本章小结

  • Composables 本质:依赖 Vue 组件实例和 Nuxt 应用上下文,必须在 setup 同步阶段调用
  • useNuxtApp:所有 Composables 的基础,payload 是 SSR 数据桥梁,runWithContext 解决异步上下文丢失
  • runtimeConfig vs appConfig:前者按环境变化(env 覆盖),后者是应用常量(响应式)
  • SSR 感知useRequestHeaders 转发 Cookie 解决 SSR 认证问题,useRequestEvent 访问 H3 底层
  • 错误处理:Fatal 错误触发 error.vue,非致命错误用 &lt;NuxtErrorBoundary&gt; 局部捕获
  • useCookie:SSR/CSR 统一的响应式 Cookie,值通过 Payload 同步避免 Hydration Mismatch
  • callOnce / onNuxtReady:控制代码在 SSR 生命周期中只执行一次或在 Hydration 后执行