Nuxt 4 完全指南:从入门到精通

深度解析最新 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 获取最新动态和版本说明。

相关推荐
Momo__1 小时前
Vue 3.4+ 被低估的 3 个 API,让你的代码更优雅
前端·vue.js
dishugj1 小时前
HANA数据库常用命令总结
java·前端·数据库
clove1 小时前
JavaScript 提升(Hoisting)与声明优先级:一篇文章说透
前端
七牛开发者1 小时前
不写框架、不用 npm,我用 AI Coding 做了一个家庭记忆站
前端·人工智能·npm
@PHARAOH1 小时前
WHAT - npm和corepack
前端·npm·node.js
不爱学英文的码字机器1 小时前
被 AE 的关键帧折磨过的人,应该试试这个用 React 写视频的路子
前端·react.js·音视频
Csvn1 小时前
组合式函数
前端·vue.js
CodeSheep1 小时前
中国编程第一人,一人抵一城!
前端·后端·程序员