NuxtNuxt UIVueSSRMigration GuideFrontend Engineering

Nuxt 4.4 與 Nuxt UI 4.6 升級實戰:Nuxt 3 平滑遷移指南

從 Nuxt 3 升級到 Nuxt 4.4 與 Nuxt UI 4.6 的實戰路線,整理目錄調整、元件 API 變更、SSR 風險控管與可回滾部署 SOP。

· 6 分鐘閱讀

這篇會帶你用「先穩定再收斂」的方式,把 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 latest4.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:

  1. UButtonGroup -> UFieldGroup
  2. UPageMarquee -> UMarquee
  3. v-model.nullify -> v-model.nullable(另新增 optional
  4. UForm 巢狀表單需顯式 nestedname
# 先把高機率會壞的用法列出來
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 行為時才設 54 本身不是這篇遷移的風險控管開關。

建議做法是: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 對巢狀表單更嚴謹,需明確標註 nestedname。這段很容易在 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

  1. PR-A:Nuxt 4 核心遷移(目錄、config、codemod 結果)
  2. PR-B:Nuxt UI 4.6 + 元件 API/表單修正
  3. PR-C:cleanup(刪除臨時相容設定、更新文件)

每個 PR 都可獨立回滾,事故定位會快很多。

CI Gate 建議

# package.json scripts 範例
pnpm lint
pnpm typecheck
pnpm test
pnpm build

如果你有 E2E,至少保留一組 SSR smoke case:首頁、登入頁、含 UForm 的關鍵流程頁。

灰度與回滾策略

  1. 先 deploy 到 staging,觀察 hydration warning 與 error rate。
  2. production 先小流量(例如內部帳號或低風險租戶)放量。
  3. 若出現 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 時間。