
Nuxt 4 徹底解説 - app/ ディレクトリ・データ層の刷新・TypeScript 分離と Nuxt 5 への準備
Nuxt 4 が 2025-07-16 に正式リリースされ、2026 年に入ってからは 4.4 系まで安定的にアップデートが続いています。Nuxt 3 のメンテナンスが 2026 年 1 月末で終了したことで、本番運用しているプロジェクトもそろそろ移行を本格検討するタイミングです。
Nuxt 4 はメジャーリリースですが、テーマは「安定性重視(stability-focused)」。多くの破壊的変更は1 年以上前からコンパチビリティフラグでテスト可能になっていたもので、Nuxt 3 の最終形をそのままデフォルトに昇格させた、というのが実態に近い印象です。
本記事では、Nuxt 4 の主要な変更点と新機能を、Nuxt 3 からの移行視点で整理します。
Nuxt 4 のリリースタイムラインと位置づけ
軽くおさらいから。
| バージョン | リリース | ハイライト |
|---|---|---|
| Nuxt 4.0 | 2025-07-16 | app/ 構造・データ層シングルトン化・TypeScript 分離・CLI 高速化 |
| Nuxt 4.4 | 2026-03-12 | Vue Router v5・createUseFetch/createUseAsyncData・useAnnouncer・unrouting |
| Nuxt 3 系 | 〜2026-01 末 | メンテナンス終了。以降は Nuxt 4 / Nuxt 5 系に集約 |
| Nuxt 5 | 開発中 | 4.2+ から future.compatibilityVersion: 5 で段階的にテスト可能 |
「Nuxt 3 から Nuxt 4 への移行は基本的に穏やか」「Nuxt 4 を入れた上で Nuxt 5 への準備を始める」というのが 2026 年現在の実用的なスタンスです。
主な変更点(俯瞰)
まず全体像から。詳細は後続の各節で扱います。
| カテゴリ | 主な変更 | 影響度 |
|---|---|---|
| プロジェクト構造 | 新しい app/ ディレクトリがデフォルト | 大 |
| データフェッチング | useAsyncData / useFetch がシングルトン化、自動クリーンアップ、リアクティブキー | 中〜大 |
| TypeScript | コンテキスト単位(app / server / shared / config)で TS プロジェクトを分離 | 中 |
| CLI / 開発体験 | 内部ソケット化、Node.js v8 compile cache の活用で起動・HMR が高速化 | 中(体感) |
| Head 管理 | Unhead v2 へアップグレード | 小〜中 |
| SPA ローディング | SPA ローディングスクリーンの DOM 位置変更 | 小 |
| error.data | エラーペイロードを自動パース | 小 |
| 互換性削除 | @nuxt/kit から Nuxt 2 互換が削除 | モジュール作者向け |
| 4.4 系の追加 | Vue Router v5、createUseFetch、useAnnouncer、unrouting | 中 |
| Nuxt 5 準備 | future.compatibilityVersion: 5 で Vite Environment API、非同期 callHook 等を試せる | 任意 |
ここから 1 つずつ見ていきます。
1. 新しい app/ ディレクトリ構造
Nuxt 4 で最も体感に影響する変更がこれです。
何が変わったか
Nuxt 4 のデフォルト srcDir が app/ になりました。components/ composables/ pages/ layouts/ middleware/ plugins/ utils/ app.vue error.vue app.config.ts といった「Vue アプリの実体」がすべて app/ 配下に移動します。
一方、server/ modules/ layers/ public/ shared/ nuxt.config.ts はルート直下のまま。
例として、Nuxt 4 のデフォルト構造はこうなります。
my-nuxt-app/
├── app/ ← srcDir(Vue アプリ本体)
│ ├── assets/
│ ├── components/
│ ├── composables/
│ ├── layouts/
│ ├── middleware/
│ ├── pages/
│ ├── plugins/
│ ├── utils/
│ ├── app.config.ts
│ ├── app.vue
│ └── router.options.ts
├── content/ ← Nuxt Content(v2.13+)
├── layers/
├── modules/
├── public/
├── shared/ ← 新規。Vue / Nitro 両方から使える共有コード
│ ├── types/
│ └── utils/
├── server/ ← Nitro 側
│ ├── api/
│ ├── middleware/
│ ├── plugins/
│ ├── routes/
│ └── utils/
├── nuxt.config.ts
└── package.jsonエイリアスも更新され、~ / @ はapp/ を指すようになります。~/components/Foo.vue は app/components/Foo.vue を解決。
なぜ分けたのか
公式アップグレードガイドに 2 つの理由が挙げられています。
- パフォーマンス: 全コードがリポジトリ直下にあると、FS ウォッチャが
.git/やnode_modules/までスキャンしてしまい、特に macOS 以外で起動が遅くなる - IDE の型安全性:
server/と Vue アプリは全く違うコンテキスト(auto-imports / global types)で動く。同じディレクトリツリーに置くと、IDE が混線してオートコンプリートが効きにくくなる
実際に手元で 4.x プロジェクトを触ると、ストレージウォッチが軽くなったのと、server/ 配下で Vue 系 composable のサジェストが出てこなくなる(=正しく分離されている)のが分かります。
shared/ の使いどころ
新設の shared/ は「Vue 側からも Nitro 側からも使いたいコード」の置き場所です。shared/utils/* と shared/types/* は両側で自動 importされます。
export function formatYen(n: number): string {
return new Intl.NumberFormat('ja-JP', { style: 'currency', currency: 'JPY' }).format(n)
}これを app/pages/index.vue でも server/api/price.get.ts でも formatYen(1000) でそのまま呼べる、という構造になります。型を shared/types/ に集約しておけば、API レスポンス型をフロント・サーバーで共有できる、という Nuxt 3 時代に微妙に面倒だった部分が綺麗に解決します。
移行は強制ではない
「既存プロジェクトが app/pages/ を持っていない(=旧構造)」と検出された場合、Nuxt は自動で旧構造のままで動かしてくれます。移行は段階的に進めて OK。強制移行したい場合は codemod が用意されています。
npx codemod@latest nuxt/4/file-structure逆に「Nuxt 4 に上げたいけど構造はそのままがいい」場合は、nuxt.config.ts で旧来の srcDir を明示できます。
export default defineNuxtConfig({
srcDir: '.',
dir: { app: 'app' },
})2. データフェッチング層のシングルトン化
useAsyncData / useFetch 周りが、地味だけど影響範囲の広い改修を受けました。
何が変わったか
ポイントは 4 つ。
- 同じキーは ref を共有: 同じキーで呼ばれた
useAsyncData/useFetchは、data/error/statusref が自動的に共有される - 自動クリーンアップ: そのキーを使っている最後のコンポーネントが unmount されると、Nuxt がそのデータを破棄する(メモリリーク防止)
- リアクティブキー:
computed/ref/ getter 関数をキーに渡せるようになり、キーが変わったら自動でリフェッチ getCachedDataの制御強化:getCachedDataは毎回呼ばれるようになり、cause(initial/refresh:hook/refresh:manual/watch)を受け取れる
例: リアクティブキーでリフェッチ
<script setup lang="ts">
const route = useRoute()
const userId = computed(() => route.params.id as string)
const { data: user } = await useAsyncData(
() => `user-${userId.value}`,
() => $fetch(`/api/users/${userId.value}`),
)
</script>Nuxt 3 では watch: [userId] で明示的に再取得していたところが、キー自体がリアクティブになることで自然な書き方に。
例: getCachedData で「手動リフレッシュ時はキャッシュを使わない」
const { data } = useAsyncData('articles', fetchArticles, {
getCachedData: (key, nuxtApp, ctx) => {
if (ctx.cause === 'refresh:manual') return undefined
return nuxtApp.payload.data[key]
},
})「画面遷移時はキャッシュ使う、リロードボタン押下時は新規取得」みたいな細かい挙動が、宣言的に書けるようになりました。
注意: 同じキーでオプション衝突するとワーニング
シングルトン化の副作用として、同じキーで deep / transform / pick / getCachedData / default が違うと警告が出ます。
// NG: 同じ key 'users' で deep が違う
const { data: u1 } = useAsyncData('users', fetchUsers, { deep: false })
const { data: u2 } = useAsyncData('users', fetchUsers, { deep: true })対策は、キーごとに専用 composable を作って一元化すること。
export function useUsers() {
return useAsyncData('users', () => $fetch('/api/users'), {
deep: true,
transform: (list) => list.map((u) => ({ ...u, fetchedAt: Date.now() })),
})
}このパターン、Nuxt 3 でもベストプラクティスでしたが、Nuxt 4 ではほぼ事実上の必須慣習になります。
移行中の緊急回避
新挙動で詰まったら、いったん旧挙動に戻せます。
export default defineNuxtConfig({
experimental: {
granularCachedData: false,
purgeCachedData: false,
},
})3. TypeScript 設定のコンテキスト分離
Nuxt 4 は1 つの tsconfig.json で全部を扱うのをやめ、コンテキスト別に複数の TS プロジェクトを生成するようになりました。
具体的には .nuxt/ 配下に次のような設定が自動生成されます。
| コンテキスト | 対象 |
|---|---|
| app | app/ 配下の Vue アプリ |
| server | server/ 配下の Nitro / API |
| shared | shared/ 配下の共有コード |
| config | nuxt.config.ts などの設定ファイル |
これにより、
server/で Vue 系の composable がサジェストに出ない(逆も同じ)useFetchの型がapp/でのみ有効に- 設定ファイルからアプリ型を逆参照してしまう「型の循環」が解消
実用上、これまで雰囲気で書いていた server/ のコードが、エディタから「明らかにダメな import」を弾いてくれるようになり、ミスが減りました。
ただし「今まで隠れていた型エラーが浮上する」副作用は確実にあります。Nuxt 4 に上げた直後は、npm run typecheck を回して虱潰しに直す、というステップを覚悟しておいた方が良いです。
4. CLI / 開発サーバーの高速化
体感に直結するアップデートが地味に強烈です。
- 内部ソケットの採用: Nuxt CLI とビルダー間の通信が socket ベースに刷新
- Node.js v8 compile cache: モジュールのコンパイル結果を v8 キャッシュとして自動再利用
- ファイルウォッチャの高速化: 上記の
app/構造化と相まって、特に Linux / Windows で起動が短縮
これらの効果はプロジェクト規模が大きいほど顕著です。中規模以上のアプリでは、dev サーバー再起動 / HMR 反映の体感がワンランク変わる印象。
5. Unhead v2
useHead / useSeoMeta などのバックエンドが Unhead v2 にアップグレードされました。
破壊的変更は限定的で、基本的なユースケースは互換動作します。useSeoMeta の型がより厳格になっていたり、内部のソート挙動が変わっていたりはするので、E2E で OG 画像・メタタグの出力を確認しておくと安心。
6. SPA ローディングスクリーンの位置変更
これまでは <div id="__nuxt"> の中にローディングスクリーンが入っていましたが、Nuxt 4 では__nuxt の外に配置されるようになりました。CSS で #__nuxt > .loading のようにスタイル指定していた場合は調整が必要です。
7. error.data の自動パース
createError で data フィールドを JSON 文字列で渡していたケースで、クライアント側 error.data が自動で JSON.parse 済みのオブジェクトとして届くようになりました。Nuxt 3 では文字列のままだったので、JSON.parse(error.data) していたコードは不要になります。
8. Nuxt 4.4 で追加された注目機能
ここからは 4.4 系の追加分。Nuxt 4 を入れるなら4.4 以降にしておくと得です。
Vue Router v5
Nuxt 3 以来となる Vue Router のメジャーアップグレード。unplugin-vue-router への依存も外れ、内部実装がスリム化しました。アプリケーションコードから見える破壊的変更は最小限ですが、useRoute / useRouter の型がよりタイト化されています。
createUseFetch / createUseAsyncData
プロジェクト共通の useFetch をラップした自前 composable をきれいに作れるファクトリ関数。
export const useApiFetch = createUseFetch({
baseURL: '/api/v1',
retry: 2,
onResponseError({ response }) {
if (response.status === 401) navigateTo('/login')
},
})これまで useFetch を毎回オプション付きで呼んでいた / グローバルラッパーを自力で書いていた、という両方を綺麗に置き換えられます。共通エラーハンドリングや認証ヘッダー付与がスマートに。
useAnnouncer composable
アクセシビリティ向け。SPA のルート遷移時にスクリーンリーダー用にライブリージョンへメッセージを送る仕組みが、コアに入りました。
<script setup>
const { polite, assertive } = useAnnouncer()
function onSave() {
polite('保存しました')
}
</script>地味ですが、ARIA ライブリージョンを自前で組むのは結構面倒だったので、これは助かる人が多い追加。
unrouting
Pages のルート解決ロジックが unrouting に置き換わりました。大規模ルートで28 倍程度の高速化が報告されています。動的ルート([...slug].vue など)を多用するサイトでは効果が大きいはず。
賢くなったキャッシュ済みルートのペイロード処理
payloadExtraction: 'client' という新オプションが追加され、クライアントナビゲーション時にフルペイロードをハイドレーションしつつ、別途 _payload.json キャッシュも生成、という構成が選べるようになりました。SSG + SPA ナビゲーションのハイブリッド構成で速度面のチューニング余地が増えました。
9. Nuxt 5 を先取りする方法
Nuxt 4.2 以降では、future.compatibilityVersion: 5 を設定するとNuxt 5 のデフォルト挙動を先取りできます。
export default defineNuxtConfig({
future: {
compatibilityVersion: 5,
},
})これで先行有効化されるのは主に以下。
| 機能 | 内容 |
|---|---|
| Vite Environment API | Vite 6 の新環境 API へ移行。extendViteConfig の server / client オプションが非推奨に |
| Normalized Page Names | ページコンポーネント名がルート名と一致するように正規化 |
clearNuxtState のリセット挙動 | undefined ではなく初期値に戻る |
非同期 callHook | hookable v6 で Promise を必ず返さなくなる(最大 20-40 倍高速化) |
| Comment Node プレースホルダ | <ClientOnly> の SSR プレースホルダが <div> から HTML コメントへ |
特に Vite Environment API への移行は、Vite プラグインを自前で書いている / Nuxt モジュールを公開している場合に影響が大きく、早めに 5 互換でテストしておくと Nuxt 5 リリース時の移行が楽になります。
addVitePlugin(() => ({
name: 'my-plugin',
configEnvironment(name, config) {
if (name === 'client') {
config.optimizeDeps ||= {}
config.optimizeDeps.include ||= []
config.optimizeDeps.include.push('my-package')
}
},
applyToEnvironment(environment) {
return environment.name === 'client'
},
}))移行手順の現実的なベストプラクティス
実プロジェクトを移行するときの推奨フロー。
-
Nuxt 3 を最新パッチに上げる(
compatibilityVersion: 4を有効化して挙動を検証) -
npm install nuxt@^4.0.0でアップグレード -
codemod を流す(任意)
npx codemod@0.18.7 nuxt/4/migration-recipe -
npm run typecheckで TS 分離による型エラーを潰す -
同じキーの
useAsyncData/useFetchをcomposable に集約 -
CI で動作確認、問題があれば一旦
experimental.granularCachedData: falseなどで暫定回避 -
落ち着いたら4.4 系へ更新(Vue Router v5 / unrouting の恩恵を取りに行く)
-
余力があれば
future.compatibilityVersion: 5を別ブランチで試して Nuxt 5 に備える
ポイントは「Nuxt 4 の破壊的変更の多くは事前にコンパチビリティフラグで試せていた」こと。Nuxt 3.10 以降の最新版を使っていたプロジェクトなら、実際の差分はそれほど大きくないはずです。
詰まりやすいポイント
実プロジェクトでよく踏むトラブルを列挙。
| 症状 | 原因 / 対策 |
|---|---|
~ の解決が外れて import エラー | エイリアス先が app/ になった影響。tsconfig / eslint / tailwindcss など外部設定を新構造に追従させる |
| 同じキーで違うオプションを使っていて警告 | キー専用 composable に集約。または experimental.granularCachedData: false で一時退避 |
server/ で型エラーが急に増える | TS 分離で隠れていたミスが浮上。基本的にはコード側を直す |
| Vite プラグインが効かない | server: false / client: false 廃止予定。applyToEnvironment への移行を検討 |
| ヘッドタグの順序が変わった | Unhead v2 のソート規則。useHead の tagPosition / key で明示制御 |
| SPA ローディングスクリーンの位置 | #__nuxt の外に出たので CSS セレクタを修正 |
まとめ
- Nuxt 4 は「Nuxt 3 の最終形をデフォルト化したリリース」。安定性重視で破壊的変更は穏やか
- 最大の変更は
app/ディレクトリ構造。FS ウォッチャの軽量化と IDE の型安全性が改善 - データ層はシングルトン化+自動クリーンアップ+リアクティブキーに進化。同じキーは専用 composable に集約するのが鉄則
- TypeScript はコンテキスト別に分離。隠れていた型エラーが浮上する代わりに、補完精度は向上
- CLI / 開発体験は内部ソケット化と v8 compile cacheで体感アップ
- 4.4 系では Vue Router v5 /
createUseFetch/useAnnouncer/ unrouting が追加。Nuxt 4 を入れるなら最新パッチで - Nuxt 5 は
future.compatibilityVersion: 5で先取り可能。Vite Environment API への移行は早めに着手すると吉 - Nuxt 3 のメンテナンスは 2026 年 1 月末で終了済み。本番運用しているなら、まず 4.x への移行計画を立てるところから
「Nuxt 4 はメジャーバージョンの割には大人しい」というのが触ってみての率直な感想です。Nuxt 5 のための地ならしとして組み込まれている改善が多いので、ここで一度上げておくと次の Nuxt 5 がぐっと楽になります。