Nuxt4 进阶 API 与隐藏技巧

Nuxt4 除了 useFetchuseRoute 这些核心 API 外,还有大量"知道了就很有用、不知道就不会用"的进阶 API。callOnce 避免重复初始化、useRequestURL 拿到 SSR 安全的 URL、prerenderRoutes 动态注册预渲染路由、clearNuxtData 手动清缓存——这些 API 在特定场景下能解决棘手问题。本章按使用场景分类,讲解 30 个进阶 API 和隐藏技巧。

1. 执行控制

技巧 1:callOnce — 只执行一次

callOnce 确保一个函数在 SSR + CSR 的生命周期中只执行一次,即使组件被多次渲染。

<script setup lang="ts">
// 应用初始化逻辑(只在首次访问时执行,不会在每次导航时重复)
await callOnce('init-analytics', async () => {
  await $fetch('/api/analytics/init', { method: 'POST' })
})
</script>

典型场景

  • 初始化第三方 SDK(只需一次)
  • 记录首次访问日志
  • 全局数据预加载

技巧 2:callOnce 配合 mode 参数

// 只在服务端执行一次
await callOnce('server-init', () => initServerCache(), { mode: 'server' })
 
// 只在客户端执行一次
await callOnce('client-init', () => initClientSDK(), { mode: 'client' })
 
// 每次导航都执行(但不重复)
await callOnce('page-view', () => trackPageView(), { mode: 'navigation' })

技巧 3:useRequestURL — SSR 安全的 URL

// ❌ SSR 时 window.location 不存在
const url = window.location.href
 
// ✅ SSR/CSR 都能用
const url = useRequestURL()
console.log(url.pathname)  // /video/123
console.log(url.origin)    // https://example.com
console.log(url.searchParams.get('q'))  // 搜索关键词

场景:生成分享链接、获取当前 URL 参数、判断域名。

技巧 4:useRequestHeaders — 读取请求头

// 在组件或 composable 中获取请求头(SSR 时有值)
const headers = useRequestHeaders(['cookie', 'accept-language'])
const lang = headers['accept-language']?.split(',')[0]

场景:服务端渲染时需要根据 Accept-Language 决定语言。

2. 数据管理

技巧 5:refreshNuxtData — 刷新所有数据

// 刷新所有 useAsyncData / useFetch 的数据
await refreshNuxtData()
 
// 只刷新指定 key 的数据
await refreshNuxtData('video-list')
 
// 使用场景:用户操作后需要刷新列表
async function handleDelete(id: string) {
  await $fetch(`/api/videos/${id}`, { method: 'DELETE' })
  await refreshNuxtData('video-list')
}

技巧 6:clearNuxtData — 手动清缓存

// 清除指定 key 的缓存数据
clearNuxtData('video-list')
 
// 清除匹配的所有缓存
clearNuxtData((key) => key.startsWith('video-'))
 
// 场景:用户登出时清除所有用户相关的缓存
function handleLogout() {
  clearNuxtData((key) => key.startsWith('user-'))
  navigateTo('/login')
}

技巧 7:useNuxtData — 跨组件读缓存

// 页面 A 加载了数据
const { data } = await useFetch('/api/videos', { key: 'videos' })
 
// 页面 B 中直接读取缓存(不发请求)
const { data: cachedVideos } = useNuxtData('videos')
// 如果缓存为空,说明用户直接访问了页面 B

技巧 8:useState 高级用法

// 跨请求隔离的全局状态(SSR 安全)
const useGlobalLoading = () => useState('global-loading', () => false)
 
// 初始化函数只执行一次(SSR 时服务端执行,结果传给客户端)
const useCurrentUser = () => useState<User | null>('current-user', () => null)
 
// 共享到服务端和客户端
const useLocale = () => useState('locale', () => 'zh-CN')

3. 路由与导航

技巧 9:prerenderRoutes — 动态注册预渲染

<script setup lang="ts">
// 在页面中声明需要预渲染的路由
prerenderRoutes(['/about', '/contact', '/faq'])
</script>
// 或在 API 路由中动态生成
// server/api/__sitemap.ts
export default defineEventHandler(async () => {
  const videos = await db.select({ id: videos.id }).from(videosTable)
  return videos.map(v => `/video/${v.id}`)
})

场景nuxi generate 静态生成时,让 Nuxt 知道有哪些动态路由需要预渲染。

技巧 10:useRouter 编程式导航进阶

const router = useRouter()
 
// 替换当前路由(不加入历史记录)
router.replace({ query: { page: 2 } })
 
// 导航前钩子
router.beforeEach((to, from) => {
  if (to.meta.requiresAuth && !isLoggedIn.value) {
    return navigateTo('/login')
  }
})
 
// 导航后钩子(适合记录页面浏览)
router.afterEach((to) => {
  trackPageView(to.fullPath)
})

技巧 11:NuxtRouteAnnouncer — 无障碍路由通知

<!-- app.vue -->
<template>
  <NuxtRouteAnnouncer />
  <NuxtLayout>
    <NuxtPage />
  </NuxtLayout>
</template>

当页面切换时,屏幕阅读器会读出新页面的标题。对视障用户友好。

技巧 12:definePageMeta 高级选项

<script setup lang="ts">
definePageMeta({
  // 路由中间件
  middleware: ['auth', 'admin'],
 
  // 布局
  layout: 'admin',
 
  // 页面切换动画
  pageTransition: { name: 'slide-left', mode: 'out-in' },
 
  // 自定义 key(控制组件复用)
  key: route => route.fullPath,
 
  // 路由验证(不匹配时返回 404)
  validate: async (route) => {
    return /^\d+$/.test(route.params.id as string)
  },
 
  // 路由别名
  alias: ['/v/:id'],
})
</script>

4. 预览与调试

技巧 13:usePreviewMode — 预览未发布内容

const { enabled, state } = usePreviewMode()
 
// 在 API 路由中根据预览模式返回草稿
export default defineEventHandler(async (event) => {
  const preview = getQuery(event).preview === 'true'
  const where = preview ? {} : { status: 'published' }
  return db.select().from(videos).where(where)
})

场景:CMS 系统中,编辑者可以预览未发布的文章/视频。

技巧 14:showError / clearError

// 手动触发全屏错误页
showError({
  statusCode: 403,
  message: '你没有权限访问此页面',
})
 
// 在错误页面清除错误(返回正常页面)
clearError({ redirect: '/' })

技巧 15:useError — 读取当前错误

<!-- error.vue -->
<script setup lang="ts">
const error = useError()
</script>
 
<template>
  <div class="error-page">
    <h1>{{ error?.statusCode }}</h1>
    <p>{{ error?.message }}</p>
    <button @click="clearError({ redirect: '/' })">返回首页</button>
  </div>
</template>

5. 渲染控制

技巧 16:useHydration — 自定义序列化

// 在 SSR 时序列化自定义数据到 payload,客户端 hydration 时恢复
useHydration('my-data', () => {
  // SSR 时序列化
  return { timestamp: Date.now(), config: getConfig() }
}, (data) => {
  // CSR hydration 时恢复
  Object.assign(myStore, data)
})

技巧 17:defineRouteRules — 页面级渲染策略

<script setup lang="ts">
defineRouteRules({
  // 此页面使用 ISR(增量静态再生)
  swr: 60 * 10,  // 10 分钟缓存
})
</script>
// 或在 nuxt.config.ts 中统一配置
export default defineNuxtConfig({
  routeRules: {
    '/': { prerender: true },           // 首页静态生成
    '/video/**': { swr: 600 },          // 视频页 ISR
    '/admin/**': { ssr: false },        // 管理后台 SPA
    '/api/**': { cors: true },          // API 允许跨域
  },
})

技巧 18:NuxtIsland — 服务端组件

<!-- 父组件(客户端) -->
<template>
  <NuxtIsland name="HeavyChart" :props="{ data: chartData }" />
</template>
 
<!-- components/islands/HeavyChart.vue(只在服务端渲染) -->
<script setup lang="ts">
const props = defineProps<{ data: ChartData }>()
// 复杂计算在服务端完成,只发送 HTML 到客户端
const result = heavyComputation(props.data)
</script>

场景:计算密集型组件在服务端渲染,不增加客户端 JS 体积。

6. 插件与模块

技巧 19:defineNuxtPlugin 提供全局函数

// plugins/toast.ts
export default defineNuxtPlugin(() => {
  return {
    provide: {
      toast: (message: string, type: 'success' | 'error' = 'success') => {
        // toast 逻辑
      },
    },
  }
})
 
// 使用
const { $toast } = useNuxtApp()
$toast('操作成功', 'success')

技巧 20:插件依赖排序

// plugins/01.auth.ts(数字前缀控制顺序)
export default defineNuxtPlugin({
  name: 'auth',
  dependsOn: ['session'],  // 显式声明依赖
  setup() {
    // 确保 session 插件先初始化
  },
})

技巧 21:defineNuxtModule — 创建项目内部模块

// modules/auto-import-icons.ts
export default defineNuxtModule({
  meta: { name: 'auto-import-icons' },
  setup(options, nuxt) {
    nuxt.hook('components:dirs', (dirs) => {
      dirs.push({
        path: resolve('./assets/icons'),
        prefix: 'Icon',
      })
    })
  },
})

7. Server 端进阶

技巧 22:server/middleware 全局钩子

// server/middleware/log.ts(所有请求都会经过)
export default defineEventHandler((event) => {
  const start = Date.now()
  event.node.res.on('finish', () => {
    const duration = Date.now() - start
    console.log(`${event.method} ${event.path} ${duration}ms`)
  })
})

技巧 23:server/plugins — 服务端生命周期

// server/plugins/db.ts
export default defineNitroPlugin((nitro) => {
  // 服务器启动时
  nitro.hooks.hook('request', (event) => {
    // 每个请求之前
  })
 
  nitro.hooks.hook('close', () => {
    // 服务器关闭时(清理数据库连接等)
  })
})

技巧 24:服务端存储 useStorage

// 使用 Nitro 内置的 KV 存储(内存/Redis/文件系统)
export default defineEventHandler(async () => {
  const storage = useStorage('cache')
 
  // 读取
  const cached = await storage.getItem('popular-tags')
  if (cached) return cached
 
  // 写入
  const tags = await db.select().from(tagsTable).limit(20)
  await storage.setItem('popular-tags', tags, { ttl: 300 })
  return tags
})

8. 构建与部署

技巧 25:nuxt.config.ts 条件配置

export default defineNuxtConfig({
  $production: {
    devtools: { enabled: false },
  },
  $development: {
    devtools: { enabled: true },
  },
  $test: {
    // 测试环境特定配置
  },
})

技巧 26:extends 继承配置

// nuxt.config.ts — 继承基础配置
export default defineNuxtConfig({
  extends: ['./base-layer'],  // 继承 base-layer 的组件、composables、pages
})

场景:多个项目共享同一套组件库和配置。

技巧 27:app:manifests 自动更新

export default defineNuxtConfig({
  experimental: {
    appManifest: true,  // 启用后,SPA 导航时检查新版本
  },
})

客户端导航时,Nuxt 检查 manifest 是否有更新。有新版本时,下次整页刷新会加载新代码。

技巧 28:useAppConfig + app.config.ts

// app.config.ts(支持 HMR,不需要重启)
export default defineAppConfig({
  theme: { primary: '#3b82f6' },
  features: { comments: true, likes: true },
})
 
// 使用
const config = useAppConfig()
if (config.features.comments) {
  // 显示评论功能
}

技巧 29:自定义 Nitro preset

export default defineNuxtConfig({
  nitro: {
    preset: 'node-server',   // 默认 Node.js
    // preset: 'cloudflare-pages',  // Cloudflare
    // preset: 'vercel-edge',       // Vercel Edge
    // preset: 'bun',               // Bun runtime
  },
})

技巧 30:useRuntimeConfig 的类型增强

// 在 nuxt.config.ts 中声明
export default defineNuxtConfig({
  runtimeConfig: {
    secretKey: '',
    public: { apiBase: '' },
  },
})
 
// 自动生成类型到 .nuxt/types/
// 使用时完整类型提示:
const config = useRuntimeConfig()
config.secretKey      // string(服务端)
config.public.apiBase // string(客户端+服务端)

本章小结

30 个进阶技巧按场景分类:

  • 执行控制(1-4):callOnce 避免重复、useRequestURL SSR 安全 URL、useRequestHeaders 读取请求头
  • 数据管理(5-8):refreshNuxtData 刷新、clearNuxtData 清缓存、useNuxtData 跨组件读取、useState 全局状态
  • 路由导航(9-12):prerenderRoutes 动态预渲染、NuxtRouteAnnouncer 无障碍、definePageMeta 高级选项
  • 预览调试(13-15):usePreviewMode 草稿预览、showError/clearError 错误控制
  • 渲染控制(16-18):useHydration 自定义序列化、routeRules 页面级策略、NuxtIsland 服务端组件
  • 插件模块(19-21):provide 全局函数、插件依赖排序、defineNuxtModule 内部模块
  • Server 端(22-24):全局中间件、服务端插件、useStorage KV 缓存
  • 构建部署(25-30):条件配置、extends 继承、appManifest 自动更新、Nitro preset