Nuxt 4 — це не повне переписування фреймворку, як свого часу було під час переходу з Nuxt 2 на Nuxt 3. Четверта версія продовжує використовувати Vue 3, Nitro, файлову маршрутизацію, автоматичні імпорти, SSR і статичну генерацію.
Основні зміни стосуються структури проєкту, роботи з даними, підтримки TypeScript і швидкості локальної розробки.
Стабільна версія Nuxt 4 вийшла 15 липня 2025 року. Команда Nuxt позиціонує її як реліз, орієнтований насамперед на стабільність і покращення developer experience. Частину можливостей Nuxt 4 поступово додавали ще в пізні версії Nuxt 3, тому різниця часто полягає не в наявності певної функції, а в її новій типовій поведінці. Офіційний анонс Nuxt 4.
Станом на серпень 2026 року Nuxt 3 уже досяг кінця життєвого циклу. Офіційною датою EOL було 31 липня 2026 року. Це означає, що для нових проєктів і застосунків, які активно розвиваються, варто використовувати Nuxt 4. Офіційна дорожня карта Nuxt.
Нова структура каталогів
Найпомітніша зміна в Nuxt 4 — поява каталогу app/, у якому тепер розміщується основний код Vue-застосунку.
У Nuxt 3 каталоги components, pages, composables, layouts, plugins, middleware, assets та utils зазвичай розташовувалися безпосередньо в корені проєкту.
У Nuxt 4 їх рекомендовано переносити в app/:
my-project/
├── app/
│ ├── assets/
│ ├── components/
│ ├── composables/
│ ├── layouts/
│ ├── middleware/
│ ├── pages/
│ ├── plugins/
│ ├── utils/
│ ├── app.vue
│ ├── app.config.ts
│ └── error.vue
├── content/
├── layers/
├── modules/
├── public/
├── server/
├── shared/
├── nuxt.config.ts
└── package.json
Каталоги server, public, shared, modules, layers і content залишаються в корені проєкту.
Такий поділ допомагає Nuxt не сканувати зайві каталоги на кшталт .git і node_modules. Це може пришвидшити запуск dev-сервера та роботу файлових спостерігачів, особливо у Windows і Linux.
Крім того, IDE краще розуміє контекст коду. Вона може точніше визначати, де використовується клієнтський код, де серверний, а де спільні типи та утиліти.
Після переходу потрібно врахувати, що псевдонім ~ за замовчуванням вказує на каталог app/. Наприклад, шлях ~/components відповідає app/components.
Через це варто перевірити:
- ручні імпорти;
- конфігурацію Tailwind CSS;
- ESLint;
- тестове середовище;
- aliases у TypeScript;
- скрипти збірки;
- конфігурації сторонніх бібліотек.
Переносити каталоги одразу не обов’язково. Nuxt 4 уміє розпізнавати стару структуру Nuxt 3 і підтримує зворотну сумісність.
Окрему увагу потрібно приділити проєктам із власним srcDir. У Nuxt 4 каталоги server, public, shared і modules визначаються відносно rootDir, а не користувацького srcDir. Докладніше про нову структуру.
Зміни в useFetch і useAsyncData
Робота з даними — одна з найважливіших частин міграції. Зміни можуть не спричинити помилку збірки, але вплинути на поведінку інтерфейсу, кешування або повторне виконання запитів.
Спільні дані для однакових ключів
У Nuxt 4 виклики useAsyncData і useFetch з однаковим ключем використовують спільні data, error і status.
Наприклад, два компоненти можуть отримувати користувачів із ключем users. Обидва компоненти працюватимуть з одним набором реактивних посилань.
Водночас налаштування таких викликів повинні збігатися:
const { data: usersA } = useAsyncData(
'users',
() => $fetch('/api/users'),
{ deep: false },
)
const { data: usersB } = useAsyncData(
'users',
() => $fetch('/api/users'),
{ deep: true },
)
У цьому прикладі для одного ключа передано різні значення deep. Nuxt 4 попереджатиме про таку невідповідність.
Для однакового ключа мають бути узгоджені:
- функція отримання даних;
deep;transform;pick;getCachedData;default.
Якщо один запит використовується в декількох компонентах, його краще винести в окремий composable.
Nuxt також очищає дані після демонтування останнього компонента, який їх використовував. Це допомагає уникнути поступового зростання споживання пам’яті.
Через таку поведінку useAsyncData не слід використовувати як постійне глобальне сховище. Для довготривалого стану краще застосовувати useState або Pinia.
shallowRef замість глибокої реактивності
У Nuxt 4 значення data, яке повертають useAsyncData, useFetch, useLazyAsyncData і useLazyFetch, за замовчуванням є shallowRef.
Заміна всього об’єкта залишається реактивною. Проте зміна вкладеної властивості може не спричинити оновлення компонента:
const { data } = await useFetch('/api/profile')
// Зміна може не оновити інтерфейс
data.value.user.name = 'Олена'
Один зі способів розв’язання проблеми — замінити весь об’єкт:
data.value = {
...data.value,
user: {
...data.value.user,
name: 'Олена',
},
}
Якщо застосунку потрібна глибока реактивність, її можна ввімкнути для конкретного запиту:
const { data } = await useFetch('/api/profile', {
deep: true,
})
Використання shallowRef зменшує навантаження на Vue під час роботи з великими вкладеними об’єктами й масивами. Для API-відповідей, які не змінюються безпосередньо на клієнті, це ефективніша типова поведінка.
undefined замість null
У Nuxt 3 початковим значенням data та error часто було null. У Nuxt 4 ці значення за замовчуванням дорівнюють undefined.
Тому старі перевірки потрібно переглянути:
if (data.value === null) {
// Старий варіант
}
Новий варіант:
if (data.value === undefined) {
// Дані ще не отримано
}
Також можна використовувати загальну перевірку відсутності значення, якщо бізнес-логіка не розрізняє null і undefined.
Нова поведінка pending
У Nuxt 4 pending дорівнює true тільки тоді, коли запит фактично виконується.
Якщо передати immediate: false, початкове значення pending буде false. У Nuxt 3 воно могло залишатися true до першого запиту.
Для керування інтерфейсом краще використовувати status:
<template>
<Loader v-if="status === 'pending'" />
<ErrorMessage v-else-if="status === 'error'" />
<Result
v-else-if="status === 'success'"
:data="data"
/>
</template>
Такий підхід чітко розділяє стани очікування, успішного завантаження та помилки.
Зміни в dedupe
У викликах refresh більше не рекомендується використовувати логічні значення true або false для dedupe.
Замість них потрібно передавати зрозуміліші рядкові значення:
await refresh({ dedupe: 'cancel' })
або:
await refresh({ dedupe: 'defer' })
Це усуває неоднозначність між налаштуваннями самого useAsyncData та параметрами його повторного виконання.
Реактивні ключі
У Nuxt 4 ключ useAsyncData може бути реактивним. Після його зміни Nuxt автоматично працюватиме з іншим набором даних.
const route = useRoute()
const key = computed(() => `article-${route.params.slug}`)
const { data } = await useAsyncData(
key,
() => $fetch(`/api/articles/${route.params.slug}`),
)
Якщо встановлено immediate: false, перший запит потрібно запустити вручну через execute або refresh. Зміна ключа не повинна несподівано запускати запит до першого виконання.
Спільні дані під час prerendering
Nuxt 4 може повторно використовувати дані між сторінками, які генеруються заздалегідь. Це пришвидшує prerendering, якщо декілька сторінок отримують однакові налаштування сайту, меню або дані CMS.
Водночас один ключ повинен завжди відповідати одним і тим самим даним. Не можна використовувати статичний ключ для різних динамічних сторінок:
const route = useRoute()
// Небезпечний варіант
const { data } = await useAsyncData(
'article',
() => $fetch(`/api/articles/${route.params.slug}`),
)
Ключ повинен містити параметр сторінки:
const route = useRoute()
const { data } = await useAsyncData(
() => `article-${route.params.slug}`,
() => $fetch(`/api/articles/${route.params.slug}`),
)
Повний перелік змін у роботі з даними наведено в офіційному міграційному гіді.
Покращена підтримка TypeScript
Nuxt 4 генерує окремі конфігурації TypeScript для різних частин проєкту:
.nuxt/tsconfig.app.json— для компонентів, composables і клієнтського коду;.nuxt/tsconfig.server.json— для Nitro та каталогуserver;.nuxt/tsconfig.shared.json— для спільних типів і утиліт;.nuxt/tsconfig.node.json— дляnuxt.config.ts, модулів і build-time коду.
Цей поділ допомагає IDE точніше визначати доступні API. Наприклад, браузерні глобальні об’єкти не повинні пропонуватися у серверних файлах, а серверні auto-imports — у Vue-компонентах.
Під час переходу потрібно врахувати кілька моментів.
У кореневому tsconfig.json не слід одночасно використовувати extends і TypeScript project references.
Type augmentation потрібно розміщувати у відповідному контексті:
- типи застосунку — в
app/; - серверні типи — в
server/; - спільні типи — в
shared/.
Для перевірки проєкту з project references команду vue-tsc потрібно запускати з параметром -b:
{
"scripts": {
"typecheck": "nuxt prepare && vue-tsc -b --noEmit"
}
}
Крім того, у Nuxt 4 типове значення noUncheckedIndexedAccess змінено на true. Через це TypeScript частіше вказуватиме, що результат доступу до елемента масиву або об’єкта може бути undefined.
Нові помилки TypeScript після оновлення не завжди означають проблему Nuxt 4. Часто нова конфігурація просто знаходить помилки, які раніше залишалися непоміченими. TypeScript у Nuxt 4.
Швидша локальна розробка
У Nuxt 4 було оптимізовано CLI та dev-сервер.
Фреймворк отримав швидший холодний запуск, повторне використання V8 compile cache та нативне спостереження за файлами через fs.watch.
CLI та Vite dev-сервер також можуть взаємодіяти через внутрішні сокети замість додаткових мережевих портів. Це зменшує накладні витрати й особливо добре помітно у Windows.
Нова структура app/ також допомагає файловим спостерігачам обробляти менше зайвих каталогів. У великих проєктах і monorepo це може помітно прискорити щоденну розробку. Опис покращень CLI.
Зміни в маршрутах і компонентах
У Nuxt 3 деякі параметри сторінки могли бути доступні одночасно в об’єкті маршруту та в route.meta.
У Nuxt 4, наприклад, назву маршруту потрібно отримувати через route.name:
const route = useRoute()
console.log(route.name)
Використання route.meta.name більше не рекомендується.
Також змінилася генерація назв вкладених компонентів. Раніше файл:
components/
└── SomeFolder/
└── MyComponent.vue
міг мати ім’я MyComponent усередині Vue, хоча для auto-import використовувалася назва SomeFolderMyComponent.
У Nuxt 4 ці назви узгоджено. Це може вплинути на:
<KeepAlive>з параметрамиincludeабоexclude;findComponentу тестах;- відображення компонентів у Vue DevTools;
- код, який залежить від імені компонента.
Зміни в SEO та керуванні <head>
Під час переходу потрібно перевірити використання useHead і низькорівневих можливостей Unhead.
Legacy-властивості vmid та hid більше не потрібні:
useHead({
meta: [
{
name: 'description',
content: 'Опис сторінки',
},
],
})
Також було видалено або змінено підтримку деяких низькорівневих властивостей, зокрема children і body.
Якщо застосунок використовує власні плагіни для сортування тегів, template params або прямі імпорти з @unhead/vue, їх потрібно перевірити окремо.
Зміни у стилях
У Nuxt 3 під час SSR могли вбудовуватися як стилі компонентів, так і глобальні CSS-файли.
У Nuxt 4 глобальні стилі за замовчуванням не вбудовуються в HTML. Вони можуть завантажуватися окремим файлом, який браузер здатен кешувати між сторінками.
Це допомагає зменшити розмір початкового HTML, але може вплинути на:
- Content Security Policy;
- тести, які перевіряють HTML;
- критичні стилі;
- метрики першого відображення;
- власну логіку обробки CSS.
За потреби стару поведінку можна тимчасово повернути через конфігурацію features.inlineStyles.
Видалення window.__NUXT__
Після завершення hydration глобальний об’єкт window.__NUXT__ у Nuxt 4 видаляється.
Якщо застосунок звертається до нього безпосередньо, потрібно використовувати useNuxtApp():
const nuxtApp = useNuxtApp()
console.log(nuxtApp.payload)
Ця зміна уніфікує доступ до даних Nuxt і готує архітектуру до підтримки складніших сценаріїв із декількома застосунками.
Зміни у статичній генерації
Верхньорівнева конфігурація generate більше не підтримується.
Старий варіант:
export default defineNuxtConfig({
generate: {
routes: ['/sitemap.xml'],
exclude: ['/admin'],
},
})
Потрібно замінити конфігурацією Nitro:
export default defineNuxtConfig({
nitro: {
prerender: {
routes: ['/sitemap.xml'],
ignore: ['/admin'],
},
},
})
Цей момент особливо важливий для проєктів, які починалися на Nuxt 2 або переносили старі налаштування через Nuxt Bridge.
На що звернути увагу авторам модулів
Для звичайних застосунків більшість внутрішніх змін Nuxt Kit залишиться непомітною. Автори власних або публічних модулів повинні провести додаткову перевірку.
У Nuxt 4 видалено сумісність Nuxt 2 з @nuxt/kit, змінено роботу шаблонів і припинено використання старого EJS-підходу через lodash/template.
Hook builder:watch тепер передає абсолютний шлях, а не шлях відносно srcDir.
Також змінився порядок завантаження модулів у Nuxt Layers. Спочатку завантажуються модулі розширюваних шарів, а модулі основного проєкту — останніми. Завдяки цьому основний проєкт має вищий пріоритет, але старі workaround-рішення можуть працювати інакше.
Рекомендований порядок міграції
Перед оновленням варто створити окрему Git-гілку та зафіксувати поточний стан проєкту.
Офіційно рекомендована команда оновлення:
npx nuxt upgrade --dedupe
Для автоматизації частини змін можна використати codemod:
npx codemod@0.18.7 nuxt/4/migration-recipe
Після автоматичної міграції необхідно вручну:
- Перевірити сумісність усіх Nuxt-модулів.
- Переглянути конфігурації ESLint, Tailwind CSS, тестів і CI.
- Перевірити виклики
useFetchтаuseAsyncData. - Знайти перевірки
nullдляdataтаerror. - Перевірити вкладені мутації API-відповідей.
- Переконатися, що ключі запитів унікальні.
- Оновити TypeScript-конфігурацію.
- Запустити unit- та end-to-end тести.
- Перевірити SSR, hydration і клієнтську навігацію.
- Протестувати prerendering динамічних сторінок.
- Виконати production-збірку та перевірити її в умовах, близьких до бойових.
Codemod може змінити структуру файлів і відомі застарілі API, але він не здатний перевірити бізнес-логіку, яка залежить від старої реактивності, кешування або послідовності завантаження модулів.
Такі справи :)