
深度解析最新 Nuxt 4.x(v4.4.5)全栈 Vue 框架的核心特性与高级用法
前言
Nuxt 是 Vue 生态中最成熟的全栈框架,从 Nuxt 2 到 Nuxt 3 再到 Nuxt 4,每一次大版本迭代都带来了底层架构的质的飞跃。Nuxt 4 基于 Vue 3、Nitro 和 Vite 构建,在保持 Nuxt 3 优秀设计的基础上,进行了大量破坏性升级(Breaking Changes),重新定义了目录结构、渲染策略和开发者体验的标准。
这篇文章基于 Nuxt 4.4.5 编写,覆盖所有新特性、Breaking Changes、以及真实生产环境中的复杂使用场景。
💡 插图在后半部分。
一、Nuxt 4 的核心架构
1.1 技术栈全景
bash
┌────────────────────────────────────────────────────────────────┐
│ Nuxt 4 应用 │
├────────────────────────────────────────────────────────────────┤
│ Vue 3 (Composition API) │ Nitro Server Engine │
│ ├── app/pages/ │ ├── API Routes │
│ ├── app/components/ │ ├── Serverless │
│ ├── app/composables/ │ ├── Edge Functions │
│ ├── app/layouts/ │ ├── Hybrid Rendering │
│ └── app/plugins/ │ └── Route Rules │
├────────────────────────────────────────────────────────────────┤
│ Vite 6 构建工具 │
│ ├── HMR (毫秒级热更新) │ Rollup Bundler │
│ ├── TypeScript 原生支持 │ WASM 插件生态 │
│ └── Env Variables / 资源处理 │ LightningCSS │
└────────────────────────────────────────────────────────────────┘
1.2 Nuxt 4 vs Nuxt 3 核心差异
| 能力维度 | Nuxt 3 | Nuxt 4 |
|---|---|---|
| 目录结构 | 根目录约定 | app/ 目录隔离 |
| 渲染模式 | SSR/SSG/SPA/ISR/Edge | 全部保留 + 更细粒度控制 |
| 状态管理 | Pinia | Pinia 2 + useState |
| TypeScript | 原生支持 | 深度类型推导 + 更严格的推断 |
| 服务端引擎 | Nitro | Nitro 2(更多部署目标) |
| 缓存策略 | 基础 | runtimeCache 细粒度控制 |
| View Transitions | 实验性 | 稳定支持 |
| 模块兼容性 | 部分需适配 | 需检查兼容性 |
| CSS 处理 | PostCSS | LightningCSS(更快) |
1.3 目录结构:Nuxt 4 的 app/ 革命
Nuxt 4 最重要的 Breaking Change --- 所有前端相关代码迁移到 app/ 目录:
bash
# Nuxt 4 目录结构
├── nuxt.config.ts # 配置文件
├── app/ # ⭐ 核心目录(Nuxt 4 新增)
│ ├── app.vue # 应用入口
│ ├── pages/ # 文件系统路由
│ │ ├── index.vue # → /
│ │ ├── about.vue # → /about
│ │ └── blog/
│ │ ├── index.vue # → /blog
│ │ └── [slug].vue # → /blog/:slug
│ ├── components/ # 自动导入组件
│ ├── composables/ # 自动导入组合式函数
│ ├── layouts/ # 布局系统
│ ├── middleware/ # 路由中间件
│ ├── plugins/ # 插件
│ ├── assets/ # 资源文件
│ └── public/ # 静态资源
├── server/ # 服务端代码(保持不变)
│ ├── api/
│ ├── middleware/
│ └── utils/
├── modules/ # 本地模块(保持不变)
└── package.json
⚠️ Breaking Change :如果你从 Nuxt 3 迁移,
pages/、components/、composables/、layouts/、middleware/、plugins/需要移动到app/目录下。迁移成本中等,但让项目结构更清晰。
二、基础配置与约定
2.1 nuxt.config.ts 核心配置
typescript
// nuxt.config.ts
export default defineNuxtConfig({
compatibilityDate: '2025-01-01', // 启用 Nuxt 4 行为
future: { compatibilityVersion: 4 }, // ⭐ 启用 Nuxt 4 行为
app: {
head: {
title: '我的 Nuxt 4 应用',
meta: [
{ name: 'description', content: '基于 Nuxt 4 的现代 Web 应用' }
],
link: [
{ rel: 'icon', type: 'image/svg+xml', href: '/favicon.svg' }
]
},
},
modules: [
'@pinia/nuxt',
'@vueuse/nuxt',
'@nuxt/image',
'@nuxtjs/i18n',
],
css: ['~/assets/css/main.css'],
devtools: { enabled: true },
})
2.2 自动导入(Auto-imports)
Nuxt 4 的自动导入能力在 Nuxt 3 基础上进一步增强:
typescript
// app/composables/useCounter.ts
export const useCounter = () => {
const count = useState<number>('count', () => 0)
const increment = () => count.value++
const decrement = () => count.value--
const reset = () => (count.value = 0)
return { count: readonly(count), increment, decrement, reset }
}
// app/pages/index.vue --- 无需任何 import
<script setup lang="ts">
const { count, increment } = useCounter()
</script>
自动导入的范围:
| 类别 | 扫描目录 | 示例 |
|---|---|---|
| 组合式函数 | app/composables/ |
useCounter() |
| Vue API | 内置 | ref, computed, watch |
| 组件 | app/components/ |
<Button /> |
| 工具函数 | server/utils/ |
服务端工具可被客户端安全函数调用 |
| 样式 | assets/ |
Vite 自动处理 |
2.3 app.vue 与布局系统
vue
<!-- app/app.vue -->
<template>
<NuxtLayout>
<NuxtPage />
</NuxtLayout>
</template>
vue
<!-- app/layouts/default.vue -->
<template>
<div>
<TheHeader />
<main>
<slot />
</main>
<TheFooter />
</div>
</template>
vue
<!-- app/layouts/admin.vue -->
<template>
<div class="admin-layout">
<AdminSidebar />
<div class="admin-content">
<slot />
</div>
</div>
</template>
vue
<!-- app/pages/admin/dashboard.vue -->
<script setup lang="ts">
definePageMeta({ layout: 'admin' })
</script>
三、数据获取:Nuxt 4 的 SSR 友好方案
3.1 useAsyncData --- 核心数据获取
typescript
const { data, pending, error, refresh, clear } = await useAsyncData(
'post-list', // 缓存键(唯一)
() => $fetch('/api/posts'), // 异步获取函数
{
server: true, // 服务端执行(默认 true)
lazy: false, // true = 客户端挂起显示骨架屏
default: () => [], // 骨架屏 / 初始值
transform: (data) => data.posts, // 数据转换
pick: ['id', 'title', 'slug', 'cover'], // 只取需要的字段
getCachedData: (key, nuxtApp) => nuxtApp.payload.data[key], // 自定义缓存读取
}
)
3.2 useFetch --- 语法糖
typescript
// GET 请求
const { data } = await useFetch('/api/posts', {
query: { page: 1, limit: 20 }, // 自动拼接到 URL
headers: useRequestHeaders(['cookie']), // 传递 cookie
})
// POST 请求
const { data } = await useFetch('/api/posts', {
method: 'POST',
body: { title: 'Nuxt 4', content: '...' },
})
3.3 客户端刷新
typescript
const { data, refresh } = await useAsyncData('posts', () => $fetch('/api/posts'))
// 手动刷新
await refresh()
// 带选项刷新(绕过缓存)
await refresh({ dedupe: true })
3.4 Nuxt 4 的 useNuxtData --- 更灵活的缓存控制
typescript
// 在页面加载前检查缓存
const { data: cachedPost } = useNuxtData('post-detail')
if (cachedPost.value) {
// 有缓存,直接使用,不发起请求
console.log('命中客户端缓存:', cachedPost.value)
}
// 即使有缓存也强制刷新
const { data } = await useFetch('/api/posts/1', {
getCachedData: () => null, // 强制跳过缓存
})
四、混合渲染(Hybrid Rendering)
这是 Nuxt 最强大的特性之一,在 Nuxt 4 中通过 routeRules 精细控制每条路由的渲染策略。
4.1 路由规则配置
typescript
// nuxt.config.ts
export default defineNuxtConfig({
routeRules: {
// 首页:构建时静态生成
'/': { prerender: true },
// 博客列表:增量静态再生(SWR),1 小时新鲜度
'/blog': { swr: 3600 },
// 博客详情:SWR + 自动爬取链接预渲染
'/blog/**': {
swr: 86400,
prerender: ['/blog/getting-started-with-nuxt4'],
crawlLinks: true,
},
// 文档:静态生成
'/docs/**': { prerender: true },
// 仪表盘:纯客户端渲染(需要登录的页面)
'/dashboard/**': { ssr: false },
// 管理后台:服务端渲染 + 中间件
'/admin': {
ssr: true,
middleware: ['auth'],
},
// API 路由:跨域开放
'/api/**': { cors: true },
// 用户个人页:SSR + 缓存
'/u/**': { ssr: true, cache: { maxAge: 300, staleMaxAge: 600 } },
// 默认:CDN 缓存 60 秒
'/**': { cache: { maxAge: 60 } },
}
})
4.2 渲染模式详解
css
┌──────────────┬────────────────────────────────────────────────┐
│ 渲染模式 │ 说明与适用场景 │
├──────────────┼────────────────────────────────────────────────┤
│ prerender │ 构建时生成 HTML,适合内容固定不变的页面 │
│ swr │ Stale-While-Revalidate,首推 ISR 方案 │
│ ssr │ 服务端渲染,首屏 SEO 友好,适合内容动态的页面 │
│ spa │ 纯客户端渲染,适合高度交互的后台管理界面 │
│ isr │ 增量静态再生,通过 swr 实现 │
│ edge │ CDN 边缘节点直出,全球 < 50ms 延迟 │
└──────────────┴────────────────────────────────────────────────┘
4.3 ISR 实战:内容站点
typescript
routeRules: {
'/blog/[slug]': {
swr: 86400, // 优先返回缓存,24h 后后台重新验证
cache: {
maxAge: 86400, // CDN 缓存 24 小时
staleMaxAge: 86400, // 过期后仍可服务,但后台更新
},
}
}
4.4 Edge Functions:边缘计算
typescript
// nuxt.config.ts
export default defineNuxtConfig({
nitro: {
preset: 'cloudflare-pages', // vercel-edge / aws-lambda-edge
},
routeRules: {
'/api/health': { prerender: true },
'/landing/**': { prerender: true }, // 预渲染推广页
}
})
五、服务端路由(Nitro Server)
Nuxt 4 的服务端代码仍在 server/ 目录,底层引擎升级为 Nitro 2,支持更多部署目标。
5.1 API 路由
typescript
// server/api/posts/index.get.ts --- GET /api/posts
export default defineEventHandler(async (event) => {
const query = getQuery(event)
const page = Number(query.page) || 1
const limit = Number(query.limit) || 20
return {
posts: [
{ id: 1, title: 'Nuxt 4 新特性', slug: 'nuxt4-features' },
{ id: 2, title: 'Nitro 2 引擎解析', slug: 'nitro-2' },
],
pagination: { page, limit, total: 42 }
}
})
// server/api/posts/index.post.ts --- POST /api/posts
export default defineEventHandler(async (event) => {
const body = await readBody(event)
// 验证
if (!body.title) {
throw createError({
statusCode: 400,
message: '标题不能为空',
})
}
// 业务逻辑
const post = await createPost(body)
setResponseStatus(event, 201)
return { post }
})
// server/api/posts/[id].get.ts --- GET /api/posts/:id
export default defineEventHandler(async (event) => {
const id = Number(getRouterParam(event, 'id'))
const post = await getPostById(id)
if (!post) {
throw createError({ statusCode: 404, message: '文章不存在' })
}
return post
})
5.2 服务端中间件
typescript
// server/middleware/auth.ts
export default defineEventHandler(async (event) => {
const publicPaths = ['/api/auth/login', '/api/posts']
const path = getRequestURL(event).pathname
if (publicPaths.some(p => path.startsWith(p))) return
const token = getHeader(event, 'Authorization')?.replace('Bearer ', '')
if (!token) {
throw createError({ statusCode: 401, message: '未授权' })
}
try {
const user = await verifyJWT(token)
event.context.user = user
} catch {
throw createError({ statusCode: 401, message: 'Token 无效' })
}
})
5.3 数据库集成(Prisma)
bash
npx nuxi@latest module add prisma
prisma
// prisma/schema.prisma
generator client {
provider = "prisma-client-js"
}
datasource db {
provider = "sqlite"
url = env("DATABASE_URL")
}
model Post {
id Int @id @default(autoincrement())
title String
content String
slug String @unique
published Boolean @default(false)
authorId Int
author User @relation(fields: [authorId], references: [id])
tags Tag[]
createdAt DateTime @default(now())
updatedAt DateTime @updatedAt
}
model User {
id Int @id @default(autoincrement())
email String @unique
name String
posts Post[]
}
model Tag {
id Int @id @default(autoincrement())
name String @unique
posts Post[]
}
typescript
// server/utils/db.ts
import { PrismaClient } from '@prisma/client'
const prisma = new PrismaClient()
export { prisma }
// server/api/posts/index.get.ts
export default defineEventHandler(async (event) => {
const query = getQuery(event)
const page = Number(query.page) || 1
const limit = Number(query.limit) || 10
const skip = (page - 1) * limit
const [posts, total] = await Promise.all([
prisma.post.findMany({
where: { published: true },
include: {
author: { select: { id: true, name: true } },
tags: { select: { id: true, name: true } },
},
orderBy: { createdAt: 'desc' },
skip,
take: limit,
}),
prisma.post.count({ where: { published: true } }),
])
return { posts, total, page, limit }
})
六、Nuxt 4 高级特性
6.1 状态管理:Pinia 2 + useState
轻量方案 --- useState:
typescript
// app/composables/useAuth.ts
export const useAuth = () => {
const token = useState<string | null>('token', () => null)
const user = useState<User | null>('user', () => null)
const isAuthenticated = computed(() => !!token.value)
const login = async (credentials: Credentials) => {
const { data } = await useFetch('/api/auth/login', {
method: 'POST',
body: credentials,
})
token.value = data.value!.token
user.value = data.value!.user
}
const logout = () => {
token.value = null
user.value = null
navigateTo('/login')
}
return {
token: readonly(token),
user: readonly(user),
isAuthenticated,
login,
logout,
}
}
Pinia 方案(复杂场景):
typescript
// stores/cart.ts
import { defineStore } from 'pinia'
export const useCartStore = defineStore('cart', () => {
const items = ref<CartItem[]>([])
const isLoading = ref(false)
const itemCount = computed(() =>
items.value.reduce((sum, i) => sum + i.quantity, 0)
)
const totalPrice = computed(() =>
items.value.reduce((sum, i) => sum + i.price * i.quantity, 0)
)
const addItem = async (product: Product) => {
const existing = items.value.find(i => i.id === product.id)
if (existing) {
existing.quantity++
} else {
items.value.push({ ...product, quantity: 1 })
}
}
const removeItem = (id: string) => {
items.value = items.value.filter(i => i.id !== id)
}
const checkout = async () => {
isLoading.value = true
try {
await $fetch('/api/orders', { method: 'POST', body: { items: items.value } })
items.value = []
return { success: true }
} finally {
isLoading.value = false
}
}
return { items, isLoading, itemCount, totalPrice, addItem, removeItem, checkout }
})
6.2 运行时配置(Runtime Config)
typescript
// nuxt.config.ts
export default defineNuxtConfig({
runtimeConfig: {
// 服务端私有(API 密钥、数据库密码)
dbPassword: '',
jwtSecret: '',
// 客户端可见(公钥)
public: {
apiBase: '/api',
siteName: 'Nuxt 4 博客',
analyticsId: 'GA-XXX',
uploadMaxSize: 10 * 1024 * 1024, // 10MB
}
}
})
// 客户端使用
const config = useRuntimeConfig()
console.log(config.public.siteName) // ✅ 客户端可用
// 服务端使用(API 路由内)
export default defineEventHandler((event) => {
const config = useRuntimeConfig(event)
console.log(config.dbPassword) // ✅ 仅服务端可见
})
6.3 Nuxt 4 View Transitions(视图过渡)
typescript
// nuxt.config.ts
export default defineNuxtConfig({
experimental: {
viewTransition: true, // ⭐ 启用 View Transitions
}
})
vue
<!-- app/pages/index.vue -->
<template>
<NuxtLink to="/blog" class="card">
<img src="/cover.jpg" />
<h2>文章标题</h2>
<NuxtPage :transition="{ name: 'slide' }" />
</NuxtLink>
</template>
<style>
/* 视图过渡动画 */
.slide-enter-active,
.slide-leave-active {
transition: all 0.3s ease;
}
.slide-enter-from {
transform: translateX(100%);
opacity: 0;
}
.slide-leave-to {
transform: translateX(-100%);
opacity: 0;
}
</style>
6.4 路由中间件
typescript
// app/middleware/auth.ts
export default defineNuxtRouteMiddleware((to, from) => {
const { isAuthenticated } = useAuth()
if (!isAuthenticated.value) {
return navigateTo(`/login?redirect=${to.fullPath}`)
}
})
// app/pages/dashboard/index.vue
<script setup lang="ts">
definePageMeta({ middleware: ['auth'] })
</script>
typescript
// 命名路由中间件(带参数)
// app/middleware/role.ts
export default defineNuxtRouteMiddleware((to, from) => {
const { user } = useAuth()
const requiredRole = to.meta.role as string | undefined
if (requiredRole && user.value?.role !== requiredRole) {
throw createError({ statusCode: 403, message: '权限不足' })
}
})
// app/pages/admin/users.vue
<script setup lang="ts">
definePageMeta({ middleware: [{ name: 'role', params: { role: 'admin' } }] })
</script>
6.5 服务端插件
typescript
// app/plugins/dayjs.ts --- 自动导入到服务端
export default defineNuxtPlugin(() => {
const dayjs = useDayjs()
return {
provide: {
dayjs,
formatDate: (date: string | Date) => dayjs(date).format('YYYY-MM-DD'),
}
}
})
typescript
// app/plugins/socket.client.ts --- 仅客户端
export default defineNuxtPlugin(() => {
const socket = new WebSocket('/_ws')
socket.addEventListener('message', (event) => {
const data = JSON.parse(event.data)
// 处理消息
})
return {
provide: {
socket,
}
}
})
6.6 WebSocket 路由(Nitro)
typescript
// server/routes/_ws.ts
export default defineWebSocketHandler({
open(peer) {
console.log('[ws] Client connected:', peer.id)
peer.send(JSON.stringify({ type: 'connected' }))
},
message(peer, message) {
try {
const data = JSON.parse(message.text())
switch (data.type) {
case 'ping':
peer.send(JSON.stringify({ type: 'pong', ts: Date.now() }))
break
case 'subscribe':
peer.subscribedChannels.add(data.channel)
break
default:
peer.send(JSON.stringify({ type: 'error', message: 'Unknown type' }))
}
} catch {
peer.send(JSON.stringify({ type: 'error', message: 'Invalid JSON' }))
}
},
close(peer) {
console.log('[ws] Client disconnected:', peer.id)
},
error(peer, error) {
console.error('[ws] Error:', error)
},
})
七、模块生态与扩展
7.1 常用模块推荐
bash
# 一键安装模块
npx nuxi@latest module add pinia
npx nuxi@latest module add vueuse
npx nuxi@latest module add image
npx nuxi@latest module add i18n
npx nuxi@latest module add prismic
npx nuxi@latest module add google-fonts
7.2 自定义模块
typescript
// modules/seo.ts
import { defineNuxtModule, addPlugin, addComponent } from '@nuxtkit/core'
export default defineNuxtModule({
name: 'seo',
configKey: 'seo',
setup(options, nuxt) {
// 添加全局组件
addComponent({ name: 'SeoMeta', filePath: resolve('./components/SeoMeta.vue') })
// 注册插件
addPlugin(resolve('./plugins/seo.client.ts'))
// 修改 nuxt 配置
nuxt.options.app.head = {
...nuxt.options.app.head,
htmlAttrs: { lang: options.defaultLocale || 'zh' },
meta: [
{ name: 'theme-color', content: options.themeColor || '#6366f1' }
]
}
}
})
// nuxt.config.ts
export default defineNuxtConfig({
modules: ['~/modules/seo'],
seo: {
defaultLocale: 'zh',
themeColor: '#6366f1',
}
})
7.3 Nuxt Image 高级用法
typescript
// nuxt.config.ts
export default defineNuxtConfig({
modules: ['@nuxt/image'],
image: {
quality: 80,
formats: ['webp', 'avif'],
screens: {
xs: 320, sm: 640, md: 768, lg: 1024, xl: 1280, xxl: 1536, '2xl': 1920,
},
domains: ['images.unsplash.com'],
alias: {
avatars: 'https://images.unsplash.com/photo-',
},
}
})
vue
<template>
<!-- 自动生成多尺寸、WebP、懒加载 -->
<NuxtImg
src="/hero.jpg"
:width="1200"
:height="630"
:sizes="'(max-width: 640px) 100vw, (max-width: 1024px) 50vw, 800px'"
format="webp"
loading="lazy"
placeholder
alt="Hero image"
/>
<!-- 头像裁剪 -->
<NuxtImg
src="avatar.jpg"
:width="80"
:height="80"
fit="cover"
rounded="full"
alt="User avatar"
/>
</template>
八、Nuxt 4 性能优化
8.1 构建优化
typescript
// nuxt.config.ts
export default defineNuxtConfig({
experimental: {
payloadExtraction: true, // 分离 payload,减少 TTFB
renderJsonPayloads: true, // JSON payload 压缩
viewTransition: true, // 视图过渡动画
},
vite: {
css: {
devSourcemap: true,
},
build: {
cssMinify: 'lightningcss', // ⭐ Nuxt 4 新增,更快的 CSS 压缩
rollupOptions: {
output: {
manualChunks: {
'vendor-vue': ['vue', 'vue-router', '@vue/runtime-core'],
'vendor-pinia': ['pinia'],
'vendor-utils': ['@vueuse/core', 'dayjs'],
},
},
},
},
},
nitro: {
compressPublicAssets: true,
minify: true,
},
})
8.2 首屏优化配置
typescript
// app/app.vue --- 关键资源预加载
<script setup lang="ts">
useHead({
link: [
{ rel: 'preconnect', href: 'https://fonts.googleapis.com' },
{ rel: 'preconnect', href: 'https://fonts.gstatic.com', crossorigin: '' },
{ rel: 'dns-prefetch', href: 'https://cdn.example.com' },
]
})
</script>
8.3 懒加载与代码分割
vue
<script setup lang="ts">
// 动态导入组件
const HeavyChart = defineAsyncComponent(() => import('~/components/HeavyChart.vue'))
// 延迟加载路由(nuxt.config.ts 中配置)
// 默认情况下 Nuxt 会按页面自动代码分割
// 如需更精细控制:
</script>
<template>
<Suspense>
<HeavyChart />
<template #fallback>
<ChartSkeleton />
</template>
</Suspense>
</template>
九、Nuxt 4 的进阶使用场景
9.1 国际化(i18n)
bash
npx nuxi@latest module add i18n
typescript
// nuxt.config.ts
export default defineNuxtConfig({
modules: ['@nuxtjs/i18n'],
i18n: {
locales: [
{ code: 'zh', iso: 'zh-CN', name: '简体中文', file: 'zh.json' },
{ code: 'en', iso: 'en-US', name: 'English', file: 'en.json' },
{ code: 'ja', iso: 'ja-JP', name: '日本語', file: 'ja.json' },
],
defaultLocale: 'zh',
lazy: true,
langDir: 'locales/',
strategy: 'prefix_except_default',
detectBrowserLanguage: {
useCookie: true,
cookieKey: 'i18n_locale',
redirectOn: 'root',
},
}
})
json
// locales/zh.json
{
"welcome": "欢迎来到 {name}",
"nav": {
"home": "首页",
"about": "关于",
"blog": "博客"
}
}
vue
<!-- app/pages/index.vue -->
<script setup lang="ts">
const { locale, setLocale, t } = useI18n()
</script>
<template>
<h1>{{ t('welcome', { name: 'Nuxt 4' }) }}</h1>
<button @click="setLocale('en')">EN</button>
<button @click="setLocale('zh')">中文</button>
</template>
9.2 Nuxt DevTools:开发者的瑞士军刀
Nuxt 4 内置 DevTools,提供可视化调试能力:
arduino
┌─────────────────────────────────────────────────┐
│ 🛠 Nuxt DevTools v5 │
├─────────────────────────────────────────────────┤
│ 📄 Pages --- 可视化路由图谱 │
│ 🔧 Components --- 组件树检查与 props 追踪 │
│ 📊 Composables --- 组合式函数调用链 │
│ 🎯 Inspect --- 模块解析结果查看 │
│ 📈 Performance --- 性能面板 │
│ 📦 Modules --- 模块管理 │
│ 🌐 Route Rules --- 渲染规则可视化配置 │
│ 📝 Payload --- 请求/响应 payload 检查 │
│ 🔌 Server --- 服务端路由与日志 │
└─────────────────────────────────────────────────┘
9.3 测试策略
bash
npm install -D vitest @nuxt/test-utils @vue/test-utils
typescript
// vitest.config.ts
import { defineConfig } from 'vitest/config'
import vue from '@vitejs/plugin-vue'
export default defineConfig({
plugins: [vue()],
test: {
environment: 'nuxt',
globals: true,
coverage: {
provider: 'v8',
reporter: ['text', 'json', 'html'],
},
},
})
typescript
// app/components/__tests__/Button.spec.ts
import { describe, it, expect } from 'vitest'
import { mount } from '@vue/test-utils'
import Button from '../Button.vue'
describe('Button', () => {
it('renders with correct text', () => {
const wrapper = mount(Button, { slots: { default: 'Click me' } })
expect(wrapper.text()).toBe('Click me')
})
it('emits click event', async () => {
const wrapper = mount(Button, { slots: { default: 'Click me' } })
await wrapper.trigger('click')
expect(wrapper.emitted('click')).toHaveLength(1)
})
})
9.4 Docker 部署
typescript
// nuxt.config.ts
export default defineNuxtConfig({
nitro: {
preset: 'docker',
}
})
dockerfile
# Dockerfile
FROM node:20-alpine AS builder
WORKDIR /app
COPY package*.json ./
RUN npm ci
COPY . .
RUN npm run build
FROM node:20-alpine AS runner
WORKDIR /app
ENV NODE_ENV=production
RUN addgroup -g 1001 -S nodejs && adduser -S nuxtjs -u 1001
COPY --from=builder --chown=nuxtjs:nodejs /app/.output .output
USER nuxtjs
EXPOSE 3000
ENV HOST=0.0.0.0
ENV PORT=3000
CMD ["node", ".output/server/index.mjs"]
bash
# 构建与运行
docker build -t nuxt4-app .
docker run -p 3000:3000 nuxt4-app
9.5 跨平台部署配置
typescript
// nuxt.config.ts
export default defineNuxtConfig({
nitro: {
// 选择部署目标
preset: 'node-server', // 标准 Node.js
// preset: 'docker', // Docker 容器
// preset: 'vercel', // Vercel
// preset: 'cloudflare-pages', // Cloudflare Pages
// preset: 'aws-lambda', // AWS Lambda
// preset: 'deno-deploy', // Deno Deploy
}
})
十、Nuxt 4 迁移指南(Nuxt 3 → 4)
10.1 必须修改的 Breaking Changes
1. 目录迁移到 app/:
bash
# 需要将以下目录移动到 app/ 下
pages/ → app/pages/
components/ → app/components/
composables/ → app/composables/
layouts/ → app/layouts/
middleware/ → app/middleware/
plugins/ → app/plugins/
assets/ → app/assets/
public/ → app/public/
2. 启用 Nuxt 4 行为:
typescript
// nuxt.config.ts
export default defineNuxtConfig({
future: { compatibilityVersion: 4 }, // ⭐ 必须添加
})
3. 迁移前后对比:
typescript
// ❌ Nuxt 3 写法
export default {
async asyncData({ $axios }) {
const posts = await $axios.$get('/api/posts')
return { posts }
},
head() {
return { title: '博客' }
},
}
// ✅ Nuxt 4 写法
definePageMeta({})
const { data: posts } = await useFetch('/api/posts')
useHead({ title: '博客' })
4. Pinia 状态迁移:
typescript
// ❌ Nuxt 3 --- Pinia store
import { defineStore } from 'pinia'
export const useStore = defineStore('main', { ... })
// ✅ Nuxt 4 --- Pinia store(需安装 @pinia/nuxt)
// 结构完全相同,Nuxt 4 使用 Pinia 2
import { defineStore } from 'pinia'
export const useStore = defineStore('main', { ... })
10.2 迁移检查清单
ini
[ ] 添加 future.compatibilityVersion: 4 到 nuxt.config.ts
[ ] 创建 app/ 目录
[ ] 移动 pages/ → app/pages/
[ ] 移动 components/ → app/components/
[ ] 移动 composables/ → app/composables/
[ ] 移动 layouts/ → app/layouts/
[ ] 移动 middleware/ → app/middleware/
[ ] 移动 plugins/ → app/plugins/
[ ] 移动 assets/ → app/assets/
[ ] 移动 public/ → app/public/(如有必要)
[ ] 更新所有组件引用路径
[ ] 检查所有模块的 Nuxt 4 兼容性
[ ] 运行 npx nuxi prepare 重新生成类型
[ ] 运行 npx nuxi typecheck 进行类型检查
[ ] 运行 npm run build 确认构建通过
[ ] 测试所有路由和功能
十一、Nuxt 4 生态推荐
| 模块 | 用途 | 安装命令 |
|---|---|---|
@pinia/nuxt |
状态管理(Pinia 2) | nuxi module add pinia |
@vueuse/nuxt |
VueUse 工具集 | nuxi module add vueuse |
@nuxt/image |
图片优化 | nuxi module add image |
@nuxtjs/i18n |
国际化 | nuxi module add i18n |
@nuxtjs/google-fonts |
Google 字体 | nuxi module add google-fonts |
@nuxtjs/prismic |
CMS 集成 | nuxi module add prismic |
nuxt-og-image |
Open Graph 图片 | nuxi module add og-image |
nuxt-schema-org |
SEO 结构化数据 | nuxi module add schema-org |
@prisma/nuxt |
Prisma ORM | nuxi module add prisma |
十二、总结
sql
┌──────────────────────────────────────────────────────────────┐
│ Nuxt 4 核心价值 │
├──────────────────────────────────────────────────────────────┤
│ │
│ 🏗 app/ 目录隔离 --- 前端代码与配置清晰分离 │
│ 🚀 性能卓越 --- Vite 6 + LightningCSS,构建速度再提升 │
│ 🌐 混合渲染 --- 按路由精细控制 SSR/SSG/ISR/Edge │
│ 🔧 开发者体验 --- DevTools v5、深度 TypeScript 支持 │
│ 📦 Pinia 2 + 模块生态 --- 完整 Vue 生态链 │
│ 🔄 View Transitions --- 丝滑的页面过渡动画 │
│ 🌪 Nitro 2 --- 跨平台部署,Node.js / Serverless / Edge │
│ ⚡ 快速迁移路径 --- Nuxt 3 → 4 有完整迁移指南 │
│ │
└──────────────────────────────────────────────────────────────┘
Nuxt 4 是 Vue 全栈应用开发的最新标准。尽管迁移成本存在,但 app/ 目录的隔离设计让项目结构更清晰,Nitro 2 的跨平台能力让部署更灵活,性能优化工具(LightningCSS、View Transitions)让用户体验更丝滑。
无论你是新建项目还是从 Nuxt 3 迁移,Nuxt 4 都值得投入。
本文基于 Nuxt 4.4.5 编写。建议持续关注 nuxt.com 获取最新动态和版本说明。