Nuxt3 → Nuxt4 完整迁移手册

Nuxt4 不是颠覆性重写,而是 Nuxt3 的渐进式升级。大部分 Breaking Changes 可以通过 compatibilityVersion: 4 提前预览和修复。本章提供一份可执行的迁移 Checklist——从评估、预览、目录迁移到回归测试,确保你的项目平滑升级到 Nuxt4。

1. 迁移评估

1.1 Nuxt4 的核心变化

变化影响范围难度
新目录结构app/ 替代项目根目录
默认 TypeScript 严格模式类型错误变编译错误低-高
useAsyncData 默认 deep: false深层响应性变化
Composition API 为默认Options API 需要显式启用
移除旧版兼容 API部分 Nuxt2 遗留 API 移除
Nitro v3服务端 API 微调
Vue 3.5+新的响应式优化无(自动生效)

1.2 评估清单

迁移前评估:
- [ ] 当前 Nuxt 版本:_____(建议先升级到 Nuxt 3.15+)
- [ ] 项目页面数量:_____ 
- [ ] 第三方模块数量:_____(逐一检查 Nuxt4 兼容性)
- [ ] 是否使用 Options API:_____(需要迁移到 Composition API)
- [ ] TypeScript strict 模式:_____(Nuxt4 默认开启)
- [ ] CI/CD 测试覆盖率:_____(建议 > 60% 再迁移)
- [ ] 预计迁移时间:小型项目 1-2 天,大型项目 1-2 周

2. compatibilityVersion 预览

2.1 开启 Nuxt4 兼容模式

在 Nuxt3 项目中提前体验 Nuxt4 的所有变化:

// nuxt.config.ts
export default defineNuxtConfig({
  future: {
    compatibilityVersion: 4,
  },
})

效果:开启后,Nuxt3 会按照 Nuxt4 的行为运行。所有 Breaking Changes 都会生效,你可以逐个修复。

2.2 逐项开启

如果一次性开启太多变化,可以逐项启用:

export default defineNuxtConfig({
  future: {
    compatibilityVersion: 3,  // 保持 v3 行为
  },
  experimental: {
    // 逐项开启 v4 特性
    scanPageMeta: true,
    sharedPrerenderData: true,
    compileTemplate: true,
    templateUtils: true,
    defaults: {
      useAsyncData: { deep: false },
    },
  },
})

2.3 Nuxt Codemods

官方提供了自动迁移工具:

npx nuxi@latest upgrade --dedupe
npx @nuxt/codemods@latest <codemod-name> <path>

3. 目录结构迁移

3.1 新旧对比

Nuxt 3 目录结构:              Nuxt 4 目录结构:
├── components/                ├── app/
├── composables/               │   ├── components/
├── layouts/                   │   ├── composables/
├── middleware/                │   ├── layouts/
├── pages/                     │   ├── middleware/
├── plugins/                   │   ├── pages/
├── utils/                     │   ├── plugins/
├── app.vue                    │   ├── utils/
├── error.vue                  │   ├── app.vue
├── nuxt.config.ts             │   └── error.vue
├── server/                    ├── server/
└── public/                    ├── public/
                               ├── shared/          ← 新增
                               └── nuxt.config.ts

核心变化:前端代码全部移入 app/ 目录,shared/ 目录用于前后端共享代码。

3.2 迁移脚本

#!/bin/bash
# migrate-to-nuxt4.sh
 
# 1. 创建 app 目录
mkdir -p app
 
# 2. 移动前端目录
for dir in components composables layouts middleware pages plugins utils assets; do
  if [ -d "$dir" ]; then
    mv "$dir" app/
    echo "Moved $dir → app/$dir"
  fi
done
 
# 3. 移动入口文件
for file in app.vue error.vue app.config.ts; do
  if [ -f "$file" ]; then
    mv "$file" app/
    echo "Moved $file → app/$file"
  fi
done
 
# 4. 创建 shared 目录(前后端共享类型/工具)
mkdir -p shared/types shared/utils
 
echo "Migration complete. Run 'pnpm dev' to verify."

3.3 路径别名更新

// nuxt.config.ts — 新的别名映射
export default defineNuxtConfig({
  alias: {
    '~': './app',
    '@': './app',
    '~~': './',
    '@@': './',
  },
})

注意~@ 现在指向 app/ 而不是项目根目录。如果代码中有 ~/server/ 这类路径,需要改为 ~~/server/

3.4 tsconfig 更新

// tsconfig.json
{
  "extends": "./.nuxt/tsconfig.json",
  "compilerOptions": {
    "strict": true,  // Nuxt4 默认 strict
    "paths": {
      "~/*": ["./app/*"],
      "@/*": ["./app/*"],
      "~~/*": ["./*"],
      "@@/*": ["./*"],
      "#shared/*": ["./shared/*"]
    }
  }
}

4. Breaking Changes 逐项修复

4.1 useAsyncData deep: false

变化:Nuxt4 中 useAsyncDatauseFetchdata 默认不是深层响应式。

// Nuxt 3 行为:data 是 deep reactive
const { data } = await useFetch('/api/videos')
data.value.items[0].title = 'new title'  // ✅ 自动触发重渲染
 
// Nuxt 4 行为:data 是 shallow reactive
const { data } = await useFetch('/api/videos')
data.value.items[0].title = 'new title'  // ❌ 不会触发重渲染
 
// 修复方式 1:显式设置 deep
const { data } = await useFetch('/api/videos', { deep: true })
 
// 修复方式 2(推荐):替换整个 data
data.value = { ...data.value, items: updatedItems }

4.2 共享的 types 和 utils

// shared/types/index.ts — 前后端共享类型
export interface Video {
  id: string
  title: string
  description: string
  createdAt: string
}
 
export interface ApiResponse<T> {
  data: T
  meta?: { total: number; page: number }
}
// shared/utils/format.ts — 前后端共享工具
export function formatDuration(seconds: number): string {
  const m = Math.floor(seconds / 60)
  const s = seconds % 60
  return `${m}:${s.toString().padStart(2, '0')}`
}

使用方式(前端和后端都可以导入):

import type { Video } from '#shared/types'
import { formatDuration } from '#shared/utils/format'

4.3 移除的 API

// ❌ 移除:useHead() 的 object syntax 中 hid 属性
useHead({ meta: [{ hid: 'description', name: 'description', content: '...' }] })
 
// ✅ Nuxt4:直接用 name 去重(自动处理)
useHead({ meta: [{ name: 'description', content: '...' }] })
// ❌ 移除:definePageMeta 中 keepalive 字符串
definePageMeta({ keepalive: 'my-page' })
 
// ✅ Nuxt4:使用布尔值或对象
definePageMeta({ keepalive: true })

4.4 Nitro v3 变化

// ❌ Nuxt3:getQuery 返回的类型可能不准确
const query = getQuery(event)
 
// ✅ Nuxt4:推荐使用 getValidatedQuery 配合 Zod
import { z } from 'zod'
const schema = z.object({ page: z.coerce.number().default(1) })
const query = await getValidatedQuery(event, schema.parse)

5. 第三方模块兼容性

5.1 检查模块兼容性

# 检查所有已安装模块的 Nuxt4 兼容性
npx nuxi module search --compatibility 4

5.2 常见模块兼容状态

模块Nuxt4 兼容说明
@nuxt/ui✅ v3+原生支持
@nuxt/image无变化
@nuxt/fonts无变化
@pinia/nuxt需要 v0.8+
nuxt-auth-utils无变化
@sidebase/nuxt-auth⚠️检查最新版本
@nuxtjs/i18n⚠️需要 v9+

5.3 不兼容模块的处理

// 临时降级:对不兼容的模块保持 v3 行为
export default defineNuxtConfig({
  future: { compatibilityVersion: 4 },
  // 但某些模块路径还在旧位置
  modules: ['@some/legacy-module'],
  // 手动配置兼容路径
  alias: {
    '@some/legacy-module': '@some/legacy-module/dist/nuxt3-compat',
  },
})

6. 回归测试

6.1 迁移后检查清单

功能验证:
- [ ] 所有页面可正常访问(SSR + CSR)
- [ ] 页面导航正常(NuxtLink + 编程式导航)
- [ ] 数据获取正常(useFetch / useAsyncData)
- [ ] 表单提交正常
- [ ] 认证流程正常(登录 / 登出 / 权限)
- [ ] 错误页面正常(404 / 500 / error.vue)
 
构建验证:
- [ ] `pnpm dev` 启动无错误
- [ ] `pnpm build` 构建成功
- [ ] `pnpm typecheck` 无类型错误
- [ ] `pnpm lint` 无规范错误
- [ ] `pnpm test` 所有测试通过
 
性能验证:
- [ ] Lighthouse 分数不低于迁移前
- [ ] Bundle 大小不超过迁移前 10%
- [ ] SSR 响应时间无明显增长

6.2 自动化回归测试

// tests/e2e/migration.spec.ts
import { test, expect } from '@playwright/test'
 
test.describe('Post-migration sanity checks', () => {
  test('homepage renders', async ({ page }) => {
    await page.goto('/')
    await expect(page.locator('h1')).toBeVisible()
  })
 
  test('navigation works', async ({ page }) => {
    await page.goto('/')
    await page.click('a[href="/about"]')
    await expect(page).toHaveURL('/about')
  })
 
  test('API routes respond', async ({ request }) => {
    const response = await request.get('/api/health')
    expect(response.ok()).toBe(true)
  })
 
  test('SSR data fetching works', async ({ page }) => {
    await page.goto('/videos')
    const content = await page.textContent('body')
    expect(content).not.toContain('undefined')
    expect(content).not.toContain('null')
  })
 
  test('auth flow works', async ({ page }) => {
    await page.goto('/login')
    await page.fill('input[name="email"]', '[email protected]')
    await page.fill('input[name="password"]', 'password')
    await page.click('button[type="submit"]')
    await expect(page).toHaveURL('/dashboard')
  })
})

7. 迁移步骤总结

7.1 推荐迁移顺序

第 1 步:升级到 Nuxt 3.15+
    ↓
第 2 步:开启 compatibilityVersion: 4
    ↓
第 3 步:修复 TypeScript strict 模式错误
    ↓
第 4 步:修复 useAsyncData deep: false 相关问题
    ↓
第 5 步:执行目录迁移脚本(→ app/ 结构)
    ↓
第 6 步:更新路径别名和 import
    ↓
第 7 步:升级第三方模块到 Nuxt4 兼容版本
    ↓
第 8 步:运行完整测试套件
    ↓
第 9 步:正式升级到 Nuxt 4.x

7.2 回滚方案

// 如果遇到严重问题,可以快速回滚
export default defineNuxtConfig({
  future: {
    compatibilityVersion: 3,  // 回退到 v3 行为
  },
})

目录结构回滚需要逆向移动文件,建议在迁移前创建 Git 分支。

本章小结

  • 渐进式迁移:通过 compatibilityVersion: 4 在 Nuxt3 中提前体验所有 v4 变化
  • 核心变化:app/ 目录结构、deep: false 默认值、strict TypeScript、shared/ 目录
  • 迁移脚本:一键移动前端文件到 app/ 目录
  • 模块兼容:官方模块基本兼容,社区模块需逐一验证
  • 回归测试:功能 + 构建 + 性能三维度验证,Playwright E2E 自动化
  • 回滚方案compatibilityVersion: 3 一键回退