前端开发
Nuxt 4 生产问题排查与部署迁移指南:在…
适用于 Nuxt 4 生产项目需要修复 hydration、提升性能、编写测试、部署到云平台或从 Nuxt 3 迁移的场景。
🧪 尚未被验证(还没有小呱复用过的记录,不代表不好)
GDI C 38/100
进化评分(GDI)
综合排序依据 —— 四个维度的加权结果,让你看出「为什么这条排在前面」
内在质量 55/100 ×0.35
使用度 0/100 ×0.30
口碑 50/100 ×0.20
新鲜度 60/100 ×0.15
适用场景
Nuxt 4 生产问题排查与部署迁移指南特定
解决思路
- v4.2 中止数据请求:用 const controller = ref<AbortController>() 保存控制器。
- 在 useAsyncData 中调用 $fetch('/api/users', { signal: controller.value?.signal }),把 signal 传给请求。
- 定义 abortRequest:调用 controller.value?.abort(),然后 controller.value = new AbortController() 以允许后续请求。
- v4.2 异步数据处理器提取:利用客户端 bundle 减小约 39%,数据获取逻辑被抽取到 server chunks,自动优化无需配置。
- v4.2 增强错误处理:开发环境同时显示自定义错误页和技术性 overlay,并改进错误消息。
- v4.1 增强 chunk 稳定性:使用 import maps 防止级联 hash 变化,改善长期缓存。
- v4.1 懒 hydration:在 <script setup> 中用 defineLazyHydrationComponent(() => import('./HeavyComponent.vue')) 定义按需 hydration 组件。
- 注意 v4 破坏性变更:源码目录从根目录改为 app/。
- 注意 v4 破坏性变更:数据响应式默认从 deep 改为 shallow。
- 注意 v4 破坏性变更:默认值从 null 改为 undefined。
- 注意 v4 破坏性变更:路由中间件默认从 client 改为 server。
- 注意 v4 破坏性变更:app manifest 从 opt-in 改为默认开启。
- 调试 hydration 节点不匹配时,加载 references/hydration.md。
- 实现 ClientOnly 组件时,加载 references/hydration.md。
- 修复非确定性渲染问题时,加载 references/hydration.md。
- 理解 SSR 与客户端渲染差异时,加载 references/hydration.md。
- 优化 Core Web Vitals 分数时,加载 references/performance.md。
- 实现懒加载和代码分割时,加载 references/performance.md。
- 配置缓存策略时,加载 references/performance.md。
- 减小 bundle 大小时,加载 references/performance.md。
- 用 @nuxt/test-utils 编写组件测试时,加载 references/testing-vitest.md。
- 测试带 Nuxt context 的 composables 时,加载 references/testing-vitest.md。
- mock Nuxt API(如 useFetch、useRoute)时,加载 references/testing-vitest.md。
- 设置 Vitest 配置时,加载 references/testing-vitest.md。
- 部署到 Cloudflare Pages 或 Workers 时,加载 references/deployment-cloudflare.md。
- 配置 wrangler.toml 时,加载 references/deployment-cloudflare.md。
- 设置 NuxtHub 集成时,加载 references/deployment-cloudflare.md。
- 使用 D1、KV、R2 bindings 时,加载 references/deployment-cloudflare.md。
- 非确定性值导致 hydration 不匹配时,把 Math.random() 改为 useState('random-id', () => Math.random())。
- 服务端使用浏览器 API 导致不匹配时,把 window.innerWidth 放入 onMounted 中,并用 ref(0) 初始值。
- 服务端使用日期/时间导致不匹配时,用 useState 或 <ClientOnly>。
- 第三方脚本(如 Analytics)导致不匹配时,用 <ClientOnly> 包裹。
- 使用 <ClientOnly> 包裹仅客户端内容,并提供 <template #fallback> 骨架屏。
- 条件渲染客户端内容:用 showWidget = ref(false),在 onMounted 中设为 true,模板用 <AnalyticsWidget v-if='showWidget' />。
- 正确示例:非确定性值用 const id = useState('random-id', () => Math.random())。
- 正确示例:浏览器 API 用 const width = ref(0),在 onMounted(() => { width.value = window.innerWidth }) 中赋值。
- 错误示例:不要在 <script setup> 直接 const id = Math.random()。
- 错误示例:不要在 <script setup> 直接 const width = window.innerWidth,服务端会崩溃。
- 客户端 only 内容用 <ClientOnly> 并给 fallback。
- 条件渲染:仅在 hydration 后通过 onMounted 显示 AnalyticsWidget。
- 懒加载重组件:用 defineAsyncComponent(() => import('~/components/HeavyChart.vue'))。
- 带状态懒加载:用 defineAsyncComponent({ loader, loadingComponent, errorComponent, delay: 200, timeout: 10000 })。
- 模板中用 <Suspense> 包裹异步组件,并提供 #fallback 的 LoadingSpinner。
- 懒 hydration 到可见时:defineLazyHydrationComponent(() => import('./HeavyComponent.vue'), { hydrate: 'visible' })。
- 懒 hydration 到交互时:defineLazyHydrationComponent(() => import('./InteractiveComponent.vue'), { hydrate: 'interaction' })。
- 懒 hydration 到浏览器空闲时:defineLazyHydrationComponent(() => import('./IdleComponent.vue'), { hydrate: 'idle' })。
- 路由缓存:在 nuxt.config.ts 的 routeRules 中为 / 和 /about 设置 { prerender: true }。
- 路由缓存:为 /blog/** 设置 { swr: 3600 }。
- 路由缓存:为 /products/** 设置 { isr: 3600 }。
- 路由缓存:为 /dashboard/** 设置 { ssr: false } 进入 SPA 模式。
- 路由缓存:为 /static/** 设置 headers: { 'Cache-Control': 'public, max-age=31536000' }。
- 图片优化:用 <NuxtImg> 并设置 src、alt、width、height、loading='lazy'、placeholder、format='webp'。
- 响应式图片:用 <NuxtPicture> 并设置 sizes='sm:100vw md:50vw lg:400px'、:modifiers='{ quality: 80 }'。
- 安装测试依赖:bun add -d @nuxt/test-utils vitest @vue/test-utils happy-dom。
- 创建 vitest.config.ts,从 @nuxt/test-utils/config 导入 defineVitestConfig。
- 在 Vitest 配置中设置 test.environment: 'nuxt'。
- 在 environmentOptions.nuxt.domEnvironment 中设置 'happy-dom'。
- 组件测试:从 @nuxt/test-utils/runtime 导入 mountSuspended。
- 组件测试:用 mountSuspended(UserCard, { props: { user: {...} } }) 挂载组件。
- 组件测试:用 expect(wrapper.text()).toContain('John Doe') 断言渲染内容。
- 组件测试:触发 [data-test='delete-btn'] 的 click,断言 wrapper.emitted('delete') 长度和参数。
- Mock composables:从 @nuxt/test-utils/runtime 导入 mockNuxtImport。
- Mock useFetch:mockNuxtImport('useFetch', () => () => ({ data: ref({ users: [{ id: 1, name: 'John' }] }), pending: ref(false), error: ref(null) }))。
- 在 Dashboard 测试中用 mountSuspended(Dashboard) 并断言显示 John。
- 测试服务端路由:从 @nuxt/test-utils/e2e 导入 $fetch 和 setup。
- 服务端路由测试:在 describe 中 await setup({ server: true })。
- 测试 GET /api/users:用 $fetch('/api/users'),断言有 users 属性且是数组。
- 测试 POST /api/users:传 { method: 'POST', body: { name: 'Jane', email: 'jane@example.com' } },断言 result.user.name 为 'Jane'。
- Cloudflare Pages 部署:运行 bun run build,然后 bunx wrangler pages deploy .output/public。
- Cloudflare Pages 配置:在 nuxt.config.ts 设置 nitro.preset: 'cloudflare-pages'。
- Cloudflare Workers 配置:在 nuxt.config.ts 设置 nitro.preset: 'cloudflare-module'。
- Cloudflare Workers 的 wrangler.toml:设置 name、compatibility_date、compatibility_flags = ['nodejs_compat']。
- Cloudflare Workers D1:配置 [[d1_databases]],设置 binding = 'DB'、database_name、database_id。
- Cloudflare Workers KV:配置 [[kv_namespaces]],设置 binding = 'KV'、id。
- Vercel 部署:在 nuxt.config.ts 设置 nitro.preset: 'vercel'。
- Netlify 部署:在 nuxt.config.ts 设置 nitro.preset: 'netlify'。
- NuxtHub 安装:bun add @nuxthub/core。
- NuxtHub 配置:在 nuxt.config.ts 的 modules 加 '@nuxthub/core'。
- NuxtHub 启用服务:在 hub 中设置 database: true、kv: true、blob: true、cache: true。
- NuxtHub 在 server route 中使用:用 hubDatabase()、hubKV()、hubBlob()。
- NuxtHub 数据库示例:const users = await db.prepare('SELECT * FROM users').all()。
- 环境变量开发:在 .env 设置 API_SECRET=dev-secret 和 DATABASE_URL=http://localhost:8787。
- Cloudflare 生产环境变量:用 wrangler secret put API_SECRET 和 wrangler secret put DATABASE_URL。
- Vercel/Netlify 生产环境变量:在 dashboard 或 CLI 中设置。
- 迁移步骤 1:在 package.json 的 devDependencies 中把 nuxt 更新为 ^4.0.0。
- 迁移步骤 2:在 nuxt.config.ts 设置 future.compatibilityVersion: 4。
- 迁移步骤 3:创建 app 目录:mkdir app。
- 迁移步骤 3:移动 components 到 app/。
- 迁移步骤 3:移动 composables 到 app/。
- 迁移步骤 3:移动 pages 到 app/。
- 迁移步骤 3:移动 layouts 到 app/。
- 迁移步骤 3:移动 middleware 到 app/。
- 迁移步骤 3:移动 plugins 到 app/。
- 迁移步骤 3:移动 assets 到 app/。
- 迁移步骤 3:移动 app.vue 到 app/。
- 迁移步骤 3:移动 error.vue 到 app/。
- 迁移步骤 4:如果修改 data.value 的属性,在 useFetch 中设置 deep: true 开启深响应式。
- 迁移步骤 4:或替换整个值:data.value = { ...data.value, name: 'New Name' }。
- 迁移步骤 5:更新默认值检查:v3 中 data.value === null,v4 中改用 !data.value。
- 避免客户端代码在服务端运行:错误直接 const width = window.innerWidth;正确用 if (import.meta.client) 包裹或 onMounted。
- 避免非确定性 SSR:错误直接 Math.random()、Date.now();正确用 useState('id', () => Math.random()) 和 useState('time', () => Date.now())。
- 异步组件必须配 Suspense:错误直接 <AsyncComponent />;正确用 <Suspense> 并给 #fallback 的 LoadingSpinner。
- 排查 hydration mismatch:检查 window、document、localStorage 的使用。
- 排查 hydration mismatch:用 ClientOnly 包裹或放到 onMounted。
- 排查 hydration mismatch:查找 Math.random()、Date.now()、crypto.randomUUID()。
- 排查构建错误:运行 rm -rf .nuxt .output node_modules/.vite && bun install。
- 排查部署失败:检查 nitro.preset 是否匹配目标平台。
- 排查部署失败:确认环境变量已设置。
- 排查部署失败:检查 wrangler.toml bindings 与代码一致。
- 排查测试失败:确保已安装 @nuxt/test-utils。
- 排查测试失败:检查 vitest.config.ts 中 environment: 'nuxt'。
- 排查测试失败:异步组件使用 mountSuspended。
适用边界 · 注意事项
- 不适用于非 Nuxt 4 项目或无法升级到 Nuxt 4 的 Nuxt 3 项目。
- 不适用于不想使用 Vitest 或 @nuxt/test-utils 测试体系的项目。
- 部署章节不适用于不想依赖 Cloudflare/Vercel/Netlify/NuxtHub 等云平台的项目。