
はじめに
こんにちは! STORES で Web エンジニアをしている @m0nch1 です。
STORES にはさまざまなプロダクトが存在しますが、Nuxt を使っているプロダクトが複数あります。
使っているプロダクトの一部画面を紹介しますね。


さて、STORES の Nuxt プロダクトは全て Nuxt4 へのアップグレードが完了しました。
どのように進めたのか、大変なことはあったかなどこれから Nuxt4 へのアップデートをする方の一助になればと思ってこれを書いています。
ぜひ参考にしていただければと思います!
そもそも
一応、なぜ Nuxt4 にあげるのか?というモチベーションを再確認しておきましょう。

この通り、Nuxt3 は 2026年1月に EOL を迎えるのです。
進め方
① キックオフ / キャッチアップ
まずはメンバーで集まりキックオフとキャッチアップをしました。(今回はエンジニア3名で対応しました)
集まったエンジニアは皆それぞれプロジェクトの開発もあったため、Nuxt4 アップグレードは同時並行で行う必要がありました。
そういった側面からも AI をフル活用して進めるという方針で合意し、進めていくことにしました。

キャッチアップに関してですが、公式ドキュメントをみんなで読んで変更点の理解やアップグレードの方法を確認しました。
中には簡単なサンプルを作って動作を検証したいものも出てきましたし、影響が大きそうな変更に当たりをつけることもできました。
ここで全員の認識が揃ったのはよかったですね。レビュー時の認知負荷も少し下がります。
そしてここが重要。
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-issues 使ってますか?)
要注意ポイント
vite v7
Nuxt4 では vite は v7 になります。
nuxt/package.json at main · nuxt/nuxt · GitHub
vite v7 では Sass legacy API が削除されるため、Sass の @import を使っているところがあると実はこれが移行のブロッカーになります。
弊社プロダクトでは @import を使っているところが多数あったためこれに対する対応も行う必要がありました。
import maps
Nuxt 4.1 から Import maps によるビルドの安定性が向上しています。(v3.19.0 でもこの変更が入っています)
詳しくは Nuxt Blog をみて欲しいのですが、利用者環境がネイティブの import maps に対応しているブラウザである必要があります。

特に Safari に関しては対応していないバージョンを使っているユーザーもまだ少数ながらいるかもしれませんので要注意です。
なお、vite.build.target で import maps に未対応のバージョンを指定した場合は自動的に無効になるようです。
意図的に無効にしたい場合はフラグが用意されています。(あまり使いたくはないですが)
export default defineNuxtConfig({
experimental: {
entryImportMap: false
}
})
移行を終えて
ありがとう nuxt
まずは nuxt team にシャウトアウトしたい!
こんな discussion もありますが、段階的に移行ができるのが本当に助かりました!
感謝っ・・・・! 圧倒的感謝っ・・・・!
AI 活用
そして AI を活用して進めた点も良かったです。


影響範囲の調査はやはり得意な印象でしたが、一方で細かく情報や指示を渡さないと想定外の差分が発生してしまうことがありました。
特に公式ドキュメントの情報は URL で渡すとあまりうまくいかなかったですね。
この辺りは llms.txt を使った方が良い結果になりそうという学びがありました。 Nuxt 公式にも用意されていましたので、ドキュメントの情報を渡す際にはこちらを使うことをオススメします!
https://nuxt.com/llms-full.txt
おわりに
いかがでしたか。
きっとこの記事を読み終わるころには compatibilityVersion: 4 にする PR を出しているはずです!
import maps のようにユーザーの体験もよくなる更新も入ってきていますし、開発体験向上を目的とした変更も多くあります。
とにかく「アップグレードがしやすいよ!」ということが伝わっていると嬉しいです!
さてみなさん...次は Nuxt5 ですよ!!