STORES Product Blog

こだわりを持ったお商売を支える「STORES」のテクノロジー部門のメンバーによるブログです。

Nuxt4 アップグレードのススメ

はじめに

こんにちは! STORES で Web エンジニアをしている @m0nch1 です。

STORES にはさまざまなプロダクトが存在しますが、Nuxt を使っているプロダクトが複数あります。

使っているプロダクトの一部画面を紹介しますね。

オーダー管理β

顧客管理

さて、STORES の Nuxt プロダクトは全て Nuxt4 へのアップグレードが完了しました。

どのように進めたのか、大変なことはあったかなどこれから Nuxt4 へのアップデートをする方の一助になればと思ってこれを書いています。

ぜひ参考にしていただければと思います!

そもそも

一応、なぜ Nuxt4 にあげるのか?というモチベーションを再確認しておきましょう。

Nuxt Roadmap
Roadmap · Nuxt Community v4

この通り、Nuxt3 は 2026年1月に EOL を迎えるのです。

進め方

① キックオフ / キャッチアップ

まずはメンバーで集まりキックオフとキャッチアップをしました。(今回はエンジニア3名で対応しました)

集まったエンジニアは皆それぞれプロジェクトの開発もあったため、Nuxt4 アップグレードは同時並行で行う必要がありました。

そういった側面からも AI をフル活用して進めるという方針で合意し、進めていくことにしました。

基本方針が書いてる Notion

キャッチアップに関してですが、公式ドキュメントをみんなで読んで変更点の理解やアップグレードの方法を確認しました。

nuxt.com

中には簡単なサンプルを作って動作を検証したいものも出てきましたし、影響が大きそうな変更に当たりをつけることもできました。

ここで全員の認識が揃ったのはよかったですね。レビュー時の認知負荷も少し下がります。

そしてここが重要。

Nuxt4 のアップグレードは Nuxt3 の package バージョンのまま 機能ごとにオプトイン することができるようになっているのです。つまり段階的に移行ができるということなのです。

この時点でもう心が穏やかになっているのです。すでに nuxt team に感謝の祈りを捧げていました。

ちなみにこの辺りは Nuxt Ecosystem Team のメンバーである wattanx がリードしてくれました!本当に心強い。

② 移行の準備

段階的に移行をしていくために compatibilityVersion の設定をします。

nuxt config に書くだけです。

export default defineNuxtConfig({
  future: {
    compatibilityVersion: 4,
  },
})

これだけで PR を作成すると CI がはちゃめちゃにこけたので何かしらのマイグレーションが必要なことがわかりました。

あえて一度 CI を落として想定箇所を見ておくのもいいかもしれませんね。

次に、機能ごとに細かくオプトインのフラグが用意されているので、設定可能なフラグをすべて OFF(つまり v3 の挙動)にした状態でリリースし、個別にアップグレードしていけるようにします。

個別に対応していけるので複数人並行(AI を含め)での作業もしやすくなります。

config 全体の設定イメージとしては以下のような感じです。

export default defineNuxtConfig({
  future: {
    compatibilityVersion: 4,
  },

  // New Directory Structure
  srcDir: '.',
  dir: {
    app: 'app',
  },

  experimental: {
    granularCachedData: false, // Singleton Data Fetching Layer
    purgeCachedData: false, // Singleton Data Fetching Layer
    normalizeComponentNames: false, // Normalized Component Names
    spaLoadingTemplateLocation: 'within',  // New DOM Location for SPA Loading Screen
    parseErrorData: false,  // Parsed error.data
    scanPageMeta: true, // Scan Page Meta After Resolution
    sharedPrerenderData: false, // Shared Prerender Data
    defaults: {
      useAsyncData: {
         // Default data and error values in useAsyncData and useFetch
        value: 'null',
        errorValue: 'null',
        // Shallow Data Reactivity in useAsyncData and useFetch
        deep: true,
      },
    },
    resetAsyncDataToUndefined: true, // Respect defaults when clearing data in useAsyncData and useFetch
    pendingWhenIdle: true, // Alignment of pending value in useAsyncData and useFetch
    alwaysRunFetchOnKeyChange: true, // Key Change Behavior in useAsyncData and useFetch
  },

  // Unhead v2
  unhead: {
    legacy: true,
  },

  // More Granular Inline Styles
  features: {
    inlineStyles: true,
  },

  // Default TypeScript Configuration Changes
  typescript: {
    tsConfig: {
      compilerOptions: {
        noUncheckedIndexedAccess: false,
      },
    },
  },
})

一点注意が必要で、設定可能なフラグ と言った通りトグルが用意されていない変更もあります。

これらについては compatibilityVersion: 4 を設定してリリースした時点で v4 compartible な状態で動くわけですから、影響範囲を確認する必要がありますのでご注意ください。

進捗の度合いを可視化する意味合いも込めて、GitHub に issue を準備もしました。

sub issue がたくさんあるの図

(ところでみなさん sub-issues 使ってますか?)

product.st.inc

要注意ポイント

vite v7

Nuxt4 では vite は v7 になります。

nuxt/package.json at main · nuxt/nuxt · GitHub

vite v7 では Sass legacy API が削除されるため、Sass の @import を使っているところがあると実はこれが移行のブロッカーになります。

ja.vite.dev

弊社プロダクトでは @import を使っているところが多数あったためこれに対する対応も行う必要がありました。

import maps

Nuxt 4.1 から Import maps によるビルドの安定性が向上しています。(v3.19.0 でもこの変更が入っています)

詳しくは Nuxt Blog をみて欲しいのですが、利用者環境がネイティブの import maps に対応しているブラウザである必要があります。

2025年11月時点の can i use

特に Safari に関しては対応していないバージョンを使っているユーザーもまだ少数ながらいるかもしれませんので要注意です。

なお、vite.build.target で import maps に未対応のバージョンを指定した場合は自動的に無効になるようです。

意図的に無効にしたい場合はフラグが用意されています。(あまり使いたくはないですが)

export default defineNuxtConfig({
  experimental: {
    entryImportMap: false
  }
})

移行を終えて

ありがとう nuxt

まずは nuxt team にシャウトアウトしたい!

こんな discussion もありますが、段階的に移行ができるのが本当に助かりました!

感謝っ・・・・! 圧倒的感謝っ・・・・!

AI 活用

そして AI を活用して進めた点も良かったです。

GHA 経由で claude を使っているようす

Devin が PR を作っているようす

影響範囲の調査はやはり得意な印象でしたが、一方で細かく情報や指示を渡さないと想定外の差分が発生してしまうことがありました。

特に公式ドキュメントの情報は URL で渡すとあまりうまくいかなかったですね。

この辺りは llms.txt を使った方が良い結果になりそうという学びがありました。 Nuxt 公式にも用意されていましたので、ドキュメントの情報を渡す際にはこちらを使うことをオススメします!

https://nuxt.com/llms-full.txt

おわりに

いかがでしたか。

きっとこの記事を読み終わるころには compatibilityVersion: 4 にする PR を出しているはずです!

import maps のようにユーザーの体験もよくなる更新も入ってきていますし、開発体験向上を目的とした変更も多くあります。

とにかく「アップグレードがしやすいよ!」ということが伝わっていると嬉しいです!

さてみなさん...次は Nuxt5 ですよ!!