Руководство по обновлению
Обновление Nuxt
Последний релиз
Чтобы обновиться до последнего релиза, выполните команду nuxt upgrade.
npx nuxt upgrade
yarn nuxt upgrade
pnpm nuxt upgrade
bun x nuxt upgrade
deno x nuxt upgrade
Канал Nightly
Чтобы использовать свежие сборки Nuxt и тестировать функции до релиза, см. канал nightly.
Тестирование Nuxt 5
Nuxt 5 сейчас в разработке. До релиза многие breaking changes можно попробовать уже в Nuxt 4.2+.
Включение режима Nuxt 5
Сначала обновите Nuxt до последнего релиза.
Затем задайте future.compatibilityVersion: 5 для поведения Nuxt 5:
export default defineNuxtConfig({
future: {
compatibilityVersion: 5,
},
})
При future.compatibilityVersion: 5 значения по умолчанию в конфигурации Nuxt переключаются на поведение Nuxt v5:
- Vite Environment API: автоматически включается новый Vite Environment API для улучшенной конфигурации сборки
- Остальные изменения Nuxt 5 по мере появления
future.compatibilityVersion: 5 заглядывайте сюда регулярно.Критичные изменения перечислены ниже вместе с шагами миграции для обратной совместимости.
Миграция на Vite Environment API
🚦 Уровень влияния: средний
Что изменилось
Nuxt 5 переходит на новый Environment API Vite 6: формализовано понятие окружений и улучшен контроль конфигурации для каждого.
Раньше Nuxt использовал отдельные конфиги Vite для клиента и сервера. Теперь — общий конфиг Vite с плагинами под окружения через метод applyToEnvironment().
future.compatibilityVersion: 5 (см. Тестирование Nuxt 5) или явно включить experimental.viteEnvironmentApi: true.Основные изменения:
- Устарели опции окружений в
extendViteConfig(): опцииserverиclientвextendViteConfig()устарели, при использовании выводятся предупреждения. - Регистрация плагинов: у плагинов Vite, зарегистрированных через
addVitePlugin()и нацеленных только на одно окружение (server: falseилиclient: false), хукиconfigиconfigResolvedне вызываются. - Общая конфигурация: хуки
vite:extendConfigиvite:configResolvedработают с общей конфигурацией вместо отдельных клиентской и серверной.
Причины изменений
Vite Environment API даёт:
- Более предсказуемое поведение в dev и production
- Точный контроль конфигурации по окружениям
- Улучшенную производительность и архитектуру плагинов
- Поддержку произвольных окружений, не только client/server
Шаги миграции
1. Переход на плагины Vite
Рекомендуется использовать плагин Vite вместо extendViteConfig, vite:configResolved и vite:extendConfig.
// Было
extendViteConfig((config) => {
config.optimizeDeps.include.push('my-package')
}, { server: false })
nuxt.hook('vite:extendConfig' /* or vite:configResolved */, (config, { isClient }) => {
if (isClient) {
config.optimizeDeps.include.push('my-package')
}
})
// Стало
addVitePlugin(() => ({
name: 'my-plugin',
config (config) {
// здесь можно задать глобальную конфигурацию Vite
},
configResolved (config) {
// здесь доступна полностью разрешённая конфигурация Vite
},
configEnvironment (name, config) {
// здесь — конфигурация для конкретного окружения
if (name === 'client') {
config.optimizeDeps ||= {}
config.optimizeDeps.include ||= []
config.optimizeDeps.include.push('my-package')
}
},
applyToEnvironment (environment) {
return environment.name === 'client'
},
}))
2. Переход плагинов Vite на окружения
Вместо addVitePlugin с server: false или client: false используйте в плагине хук applyToEnvironment.
// Было
addVitePlugin(() => ({
name: 'my-plugin',
config (config) {
config.optimizeDeps.include.push('my-package')
},
}), { client: false })
// Стало
addVitePlugin(() => ({
name: 'my-plugin',
config (config) {
// здесь можно задать глобальную конфигурацию Vite
},
configResolved (config) {
// здесь доступна полностью разрешённая конфигурация Vite
},
configEnvironment (name, config) {
// здесь — конфигурация для конкретного окружения
if (name === 'client') {
config.optimizeDeps ||= {}
config.optimizeDeps.include ||= []
config.optimizeDeps.include.push('my-package')
}
},
applyToEnvironment (environment) {
return environment.name === 'client'
},
}))
Миграция на Nuxt 4
Nuxt 4 вносит существенные изменения. Это руководство поможет перенести приложение с Nuxt 3 на Nuxt 4.
Сначала обновитесь до Nuxt 4:
npm install nuxt@^4.0.0
yarn add nuxt@^4.0.0
pnpm add nuxt@^4.0.0
bun add nuxt@^4.0.0
deno add npm:nuxt@^4.0.0
После обновления поведение Nuxt 4 по умолчанию включено. Часть возможностей можно настроить для обратной совместимости во время миграции.
Ниже — основные изменения и шаги миграции при переходе на Nuxt 4.
Критичные изменения описаны вместе с шагами миграции и доступными опциями конфигурации.
Миграция с помощью Codemods
Вместе с командой Codemod подготовлены открытые codemods для автоматизации многих шагов миграции.
npx codemod feedback 🙏Полный список codemods для Nuxt 4, описание и способы запуска — в Codemod Registry.
Все codemods из этого руководства можно запустить одним рецептом:
# Using pinned version due to https://github.com/codemod/codemod/issues/1710
npx codemod@0.18.7 nuxt/4/migration-recipe
# Using pinned version due to https://github.com/codemod/codemod/issues/1710
yarn dlx codemod@0.18.7 nuxt/4/migration-recipe
# Using pinned version due to https://github.com/codemod/codemod/issues/1710
pnpm dlx codemod@0.18.7 nuxt/4/migration-recipe
# Using pinned version due to https://github.com/codemod/codemod/issues/1710
bun x codemod@0.18.7 nuxt/4/migration-recipe
# Using pinned version due to https://github.com/codemod/codemod/issues/1710
deno x codemod@0.18.7 nuxt/4/migration-recipe
Команда выполнит все codemods по очереди; ненужные можно отключить. Каждый codemod описан ниже рядом с соответствующим изменением и может быть запущен отдельно.
Новая структура каталогов
🚦 Уровень влияния: значительный
В Nuxt 4 по умолчанию используется новая структура каталогов; при этом сохранена обратная совместимость (если Nuxt видит старую структуру, например app/pages/ в корне, новая не применяется).
Что изменилось
- По умолчанию
srcDir—app/, большинство путей разрешается относительно неё. serverDirпо умолчанию<rootDir>/server, а не<srcDir>/server.layers/,modules/иpublic/по умолчанию разрешаются относительно<rootDir>.- При использовании Nuxt Content v2.13+ каталог
content/разрешается относительно<rootDir>. - Добавлен
dir.app— каталог, в котором ищутсяrouter.options.tsиspa-loading-template.html(по умолчанию<srcDir>/).
Пример структуры каталогов в v4.
.output/
.nuxt/
app/
assets/
components/
composables/
layouts/
middleware/
pages/
plugins/
utils/
app.config.ts
app.vue
router.options.ts
content/
layers/
modules/
node_modules/
public/
shared/
server/
api/
middleware/
plugins/
routes/
utils/
nuxt.config.ts
~ по умолчанию указывает на каталог app/ (ваш srcDir). То есть ~/components → app/components/, ~/pages → app/pages/ и т.д.👉 Подробнее: PR с реализацией.
Причины изменений
- Производительность — размещение всего кода в корне репозитория приводит к сканированию
.git/иnode_modules/файловыми watchers и замедлению запуска (особенно не на macOS). - Типизация в IDE —
server/и остальное приложение работают в разных контекстах с разными глобальными импортами; выносserver/из каталога приложения улучшает автодополнение в IDE.
Шаги миграции
- Создайте каталог
app/. - Перенесите в него
assets/,components/,composables/,layouts/,middleware/,pages/,plugins/,utils/, а такжеapp.vue,error.vue,app.config.ts. Пути кrouter.options.tsиspa-loading-template.htmlпри необходимости остаются вapp/. - Оставьте в корне проекта (вне
app/):nuxt.config.ts,content/,layers/,modules/,public/,server/. - Обновите конфиги сторонних инструментов (tailwindcss, eslint и т.д.) под новую структуру.
@nuxtjs/tailwindcssобычно настраивает tailwind автоматически.
npx codemod@latest nuxt/4/file-structureМиграция не обязательна: при текущей структуре Nuxt должен определить её сам (если нет — создайте issue). Исключение — кастомный srcDir: тогда modules/, public/ и server/ разрешаются от rootDir, а не от srcDir. Переопределить можно через dir.modules, dir.public и serverDir.
Вернуть структуру каталогов как в v3 можно такой конфигурацией:
export default defineNuxtConfig({
// Возврат srcDir в корень проекта
srcDir: '.',
// Каталог для router.options.ts и spa-loading-template.html
dir: {
app: 'app',
},
})
Единый слой загрузки данных
🚦 Уровень влияния: средний
Что изменилось
Система загрузки данных Nuxt (useAsyncData и useFetch) переработана для производительности и единообразия:
- Общие ref при одном ключе: все вызовы
useAsyncData/useFetchс одним ключом используют одни и те же refsdata,errorиstatus. Важно, чтобы при явном ключе не было конфликтующих опцийdeep,transform,pick,getCachedDataилиdefault. - Контроль
getCachedData: функцияgetCachedDataтеперь вызывается при каждой загрузке, в том числе при срабатывании watcher или вызовеrefreshNuxtData. (Раньше в этих случаях данные всегда запрашивались заново, а функция не вызывалась.) Для гибкого выбора «кэш или повторный запрос» функция получает контекст с причиной запроса. - Реактивные ключи: в качестве ключа можно передавать computed ref, обычный ref или getter — данные будут перезапрашиваться автоматически и храниться раздельно.
- Очистка данных: когда размонтируется последний компонент, использующий данные по ключу, Nuxt удаляет эти данные, чтобы не раздувать память.
Причины изменений
Цель — снизить потребление памяти и унифицировать состояния загрузки между вызовами useAsyncData.
Шаги миграции
- Проверьте согласованность опций: найдите компоненты, где один ключ используется с разными опциями или разными функциями запроса.
// This will now trigger a warning const { data: users1 } = useAsyncData('users', () => $fetch('/api/users'), { deep: false }) const { data: users2 } = useAsyncData('users', () => $fetch('/api/users'), { deep: true })
Вынесите вызовыuseAsyncDataс общим ключом и кастомными опциями в отдельный композабл:app/composables/useUserData.tsexport function useUserData (userId: string) { return useAsyncData( `user-${userId}`, () => fetchUser(userId), { deep: true, transform: user => ({ ...user, lastAccessed: new Date() }), }, ) } - Обновите реализации
getCachedData:useAsyncData('key', fetchFunction, { - getCachedData: (key, nuxtApp) => { - return cachedData[key] - } + getCachedData: (key, nuxtApp, ctx) => { + // ctx.cause — 'initial' | 'refresh:hook' | 'refresh:manual' | 'watch' + + // Пример: не использовать кэш при ручном обновлении + if (ctx.cause === 'refresh:manual') return undefined + + return cachedData[key] + } })
Временно отключить это поведение можно так:
export default defineNuxtConfig({
experimental: {
granularCachedData: false,
purgeCachedData: false,
},
})
Исправленный порядок загрузки модулей в слоях
🚦 Уровень влияния: минимальный
Что изменилось
Исправлен порядок загрузки модулей при использовании слоёв Nuxt. Раньше модули из корня проекта загружались раньше модулей из расширяемых слоёв — наоборот ожидаемому.
Теперь порядок такой:
- Сначала модули слоёв (в порядке extends — более глубокие слои первыми)
- Затем модули проекта (наивысший приоритет)
Это касается и модулей из массива modules в nuxt.config.ts, и автообнаруживаемых в каталоге modules/.
Причины изменений
Так обеспечиваются:
- меньший приоритет слоёв по сравнению с проектом
- интуитивный порядок выполнения при наследовании слоёв
- корректная работа конфигурации и хуков в многослойной настройке
Шаги миграции
В большинстве проектов менять ничего не нужно — порядок приведён к ожидаемому.
Если вы полагались на старый порядок, возможно потребуется:
- Проверить зависимости модулей: есть ли модули, зависящие от порядка загрузки.
- Скорректировать конфигурацию: если она обходила старый порядок.
- Прогнать тесты: убедиться, что всё работает при новом порядке.
Пример нового порядка:
// Layer: my-layer/nuxt.config.ts
export default defineNuxtConfig({
modules: ['layer-module-1', 'layer-module-2'],
})
// Project: nuxt.config.ts
export default defineNuxtConfig({
extends: ['./my-layer'],
modules: ['project-module-1', 'project-module-2'],
})
// Порядок загрузки (исправленный):
// 1. layer-module-1
// 2. layer-module-2
// 3. project-module-1 (может переопределять модули слоя)
// 4. project-module-2 (может переопределять модули слоя)
Если порядок модулей мешает регистрации хука, используйте хук modules:done — он выполняется после загрузки всех модулей.
👉 Подробнее: PR #31507, issue #25719.
Дедупликация метаданных маршрута
🚦 Уровень влияния: минимальный
Что изменилось
Часть метаданных маршрута задаётся через definePageMeta (name, path и т.д.). Раньше они были доступны и в объекте маршрута, и в route.meta (например route.name и route.meta.name).
Теперь доступ только через объект маршрута.
Причины изменений
Следствие включения experimental.scanPageMeta по умолчанию и оптимизации производительности.
Шаги миграции
Обычно достаточно заменить обращение:
const route = useRoute()
- console.log(route.meta.name)
+ console.log(route.name)
Нормализованные имена компонентов
🚦 Уровень влияния: средний
Vue теперь генерирует имена компонентов по правилам Nuxt.
Что изменилось
По умолчанию (если имя не задано вручную) Vue присваивает компоненту имя по имени файла.
├─ components/
├─── SomeFolder/
├───── MyComponent.vue
Для Vue имя компонента — MyComponent (для <KeepAlive> и Vue DevTools). Для автоимпорта раньше нужно было использовать SomeFolderMyComponent.
Теперь оба варианта совпадают: Vue выдаёт имя по правилам Nuxt.
Шаги миграции
В тестах с findComponent из @vue/test-utils и в <KeepAlive> по имени компонента используйте обновлённое имя.
Временно отключить это поведение можно так:
export default defineNuxtConfig({
experimental: {
normalizeComponentNames: false,
},
})
Unhead v2
🚦 Уровень влияния: минимальный
Что изменилось
Unhead, используемый для тегов <head>, обновлён до v2. В целом совместим, но есть breaking changes в низкоуровневом API.
- Удалены пропсы:
vmid,hid,children,body. - Ввод в виде Promise больше не поддерживается.
- Теги по умолчанию сортируются через Capo.js.
Шаги миграции
На большинство приложений это почти не повлияет.
При проблемах проверьте:
- Не используются ли удалённые пропсы.
useHead({
meta: [{
name: 'description',
// meta tags don't need a vmid, or a key
- vmid: 'description'
- hid: 'description'
}]
})
- При использовании Template Params или Alias Tag Sorting эти возможности нужно явно подключить.
import { AliasSortingPlugin, TemplateParamsPlugin } from '@unhead/vue/plugins'
export default defineNuxtPlugin({
setup () {
const unhead = injectHead()
unhead.use(TemplateParamsPlugin)
unhead.use(AliasSortingPlugin)
},
})
Не обязательно, но рекомендуется заменить импорты из @unhead/vue на #imports или nuxt/app.
-import { useHead } from '@unhead/vue'
+import { useHead } from '#imports'
При проблемах можно вернуть поведение v1, включив опцию head.legacy.
export default defineNuxtConfig({
unhead: {
legacy: true,
},
})
Новое расположение экрана загрузки SPA в DOM
🚦 Уровень влияния: минимальный
Что изменилось
При рендере клиентской страницы (ssr: false) экран загрузки (из ~/app/spa-loading-template.html; в Nuxt 4 путь — ~/spa-loading-template.html) раньше рендерился внутри корня приложения Nuxt:
<div id="__nuxt">
<!-- шаблон загрузки SPA -->
</div>
Теперь по умолчанию шаблон рендерится рядом с корнем:
<div id="__nuxt"></div>
<!-- шаблон загрузки SPA -->
Причины изменений
Шаблон загрузки остаётся в DOM до разрешения suspense приложения Vue, что убирает белую вспышку.
Шаги миграции
Если вы обращались к шаблону загрузки через CSS или document.querySelector, обновите селекторы. Можно использовать новые опции app.spaLoaderTag и app.spaLoaderAttrs.
Вернуть прежнее поведение можно так:
export default defineNuxtConfig({
experimental: {
spaLoadingTemplateLocation: 'within',
},
})
Парсинг error.data
🚦 Уровень влияния: минимальный
Раньше при выбросе ошибки с полем data оно не парсилось. Теперь оно парсируется и доступно в объекте error. Это исправление, но breaking change для кода, который парсил error.data вручную.
Шаги миграции
В кастомном error.vue уберите ручной парсинг error.data:
<script setup lang="ts">
import type { NuxtError } from '#app'
const props = defineProps({
error: Object as () => NuxtError
})
- const data = JSON.parse(error.data)
+ const data = error.data
</script>
Более точечная инлайнизация стилей
🚦 Уровень влияния: средний
Nuxt теперь инлайнит только стили Vue-компонентов, не глобальный CSS.
Что изменилось
Раньше Nuxt инлайнил весь CSS, включая глобальный, и убирал <link> на отдельные файлы. Теперь инлайнятся только стили компонентов (ранее из них получались отдельные чанки). Это даёт и меньше отдельных запросов при первой загрузке, и кэширование одного глобального CSS, и меньший размер документа.
Шаги миграции
Поведение настраивается: вернуть инлайн глобального CSS можно опцией inlineStyles: true.
export default defineNuxtConfig({
features: {
inlineStyles: true,
},
})
Сканирование метаданных страниц после разрешения
🚦 Уровень влияния: минимальный
Что изменилось
Метаданные страниц (из definePageMeta) теперь сканируются после хука pages:extend, а не до него.
Причины изменений
Так учитываются страницы, добавленные в pages:extend. Переопределять метаданные можно в новом хуке pages:resolved.
Шаги миграции
Переопределение метаданных страниц перенесите из pages:extend в pages:resolved.
export default defineNuxtConfig({
hooks: {
- 'pages:extend'(pages) {
+ 'pages:resolved'(pages) {
const myPage = pages.find(page => page.path === '/')
myPage.meta ||= {}
myPage.meta.layout = 'overridden-layout'
}
}
})
Вернуть прежнее поведение можно так:
export default defineNuxtConfig({
experimental: {
scanPageMeta: true,
},
})
Общие данные при пререндере
🚦 Уровень влияния: средний
Что изменилось
Включена ранее экспериментальная возможность делиться данными из useAsyncData и useFetch между страницами. См. исходный PR.
Причины изменений
Данные payload автоматически переиспользуются между пререндеренными страницами. Это ускоряет пререндер сайтов, где на разных страницах запрашиваются одни и те же данные.
Например, если на каждой странице вызывается useFetch (меню, настройки из CMS), при пререндере данные запросятся один раз и закэшируются для остальных страниц.
Шаги миграции
Убедитесь, что каждый уникальный ключ данных всегда соответствует одним и тем же данным. Для данных, привязанных к странице, задавайте ключ, однозначно идентифицирующий эти данные. (useFetch делает это автоматически.)
// Так делать небезопасно на динамической странице: slug влияет на данные,
// но Nuxt этого не видит, если ключ не отражает slug.
const route = useRoute()
const { data } = await useAsyncData(async () => {
return await $fetch(`/api/my-page/${route.params.slug}`)
})
// Используйте ключ, однозначно идентифицирующий загружаемые данные.
const { data } = await useAsyncData(route.params.slug, async () => {
return await $fetch(`/api/my-page/${route.params.slug}`)
})
Отключить возможность можно так:
export default defineNuxtConfig({
experimental: {
sharedPrerenderData: false,
},
})
Значения по умолчанию для data и error в useAsyncData и useFetch
🚦 Уровень влияния: минимальный
Что изменилось
Объекты data и error, возвращаемые useAsyncData, по умолчанию теперь undefined.
Причины изменений
Раньше data инициализировался как null, а в clearNuxtData сбрасывался в undefined; error был null. Изменение для единообразия.
Шаги миграции
Проверки на data.value === null или error.value === null замените на проверку undefined.
npx codemod@latest nuxt/4/default-data-error-valueУдаление устаревших boolean-значений опции dedupe при вызове refresh в useAsyncData и useFetch
🚦 Уровень влияния: минимальный
Что изменилось
Раньше в refresh можно было передать dedupe: boolean — это были алиасы cancel (true) и defer (false).
// @errors: 2322
const { refresh } = await useAsyncData(() => Promise.resolve({ message: 'Hello, Nuxt!' }))
async function refreshData () {
await refresh({ dedupe: true })
}
Причины изменений
Алиасы убраны для ясности: при добавлении dedupe в опции useAsyncData boolean-значения оказались противоположными по смыслу.
refresh({ dedupe: false }) означало «не отменять текущие запросы в пользу нового», а dedupe: true в опциях useAsyncData — «не делать новый запрос, если уже есть ожидающий». См. PR.
Шаги миграции
Достаточно заменить значения:
const { refresh } = await useAsyncData(async () => ({ message: 'Hello, Nuxt 3!' }))
async function refreshData () {
- await refresh({ dedupe: true })
+ await refresh({ dedupe: 'cancel' })
- await refresh({ dedupe: false })
+ await refresh({ dedupe: 'defer' })
}
npx codemod@latest nuxt/4/deprecated-dedupe-valueУчёт значений по умолчанию при очистке data в useAsyncData и useFetch
🚦 Уровень влияния: минимальный
Что изменилось
При заданном кастомном default в useAsyncData при вызове clear или clearNuxtData данные сбрасываются в это значение по умолчанию, а не просто в «не задано».
Причины изменений
Часто задают пустое значение (например пустой массив), чтобы не проверять null/undefined при итерации. При сбросе/очистке это значение должно восстанавливаться.
Согласование значения pending в useAsyncData и useFetch
🚦 Уровень влияния: средний
pending, возвращаемый useAsyncData, useFetch, useLazyAsyncData и useLazyFetch, теперь computed: true только когда status тоже pending.
Что изменилось
При immediate: false теперь pending будет false, пока не выполнен первый запрос. Раньше до первого запроса pending был всегда true.
Причины изменений
Смысл pending приведён в соответствие со свойством status, которое тоже в состоянии pending во время запроса.
Шаги миграции
Если вы опираетесь на pending, учтите: теперь он true только при status === 'pending'.
<template>
- <div v-if="!pending">
+ <div v-if="status === 'success'">
<p>Data: {{ data }}</p>
</div>
<div v-else>
<p>Loading...</p>
</div>
</template>
<script setup lang="ts">
const { data, pending, execute, status } = await useAsyncData(() => fetch('/api/data'), {
immediate: false
})
onMounted(() => execute())
</script>
Временно вернуть прежнее поведение можно так:
export default defineNuxtConfig({
experimental: {
pendingWhenIdle: true,
},
})
Key Change Behavior in useAsyncData and useFetch
🚦 Уровень влияния: средний
Что изменилось
При реактивных ключах в useAsyncData или useFetch Nuxt автоматически перезапрашивает данные при смене ключа. При immediate: false данные запрашиваются при смене ключа только если они уже были запрошены хотя бы раз.
Раньше useFetch вёл себя иначе: всегда запрашивал данные при смене ключа.
Теперь useFetch и useAsyncData ведут себя одинаково: при смене ключа данные запрашиваются только если они уже были получены ранее.
Причины изменений
Единое поведение и отсутствие неожиданных запросов. При immediate: false нужно вызывать refresh или execute, иначе данные в useFetch/useAsyncData не будут запрошены.
Шаги миграции
Обычно поведение только улучшается. Если вы полагались на автоматический запрос при смене ключа/опций у не-immediate useFetch, первый запрос теперь нужно запускать вручную.
const id = ref('123')
const { data, execute } = await useFetch('/api/test', {
query: { id },
immediate: false
)
+ watch(id, () => execute(), { once: true })
Отключить это поведение:
// Или глобально в nuxt.config
export default defineNuxtConfig({
experimental: {
alwaysRunFetchOnKeyChange: true,
},
})
Shallow Data Reactivity in useAsyncData and useFetch
🚦 Уровень влияния: минимальный
data, возвращаемый useAsyncData, useFetch, useLazyAsyncData и useLazyFetch, теперь shallowRef, а не ref.
Что изменилось
При новом запросе зависимости от data остаются реактивными — объект заменяется целиком. Изменение свойств внутри этого объекта не вызовет реактивности в приложении.
Причины изменений
Существенный прирост производительности для вложенных объектов и массивов: Vue не отслеживает каждое свойство. В большинстве случаев data должен быть неизменяемым.
Шаги миграции
Обычно миграция не нужна. Если вы полагаетесь на глубокую реактивность data, есть два варианта:
- Включить глубокую реактивность точечно для каждого композабла:
- const { data } = useFetch('/api/test') + const { data } = useFetch('/api/test', { deep: true }) - Задать поведение по умолчанию для всего проекта (не рекомендуется):
nuxt.config.ts
export default defineNuxtConfig({ experimental: { defaults: { useAsyncData: { deep: true, }, }, }, })
npx codemod@latest nuxt/4/shallow-function-reactivityAbsolute Watch Paths in builder:watch
🚦 Уровень влияния: минимальный
Что изменилось
Хук Nuxt builder:watch теперь передаёт абсолютный путь вместо относительного к srcDir.
Причины изменений
Так можно отслеживать пути вне srcDir и лучше поддерживать слои и сложные схемы.
Шаги миграции
Публичные модули Nuxt, использующие этот хук, уже обновлены. См. issue #25339.
Если вы автор модуля и хотите совместимость с Nuxt v3 и v4, используйте такой код:
+ import { relative, resolve } from 'node:fs'
// ...
nuxt.hook('builder:watch', async (event, path) => {
+ path = relative(nuxt.options.srcDir, resolve(nuxt.options.srcDir, path))
// ...
})
npx codemod@latest nuxt/4/absolute-watch-pathУдаление объекта window.__NUXT__
Что изменилось
Глобальный объект window.__NUXT__ удаляется после гидрации приложения.
Причины изменений
Это открывает путь к мульти-приложениям (#21635) и единому способу доступа к данным — useNuxtApp().
Шаги миграции
Данные по-прежнему доступны через useNuxtApp().payload:
- console.log(window.__NUXT__)
+ console.log(useNuxtApp().payload)
Сканирование index в каталогах
🚦 Уровень влияния: средний
Что изменилось
Вложенные папки в app/middleware/ тоже сканируются на наличие index-файлов, они регистрируются как middleware.
Причины изменений
Nuxt автоматически сканирует несколько папок, в том числе app/middleware/ и app/plugins/. В app/plugins/ уже сканировались вложенные папки по index — поведение приведено к единому виду.
Шаги миграции
Миграция обычно не нужна. Чтобы вернуть прежнее поведение, можно отфильтровать такие middleware хуком:
export default defineNuxtConfig({
hooks: {
'app:resolve' (app) {
app.middleware = app.middleware.filter(mw => !/\/index\.[^/]+$/.test(mw.path))
},
},
})
Изменения компиляции шаблонов
🚦 Уровень влияния: минимальный
Что изменилось
Раньше Nuxt использовал lodash/template для компиляции шаблонов на файловой системе в формате .ejs.
Утилиты (serialize, importName, importSources) для генерации кода в этих шаблонах удаляются.
Причины изменений
В Nuxt v3 перешли на «виртуальный» синтаксис с функцией getContents(), гибче и быстрее.
У lodash/template были уязвимости; для Nuxt это не критично (сборка, доверенный код), но аудиты их показывают. Плюс lodash — тяжёлая зависимость, большинству проектов не нужна.
Функции сериализации кода лучше вынести в отдельные пакеты, например unjs/knitwork, чтобы исправления не требовали обновления Nuxt.
Шаги миграции
Модули на EJS уже обновляются через PR. Самостоятельно можно сделать так (с сохранением совместимости):
- Перенести интерполяцию строк в
getContents(). - Реализовать свою подстановку, как в https://github.com/nuxt-modules/color-mode/pull/240.
- Подключить
es-toolkit/compat(замена lodash template) как зависимость вашего проекта:
+ import { readFileSync } from 'node:fs'
+ import { template } from 'es-toolkit/compat'
// ...
addTemplate({
fileName: 'appinsights-vue.js'
options: { /* some options */ },
- src: resolver.resolve('./runtime/plugin.ejs'),
+ getContents({ options }) {
+ const contents = readFileSync(resolver.resolve('./runtime/plugin.ejs'), 'utf-8')
+ return template(contents)({ options })
+ },
})
Утилиты шаблонов (serialize, importName, importSources) можно заменить функциями из knitwork:
import { genDynamicImport, genImport, genSafeVariableName } from 'knitwork'
const serialize = (data: any) => JSON.stringify(data, null, 2).replace(/"\{(.+)\}"(?=,?$)/gm, r => JSON.parse(r).replace(/^\{(.*)\}$/, '$1'))
const importSources = (sources: string | string[], { lazy = false } = {}) => {
return toArray(sources).map((src) => {
if (lazy) {
return `const ${genSafeVariableName(src)} = ${genDynamicImport(src, { comment: `webpackChunkName: ${JSON.stringify(src)}` })}`
}
return genImport(src, genSafeVariableName(src))
}).join('\n')
}
const importName = genSafeVariableName
npx codemod@latest nuxt/4/template-compilation-changesИзменения конфигурации TypeScript по умолчанию
🚦 Уровень влияния: минимальный
Что изменилось
compilerOptions.noUncheckedIndexedAccess теперь true вместо false.
Причины изменений
Продолжение обновления конфига 3.12: приведение дефолтов в соответствие с рекомендациями TotalTypeScript.
Шаги миграции
Варианты:
- Запустить проверку типов и исправить новые ошибки (рекомендуется).
- Переопределить значение в
nuxt.config.ts:export default defineNuxtConfig({ typescript: { tsConfig: { compilerOptions: { noUncheckedIndexedAccess: false, }, }, }, })
Разделение конфигурации TypeScript
🚦 Уровень влияния: минимальный
Что изменилось
Nuxt теперь генерирует отдельные конфиги TypeScript для разных контекстов:
- Новые файлы конфигурации:
.nuxt/tsconfig.app.json— код приложения (компоненты Vue, composables и т.д.).nuxt/tsconfig.server.json— серверный код (Nitro/server).nuxt/tsconfig.node.json— код сборки (модули,nuxt.config.tsи т.д.).nuxt/tsconfig.shared.json— общий код (типы, утилиты).nuxt/tsconfig.json— прежний конфиг для обратной совместимости
- Обратная совместимость: проекты, расширяющие
.nuxt/tsconfig.json, работают как раньше. - Опциональные project references: можно включить для улучшенной проверки типов.
- Проверка по контексту: у каждого контекста свои compiler options и includes/excludes.
- Опция
typescript.nodeTsConfig: настройка TypeScript для Node.js при сборке.
Причины изменений
Плюсы:
- Типобезопасность: у контекстов (app, server, build-time) свои глобалы и API.
- Лучше в IDE: точнее IntelliSense и ошибки по частям кодовой базы.
- Разделение: серверный код не предлагает клиентские API и наоборот.
- Производительность: TypeScript эффективнее с правильно разграниченными конфигами.
Раньше auto-imports в nuxt.config.ts не помечались TypeScript; контекст server/ в IDE не совпадал с проверкой типов.
Шаги миграции
Миграция не обязательна — существующие проекты работают без изменений.
Чтобы использовать улучшенную проверку типов, включите project references:
- Обновите корневой
tsconfig.json:Если вtsconfig.jsonесть"extends": "./.nuxt/tsconfig.json", удалите его перед добавлением references. Project references и extends несовместимы.{ // Удалите "extends": "./.nuxt/tsconfig.json", если есть "files": [], "references": [ { "path": "./.nuxt/tsconfig.app.json" }, { "path": "./.nuxt/tsconfig.server.json" }, { "path": "./.nuxt/tsconfig.shared.json" }, { "path": "./.nuxt/tsconfig.node.json" } ] } - Удалите ручные
server/tsconfig.json, если они расширяли.nuxt/tsconfig.server.json. - Обновите скрипты проверки типов — используйте флаг build для project references:
- "typecheck": "nuxt prepare && vue-tsc --noEmit" + "typecheck": "nuxt prepare && vue-tsc -b --noEmit" - Перенесите расширения типов в нужный контекст:
- типы для app — в
app/ - типы для server — в
server/ - общие типы (app и server) — в
shared/
Расширения типов внеapp/,server/илиshared/не будут работать с новыми project references. - типы для app — в
- При необходимости настройте опции TypeScript:
export default defineNuxtConfig({ typescript: { // customize tsconfig.app.json tsConfig: { // ... }, // customize tsconfig.shared.json sharedTsConfig: { // ... }, // customize tsconfig.node.json nodeTsConfig: { // ... }, }, nitro: { typescript: { // customize tsconfig.server.json tsConfig: { // ... }, }, }, }) - Обновите CI/сборочные скрипты проверки типов под новый подход с project references.
Новая конфигурация даёт лучшую типобезопасность и IntelliSense при включении, без потери совместимости для существующих проектов.
Удаление экспериментальных опций
🚦 Уровень влияния: минимальный
Что изменилось
В Nuxt 4 эти опции больше не настраиваются:
experimental.treeshakeClientOnly— всегдаtrue(дефолт с v3.0)experimental.configSchema— всегдаtrue(дефолт с v3.3)experimental.polyfillVueUseHead— всегдаfalse(дефолт с v3.4)experimental.respectNoSSRHeader— всегдаfalse(дефолт с v3.4)vite.devBundlerне настраивается — по умолчаниюvite-node
Причины изменений
Значения давно зафиксированы, причин оставлять их конфигурируемыми нет.
Шаги миграции
polyfillVueUseHeadможно реализовать в проекте через этот плагин.respectNoSSRHeader— через серверный middleware.
Удаление конфигурации верхнего уровня generate
🚦 Уровень влияния: минимальный
Что изменилось
Опция конфигурации верхнего уровня generate в Nuxt 4 недоступна, в том числе:
generate.exclude— исключение маршрутов из пререндерингаgenerate.routes— список маршрутов для пререндеринга
Причины изменений
generate остался с Nuxt 2. В Nuxt 3+ предпочтительный способ — nitro.prerender.
Шаги миграции
Замените generate на соответствующие опции nitro.prerender:
export default defineNuxtConfig({
- generate: {
- exclude: ['/admin', '/private'],
- routes: ['/sitemap.xml', '/robots.txt']
- }
+ nitro: {
+ prerender: {
+ ignore: ['/admin', '/private'],
+ routes: ['/sitemap.xml', '/robots.txt']
+ }
+ }
})
Nuxt 2 и Nuxt 3+
Краткое сравнение трёх вариантов Nuxt:
| Функция / Версия | Nuxt 2 | Nuxt Bridge | Nuxt 3+ |
|---|---|---|---|
| Vue | 2 | 2 | 3 |
| Стабильность | 😊 Стабильно | 😊 Стабильно | 😊 Стабильно |
| Производительность | 🏎 Быстро | ✈️ Быстрее | 🚀 Быстрее всего |
| Движок Nitro | ❌ | ✅ | ✅ |
| Поддержка ESM | 🌙 Частично | 👍 Лучше | ✅ |
| TypeScript | ☑️ Опционально | 🚧 Частично | ✅ |
| Composition API | ❌ | 🚧 Частично | ✅ |
| Options API | ✅ | ✅ | ✅ |
| Автоимпорт компонентов | ✅ | ✅ | ✅ |
Синтаксис <script setup> | ❌ | 🚧 Частично | ✅ |
| Автоимпорты | ❌ | ✅ | ✅ |
| webpack | 4 | 4 | 5 |
| Vite | ⚠️ Частично | 🚧 Частично | ✅ |
| Nuxt CLI | ❌ Старый | ✅ nuxt | ✅ nuxt |
| Статические сайты | ✅ | ✅ | ✅ |
Миграция с Nuxt 2 на Nuxt 3+
В руководстве по миграции — пошаговое сравнение возможностей Nuxt 2 и Nuxt 3+ и адаптация приложения.
Nuxt 2 и Nuxt Bridge
Для постепенной миграции с Nuxt 2 на Nuxt 3 можно использовать Nuxt Bridge — слой совместимости, позволяющий подключать возможности Nuxt 3+ в Nuxt 2 по мере необходимости.