Nuxt4 高频开发技巧 Top 30
本书前面的章节按模块讲解了 Nuxt4 的各个方面。但真实开发中,很多实用技巧散落在各处,不容易系统性地记住。本章把日常开发中最高频、最实用的 30 个技巧整理成速查手册——每个技巧都是"知道了就能立刻用,不知道就可能踩坑"的实战知识。按使用频率排序,前 10 个几乎每天都会用到。
1. Auto-import 与模块系统
技巧 1:不要手动 import Nuxt 内置 API
Nuxt 自动导入了 Vue 和 Nuxt 的核心 API,手动 import 不仅多余,还可能导致 SSR 问题。
<!-- ❌ 不要这样 -->
<script setup lang="ts">
import { ref, computed, watch, onMounted } from 'vue'
import { useFetch, useRoute, navigateTo } from '#imports'
</script>
<!-- ✅ 直接使用 -->
<script setup lang="ts">
const count = ref(0)
const double = computed(() => count.value * 2)
const route = useRoute()
const { data } = await useFetch('/api/videos')
</script>自动导入包括:ref、computed、watch、onMounted、useFetch、useRoute、useRouter、navigateTo、useState、useHead、useSeoMeta 等。
技巧 2:自定义 composable 自动导入
composables/ 目录下的文件自动导入,但有命名约定:
composables/
├── useVideo.ts ✅ 默认导出自动导入
├── useAuth.ts ✅ 默认导出自动导入
├── utils/ ❌ 子目录默认不导入
└── index.ts ✅ 命名导出自动导入
需要导入子目录时,在 nuxt.config.ts 中配置:
export default defineNuxtConfig({
imports: {
dirs: ['composables/**'],
},
})技巧 3:第三方库自动导入
export default defineNuxtConfig({
imports: {
presets: [
{ from: 'date-fns', imports: ['format', 'parseISO', 'differenceInDays'] },
{ from: 'lodash-es', imports: ['debounce', 'throttle', 'groupBy'] },
],
},
})技巧 4:检查哪些函数被自动导入
# 生成 .nuxt/types/imports.d.ts 查看所有自动导入
npx nuxi prepare
cat .nuxt/types/imports.d.ts技巧 5:组件自动导入与路径别名
components/ 目录下的组件自动按路径层级注册:
components/
├── VideoCard.vue → <VideoCard />
├── video/
│ ├── Player.vue → <VideoPlayer />
│ └── Controls.vue → <VideoControls />
└── admin/
└── Sidebar.vue → <AdminSidebar />
目录名自动变成组件名前缀。如果不想要前缀:
export default defineNuxtConfig({
components: [
{ path: '~/components/admin', prefix: '' },
],
})2. 数据获取
技巧 6:useFetch vs useAsyncData vs $fetch
| API | SSR | 去重 | 场景 |
|---|---|---|---|
useFetch | ✅ | ✅ | 页面/组件数据加载(最常用) |
useAsyncData | ✅ | ✅ | 需要自定义获取逻辑 |
$fetch | ❌ | ❌ | 事件处理中的 API 调用 |
<script setup lang="ts">
// 页面加载数据 → useFetch(SSR + 去重)
const { data: videos } = await useFetch('/api/videos')
// 按钮点击 → $fetch(无需 SSR)
async function handleLike(id: string) {
await $fetch(`/api/videos/${id}/like`, { method: 'POST' })
}
</script>技巧 7:useFetch 的 transform 减少传输量
const { data: titles } = await useFetch('/api/videos', {
transform: (response) => response.items.map(v => ({
id: v.id,
title: v.title,
})),
})
// SSR → CSR 传输的数据量大幅减少(payload 里只有 id + title)技巧 8:useFetch 的 watch 自动重新请求
<script setup lang="ts">
const page = ref(1)
const category = ref('all')
const { data } = await useFetch('/api/videos', {
query: { page, category },
watch: [page, category],
})
// page 或 category 变化时自动重新请求,无需手动 watch
</script>技巧 9:useAsyncData 的 getCachedData 缓存
const { data } = await useAsyncData('video-list', () => $fetch('/api/videos'), {
getCachedData: (key, nuxtApp) => {
return nuxtApp.payload.data[key] || nuxtApp.static.data[key]
},
})
// 页面导航回来时优先用缓存,不重新请求技巧 10:$fetch 在服务端直接调用无网络开销
// 服务端渲染时,$fetch('/api/videos') 不会发起 HTTP 请求
// 而是直接调用 server/api/videos.get.ts 的处理函数
// 零网络开销,就像函数调用一样快3. 组件技巧
技巧 11:.client.vue 和 .server.vue 后缀
components/
├── VideoPlayer.client.vue # 只在客户端渲染(浏览器 API 安全)
├── SkeletonCard.server.vue # 只在服务端渲染(SSR 占位)
└── VideoCard.vue # 同构渲染
.client.vue 组件在 SSR 时自动渲染为空,hydration 后加载。适合依赖浏览器 API 的组件(播放器、图表、地图)。
技巧 12:defineProps 的 TypeScript 泛型
<script setup lang="ts">
// ✅ TypeScript 泛型(推荐)
const props = defineProps<{
video: Video
showActions?: boolean
}>()
// ✅ 带默认值
const props = withDefaults(defineProps<{
video: Video
showActions?: boolean
}>(), {
showActions: true,
})
</script>技巧 13:v-model 多绑定
<!-- 父组件 -->
<VideoFilter v-model:category="category" v-model:sort="sortBy" />
<!-- 子组件 -->
<script setup lang="ts">
const category = defineModel<string>('category')
const sort = defineModel<string>('sort')
</script>技巧 14:useTemplateRef 替代字符串 ref
<script setup lang="ts">
// ✅ Nuxt4 / Vue 3.5+ 推荐
const videoEl = useTemplateRef<HTMLVideoElement>('video')
onMounted(() => {
videoEl.value?.play()
})
</script>
<template>
<video ref="video" />
</template>技巧 15:Teleport 与 ClientOnly 组合
<template>
<ClientOnly>
<Teleport to="body">
<div class="modal-overlay">
<VideoPreviewModal :video="video" @close="close" />
</div>
</Teleport>
</ClientOnly>
</template>Teleport 依赖 DOM,必须包在 ClientOnly 或 .client.vue 中。
4. Server API 技巧
技巧 16:文件名即路由方法
server/api/
├── videos/
│ ├── index.get.ts → GET /api/videos
│ ├── index.post.ts → POST /api/videos
│ ├── [id].get.ts → GET /api/videos/:id
│ ├── [id].put.ts → PUT /api/videos/:id
│ ├── [id].delete.ts → DELETE /api/videos/:id
│ └── [id]/
│ └── like.post.ts → POST /api/videos/:id/like
技巧 17:Zod 校验 + 类型推导
// server/api/videos/index.post.ts
import { z } from 'zod'
const schema = z.object({
title: z.string().min(1).max(100),
description: z.string().optional(),
categoryId: z.string().uuid(),
})
export default defineEventHandler(async (event) => {
const body = await readValidatedBody(event, schema.parse)
// body 自动推导为 { title: string, description?: string, categoryId: string }
})技巧 18:defineCachedEventHandler 服务端缓存
// server/api/tags/popular.get.ts
export default defineCachedEventHandler(async () => {
const tags = await db.select().from(tagsTable).orderBy(desc(tagsTable.count)).limit(20)
return tags
}, {
maxAge: 60 * 5, // 缓存 5 分钟
swr: true, // 过期后仍返回旧数据,后台刷新
name: 'popular-tags',
})技巧 19:server/utils 自动导入
server/utils/ 下的函数在 API 路由中自动可用:
// server/utils/auth.ts
export function requireAuth(event: H3Event) {
const session = await getUserSession(event)
if (!session.user) throw createError({ statusCode: 401, message: 'Unauthorized' })
return session.user
}
// server/api/videos/index.post.ts — 直接使用,无需 import
export default defineEventHandler(async (event) => {
const user = await requireAuth(event)
// ...
})技巧 20:API 路由的错误处理
export default defineEventHandler(async (event) => {
const id = getRouterParam(event, 'id')
const video = await db.query.videos.findFirst({
where: eq(videos.id, id!),
})
if (!video) {
throw createError({
statusCode: 404,
message: `Video ${id} not found`,
})
}
return video
})5. 配置与状态
技巧 21:useRuntimeConfig 环境变量
// nuxt.config.ts
export default defineNuxtConfig({
runtimeConfig: {
dbUrl: '', // 仅服务端(来自 NUXT_DB_URL)
klingApiKey: '', // 仅服务端(来自 NUXT_KLING_API_KEY)
public: {
appName: 'AI Video', // 客户端可用(来自 NUXT_PUBLIC_APP_NAME)
},
},
})// server 端
const config = useRuntimeConfig()
config.dbUrl // ✅ 可访问
// client 端
const config = useRuntimeConfig()
config.public.appName // ✅ 可访问
config.dbUrl // ❌ undefined(私有)技巧 22:useState 跨组件共享状态
// composables/useCounter.ts
export function useCounter() {
const count = useState('counter', () => 0) // SSR 安全
const increment = () => count.value++
return { count, increment }
}useState 比 ref 的区别:SSR 时数据会从服务端序列化到客户端,hydration 不会丢失状态。
技巧 23:app.config.ts 构建时常量
// app.config.ts(构建时确定,客户端可用,不需要环境变量)
export default defineAppConfig({
theme: {
primaryColor: '#3b82f6',
darkMode: true,
},
pagination: {
defaultPageSize: 20,
},
})
// 使用
const appConfig = useAppConfig()
appConfig.theme.primaryColorapp.config.ts vs runtimeConfig.public:前者是构建时常量(可以做 HMR),后者是运行时变量(可以通过环境变量覆盖)。
6. 路由与中间件
技巧 24:页面级中间件
<!-- pages/admin/dashboard.vue -->
<script setup lang="ts">
definePageMeta({
middleware: 'auth',
layout: 'admin',
})
</script>// middleware/auth.ts
export default defineNuxtRouteMiddleware((to, from) => {
const { loggedIn } = useUserSession()
if (!loggedIn.value) {
return navigateTo('/login')
}
})技巧 25:路由参数类型安全
<script setup lang="ts">
const route = useRoute()
// route.params.id 是 string | string[]
// 用类型断言确保安全
const id = computed(() => {
const param = route.params.id
return Array.isArray(param) ? param[0] : param
})
</script>技巧 26:NuxtLink 预加载
<template>
<!-- 默认:进入视口时预加载 -->
<NuxtLink to="/video/123">视频详情</NuxtLink>
<!-- 禁用预加载(列表项太多时节省带宽) -->
<NuxtLink to="/video/123" :prefetch="false">视频详情</NuxtLink>
<!-- 外部链接自动处理 -->
<NuxtLink to="https://example.com" external>外部链接</NuxtLink>
</template>7. SEO 与 Head
技巧 27:useSeoMeta 统一 SEO
<script setup lang="ts">
const { data: video } = await useFetch(`/api/videos/${route.params.id}`)
useSeoMeta({
title: () => video.value?.title ?? 'AI 视频平台',
description: () => video.value?.description,
ogImage: () => video.value?.thumbnail,
ogType: 'video.other',
twitterCard: 'summary_large_image',
})
</script>useSeoMeta 比 useHead 更好——它有严格的类型提示,不会写错 meta 名称。
技巧 28:useHead 动态 title 模板
// app.vue 或 nuxt.config.ts
useHead({
titleTemplate: (title) => title ? `${title} - AI 视频平台` : 'AI 视频平台',
})
// pages/video/[id].vue
useHead({ title: video.value?.title })
// 渲染结果: "视频标题 - AI 视频平台"8. 性能与调试
技巧 29:useNuxtData 共享缓存
// 页面 A 加载了视频列表
const { data: videos } = await useFetch('/api/videos', { key: 'videos' })
// 页面 B 可以直接读取缓存,不重新请求
const { data: cachedVideos } = useNuxtData('videos')技巧 30:Nuxt DevTools 调试
按 Shift + Alt + D(或 Shift + Option + D)打开 Nuxt DevTools:
- Pages:查看路由和当前参数
- Components:查看组件树和 props
- Composables:查看 useState / useAsyncData 的值
- Server Routes:查看所有 API 路由和测试调用
- Modules:查看已安装模块和配置
- Payload:查看 SSR payload 数据大小
// nuxt.config.ts
export default defineNuxtConfig({
devtools: { enabled: true },
})本章小结
30 个技巧按使用频率分为七大类:
- Auto-import(1-5):不手动 import、composable 自动导入、第三方库导入、组件路径别名
- 数据获取(6-10):useFetch/useAsyncData/$fetch 选型、transform 优化、watch 自动请求、缓存、服务端零开销调用
- 组件(11-15):.client/.server 后缀、defineProps 泛型、v-model 多绑定、useTemplateRef、Teleport+ClientOnly
- Server API(16-20):文件名路由、Zod 校验、服务端缓存、utils 自动导入、错误处理
- 配置状态(21-23):runtimeConfig、useState、app.config
- 路由中间件(24-26):页面级中间件、参数类型安全、NuxtLink 预加载
- SEO 与调试(27-30):useSeoMeta、title 模板、useNuxtData 缓存共享、DevTools