📋 目錄
這篇會帶你用「先穩定再收斂」的方式,把 Nuxt 3 專案升到 Nuxt 4.4 並導入 Nuxt UI 4.6,同時保留灰度與回滾空間。
前言
截至 2026-04-01,npm registry 的 nuxt latest dist-tag 是 4.4.2(published at 2026-03-12 UTC),@nuxt/ui latest 是 4.6.0(published at 2026-03-23 UTC)。這兩個版本靠得很近,對維運中的 Nuxt 3 專案來說,意味著「框架升級」和「設計系統升級」很容易在同一個季度一起發生。
真正困難的地方不是指令怎麼下,而是怎麼避免一次改太多:目錄結構、路由與 data API、UI 元件命名、表單 state 行為,全都可能一起影響 SSR 輸出與回歸測試。
這篇文章的主軸是工程落地,不是版本介紹。我會把流程拆成四段:inventory -> Nuxt 4 核心遷移 -> Nuxt UI 4.6 遷移 -> rollout/rollback,最後給你一份可直接放進 CI 的檢查清單。
為什麼現在是升級窗口:先看兩個可驗證訊號
1. Nuxt 4.4 已是穩定線,不是 preview
Nuxt 官方 upgrade 文件已以 4.x 為主線,升級入口是 nuxt upgrade 或直接安裝 nuxt@^4.0.0。這代表從工具鏈角度,Nuxt 3 -> 4 已經不是「提早嘗鮮」,而是主線維運議題。
# 建議先在升級分支執行,不要直接在 main 動手
pnpm nuxt upgrade
# 或明確指定 major
pnpm add nuxt@^4.0.0
2. Nuxt UI 4.6 明確要求 Nuxt 4 相依
Nuxt UI v4 migration 文件直接寫明:Nuxt UI v4 需要 Nuxt 4。換句話說,如果你的產品 roadmap 已經要吃 Nuxt UI v4(例如新組件、統一主題 token),那 Nuxt 4 幾乎是前置條件,不是可選項。
第 0 階段:先做 inventory,避免升級時才發現爆點
先盤點,才能決定你要「同批升級」還是「拆兩批升級」。我建議至少先掃三類風險。
A. 檔案結構與 alias 依賴
Nuxt 4 的預設 srcDir 變成 app/,而 server/、public/、modules/、layers/ 都回到專案 root。若你有大量 ~/ 路徑、客製 lint 規則或 Tailwind source 設定,會直接受到影響。
# 找可能受目錄遷移影響的 alias 與設定檔
rg -n "from ['\"]~\/|@source|tailwind|eslint|router.options" .
B. Nuxt UI 舊元件與舊 modifier
Nuxt UI v4 有幾個常見 breaking/rename:
UButtonGroup->UFieldGroupUPageMarquee->UMarqueev-model.nullify->v-model.nullable(另新增optional)UForm巢狀表單需顯式nested與name
# 先把高機率會壞的用法列出來
rg -n "UButtonGroup|UPageMarquee|v-model\.nullify|model-modifiers|<UForm" app src
C. CI gate 是否真的能擋回歸
很多升級失敗不是因為程式不能改,而是 CI 沒把「SSR + 型別 + E2E smoke」串成同一條 gate,最後回歸出現在上線後。
第 1 階段:Nuxt 3 -> Nuxt 4.4 核心遷移(先框架,後 UI)
這一階段目標是「框架先穩」,暫時不要把 Nuxt UI 大改一起混進來。
Step 1:先跑官方 codemod recipe
Nuxt 升級指南提供 nuxt/4/migration-recipe。官方目前建議使用 pinned 版本 codemod@0.18.7。
# 官方目前建議的 pinned 版本
npx codemod@0.18.7 nuxt/4/migration-recipe
Step 2:調整目錄結構與設定檔
下面是 Nuxt 4 建議結構(重點:app/ 與 root-level server//shared/):
app/
assets/
components/
composables/
layouts/
middleware/
pages/
plugins/
utils/
app.vue
server/
shared/
public/
nuxt.config.ts
如果你同時有 Tailwind 或 Content 掃描規則,記得一起修正 @source 或 glob 路徑,否則 build 過了也可能出現 class 遺失。
Step 3:不要把 compatibilityVersion: 4 當成 migration 開關
這是最容易誤讀的一段。future.compatibilityVersion 在官方升級文件裡,主要是用來「提早測 Nuxt 5 行為」的 opt-in;也就是你要測 Nuxt 5 行為時才設 5。4 本身不是這篇遷移的風險控管開關。
建議做法是:Nuxt 4 升級先專注在目錄、型別、SSR 與核心流程穩定;如果你要預演 Nuxt 5,再另外開 canary branch 設 compatibilityVersion: 5。
// nuxt.config.ts
export default defineNuxtConfig({
// 只在獨立測試分支啟用 Nuxt 5 行為預演
future: {
compatibilityVersion: 5,
},
});
如果你的目標是「Nuxt 3 -> Nuxt 4 平滑遷移」,可以完全不設定這段,先把 Nuxt 4 主流程跑穩。
第 2 階段:導入 Nuxt UI 4.6(設計系統遷移)
Nuxt UI 的遷移不要只做「套件升級」,要把元件 API 與表單行為一併驗證。
Step 1:安裝與基礎設定
pnpm add @nuxt/ui tailwindcss
// nuxt.config.ts
export default defineNuxtConfig({
modules: ["@nuxt/ui"],
css: ["~/assets/css/main.css"],
ui: {
theme: {
// Nuxt UI 官方 installation 文件的 `theme.prefix`(4.2+)
// 必須和 CSS 裡的 tailwind prefix(tw) 一致
prefix: "tw",
},
},
});
/* app/assets/css/main.css */
@import "tailwindcss" prefix(tw);
@import "@nuxt/ui";
/* 若有 Nuxt Content,Nuxt 4 目錄下路徑通常要一起調整 */
@source "../../../content/**/*";
Step 2:批次替換高風險 API
<!-- Before -->
<UButtonGroup>
<UButton label="儲存" />
<UButton icon="i-lucide-chevron-down" />
</UButtonGroup>
<!-- After -->
<UFieldGroup>
<UButton label="儲存" />
<UButton icon="i-lucide-chevron-down" />
</UFieldGroup>
<!-- Before -->
<UInput v-model.nullify="form.email" />
<!-- After -->
<UInput v-model.nullable="form.email" />
Step 3:特別注意 UForm 巢狀行為
Nuxt UI v4 的 UForm 對巢狀表單更嚴謹,需明確標註 nested 與 name。這段很容易在 checkout 流程或複雜表單裡踩雷。
<script setup lang="ts">
import { reactive } from "vue";
const state = reactive({
customer: "",
items: [{ description: "", price: 0 }],
});
</script>
<template>
<UForm :state="state" @submit="() => {}">
<UFormField label="Customer" name="customer">
<UInput v-model="state.customer" />
</UFormField>
<div v-for="(item, index) in state.items" :key="index">
<!-- v4 重點:巢狀表單要明確 name + nested -->
<UForm :name="`items.${index}`" nested>
<UFormField name="description" :label="index === 0 ? 'Description' : undefined">
<UInput v-model="item.description" />
</UFormField>
<UFormField name="price" :label="index === 0 ? 'Price' : undefined">
<UInput v-model="item.price" type="number" />
</UFormField>
</UForm>
</div>
</UForm>
</template>
第 3 階段:可回滾遷移 SOP(Branch、灰度、回滾)
這段是最關鍵。只靠「測過本機」不足以保證升級安全。
建議分三個 PR
- PR-A:Nuxt 4 核心遷移(目錄、config、codemod 結果)
- PR-B:Nuxt UI 4.6 + 元件 API/表單修正
- PR-C:cleanup(刪除臨時相容設定、更新文件)
每個 PR 都可獨立回滾,事故定位會快很多。
CI Gate 建議
# package.json scripts 範例
pnpm lint
pnpm typecheck
pnpm test
pnpm build
如果你有 E2E,至少保留一組 SSR smoke case:首頁、登入頁、含 UForm 的關鍵流程頁。
灰度與回滾策略
- 先 deploy 到 staging,觀察 hydration warning 與 error rate。
- production 先小流量(例如內部帳號或低風險租戶)放量。
- 若出現 P1 以上問題,直接回退到 PR-A 穩定點,不要在 production 熱修大量 API rename。
一份可落地的升級檢查清單
- [ ] 已完成 Nuxt 4 codemod,並人工 review 差異
- [ ] `app/`、`server/`、`shared/` 結構已對齊 Nuxt 4
- [ ] Tailwind / @source / ESLint glob 已更新
- [ ] Nuxt UI 套件與 `@nuxt/ui` 模組已啟用
- [ ] `UButtonGroup`、`UPageMarquee`、`nullify` 等舊 API 已清空
- [ ] 巢狀 `UForm` 已補 `name` 與 `nested`
- [ ] CI 已串 lint + typecheck + test + build
- [ ] Staging SSR smoke 與 hydration 檢查通過
- [ ] Production 灰度與回滾點已預先定義
常見問題 / 注意事項
Q1:可以一次把 Nuxt 4 和 Nuxt UI 4.6 一起升嗎?
可以,但不建議在同一個 PR 一次完成。建議拆成至少兩批,否則 regressions 來源很難定位。
Q2:只升 Nuxt UI 到 v4,不升 Nuxt 4 可以嗎?
不建議。Nuxt UI v4 文件已明確要求 Nuxt 4 相依。
Q3:compatibilityVersion 要不要直接設 5?
只有在你要「提前測 Nuxt 5 行為」時才建議設 5,而且要在 canary 或獨立分支做。單純 Nuxt 3 -> 4 遷移不需要把它當預設步驟。
Q4:怎麼最快找出 UI 破壞性變更?
先用 rg 掃舊元件名與舊 modifier,再跑一輪關鍵表單的互動測試。多數問題會在這步就浮出來。
總結
Nuxt 4.4 × Nuxt UI 4.6 的升級重點,不是「把版本號改掉」,而是把風險拆小:先盤點、先框架、再 UI、最後 rollout。只要把 PR 切分與回滾點設計好,這次遷移其實可以非常工程化。
如果你要今天就啟動,建議第一步是開一條 upgrade/nuxt4-core 分支,先跑 codemod + 目錄調整,等 CI 穩了再開第二條 upgrade/nuxt-ui4。這樣做雖然多一次 PR,但能省下大量 incident 時間。