Руководство по обновлению

Узнайте, как обновиться до последней версии Nuxt.

Обновление Nuxt

Последний релиз

Чтобы обновить Nuxt до последнего релиза, используйте команду nuxt upgrade.

npx nuxt upgrade

Канал ночных сборок

Чтобы использовать свежую сборку Nuxt и тестировать возможности до релиза, прочитайте руководство по каналу ночных сборок.

Тестирование Nuxt 5

Nuxt 5 сейчас в разработке. До релиза многие breaking changes Nuxt 5 можно опробовать, начиная с Nuxt 4.2+.

Подключение к поведению Nuxt 5

Сначала обновите Nuxt до последнего релиза.

Затем задайте future.compatibilityVersion, чтобы включить поведение Nuxt 5:

nuxt.config.ts
export default defineNuxtConfig({
  future: {
    compatibilityVersion: 5,
  },
})

Когда future.compatibilityVersion равен 5, значения по умолчанию в конфигурации Nuxt меняются в сторону поведения Nuxt v5, в том числе:

Раздел может меняться до финального релиза; если вы тестируете Nuxt 5 с future.compatibilityVersion: 5, периодически проверяйте эту страницу.

Ниже перечислены существенные изменения и шаги миграции для обратной и прямой совместимости.

Миграция на Vite Environment API

🚦 Уровень влияния: Средний

Что изменилось

Nuxt 5 переходит на новый Environment API Vite 6, который формализует понятие окружений и даёт лучший контроль над конфигурацией для каждого окружения.

Раньше Nuxt использовал отдельные конфигурации Vite для клиента и сервера. Теперь — общую конфигурацию Vite с плагинами под конкретные окружения, которые через метод applyToEnvironment() нацеливаются на нужные окружения.

Vite Environment API в Nuxt 5 всегда включён. Опция experimental.viteEnvironmentApi удалена.

Ключевые изменения:

  1. Устарели окружение-специфичные вызовы extendViteConfig(): опции server и client в extendViteConfig() помечены как устаревшие и при использовании выводят предупреждения.
  2. Изменилась регистрация плагинов: у Vite-плагинов, зарегистрированных через addVitePlugin() и нацеленных только на одно окружение (через server: false или client: false), не вызываются хуки config или configResolved.
  3. Общая конфигурация: хуки vite:extendConfig и vite:configResolved работают с общей конфигурацией, а не с раздельными client/server.

Зачем это сделано

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' /* или 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) {
    // здесь можно задать конфигурацию Vite для конкретного окружения
    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) {
    // здесь можно задать конфигурацию Vite для конкретного окружения
    if (name === 'client') {
      config.optimizeDeps ||= {}
      config.optimizeDeps.include ||= []
      config.optimizeDeps.include.push('my-package')
    }
  },
  applyToEnvironment (environment) {
    return environment.name === 'client'
  },
}))
Подробнее об Environment API в Vite

Миграция на Vite 8

🚦 Уровень влияния: Средний

Что изменилось

Nuxt 5 переходит с Vite 7 на Vite 8: вместо esbuild и Rollup в качестве бандлера используется Rolldown. Сборки становятся заметно быстрее, но есть ряд breaking changes.

В отличие от миграции на Vite Environment API, это изменение нельзя заранее включить через future.compatibilityVersion: 5. Чтобы проверить совместимость с Vite 8 заранее, добавьте в package.json переопределение версии "vite": "^8.0.0-beta.15".

Большую часть миграции Nuxt выполняет сам, но имейте в виду следующее:

  • vite.esbuild и vite.optimizeDeps.esbuildOptions устарели в пользу vite.oxc и vite.optimizeDeps.rolldownOptions. Vite 8 пока конвертирует их автоматически, но в будущем они будут удалены.
  • build.rollupOptions устарел в пользу build.rolldownOptions.
  • Поведение CommonJS interop изменилось. Если вы импортируете CJS-модули, см. руководство по миграции Vite 8.
Полное руководство по миграции на Vite 8 со всеми breaking changes и шагами миграции.

Миграция на Nitro v3

🚦 Уровень влияния: Значительный

Что изменилось

Nuxt 5 переходит на Nitro v3 — крупную переработку серверного движка. Nitro v3 построен на srvx и h3 v2 и везде использует веб-стандартные API Request/Response. Это улучшает производительность и единообразие API, но ломает совместимость серверного кода.

Интеграция Nitro v3 ещё в работе: ожидайте дальнейших изменений и дополнительных шагов для упрощения миграции.
Анонс бета-версии Nitro v3 с полным обзором.
Полное руководство по миграции на Nitro v3 со всеми breaking changes.

Ниже — изменения, наиболее важные для разработчиков приложений и авторов модулей Nuxt.

Пакет и пути импорта

Пакет nitropack переименован в nitro. Изменились все пути импорта:

ДоПосле
nitropacknitro
nitropack/typesnitro/types
nitropack/runtimenitro
h3 (серверные утилиты)nitro/h3

Автоимпорты в серверных маршрутах (defineEventHandler, getQuery, readBody, useRuntimeConfig и т.д.) работают как прежде.

Если в серверном коде есть явные импорты, обновите их:

- import { defineEventHandler, getQuery } from 'h3'
+ import { defineEventHandler, getQuery } from 'nitro/h3'

Для авторов модулей расширения типов должны указывать на новый путь модуля:

- declare module 'nitropack/types' {
+ declare module 'nitro/types' {
    interface NitroRouteRules {
      myModule?: { /* ... */ }
    }
  }

Обработка ошибок: status/statusText вместо statusCode/statusMessage

В h3 v2 свойства ошибок переименованы в соответствии с веб-стандартами:

  createError({
-   statusCode: 404,
-   statusMessage: 'Not Found',
+   status: 404,
+   statusText: 'Not Found',
  })

В серверных маршрутах вместо createError из h3 используется класс HTTPError:

- import { createError } from 'h3'
+ import { HTTPError } from 'nitro/h3'

  export default defineEventHandler(() => {
-   throw createError({ statusCode: 400, statusMessage: 'Bad request' })
+   throw new HTTPError({ status: 400, statusText: 'Bad request' })
  })
Во Vue-части приложения (каталог app/) композабл createError от Nuxt по-прежнему работает и рекомендуется для выброса ошибок.

Изменения Server Event API (h3 v2)

Объект H3Event теперь опирается на веб-стандартные API:

Свойства запроса:

- event.path              // строка
+ event.url.pathname      // объект URL — используйте .pathname, .search, .hash

- event.method            // строка
+ event.req.method        // через Web Request

- event.node.req.headers  // Node.js IncomingHttpHeaders
+ event.req.headers       // Web Headers API (.get(), .set(), .has())

Свойства ответа:

- event.node.res.statusCode = 200
+ event.res.status = 200

- event.node.res.statusMessage = 'OK'
+ event.res.statusText = 'OK'

- setResponseHeader(event, 'x-custom', 'value')
+ event.res.headers.set('x-custom', 'value')

- appendResponseHeader(event, 'set-cookie', cookie)
+ event.res.headers.append('set-cookie', cookie)

useRuntimeConfig() больше не принимает event

В Nitro v3 useRuntimeConfig() в серверных маршрутах не принимает (и не требует) аргумент event:

  export default defineEventHandler((event) => {
-   const config = useRuntimeConfig(event)
+   const config = useRuntimeConfig()
  })

Правила маршрутов: statusCode переименован в status

Для правил редиректа изменилось имя свойства:

  export default defineNuxtConfig({
    routeRules: {
      '/old-page': {
-       redirect: { to: '/new-page', statusCode: 302 },
+       redirect: { to: '/new-page', status: 302 },
      },
    },
  })

Дополнительно для авторов модулей

  • Импорты плагинов Nitro: для явных импортов используйте import { definePlugin } from 'nitro' (автоимпорты по-прежнему работают).
  • Хуки рантайма: nitroApp.hooks.hook('beforeResponse', ...) и nitroApp.hooks.hook('afterResponse', ...) заменены на nitroApp.hooks.hook('response', ...).
  • getRouteRules() из nitro/app: на сервере хелпер изменился с getRouteRules(event) на getRouteRules(method, pathname) и возвращает { routeRules }.

Удаление experimental.externalVue

🚦 Уровень влияния: Минимальный

Что изменилось

Опция experimental.externalVue удалена. Зависимости компилятора Vue (@babel/parser, @vue/compiler-core, @vue/compiler-dom, @vue/compiler-ssr, estree-walker) при отключённом vue.runtimeCompiler теперь всегда подменяются мок-прокси в серверном бандле.

Зачем это сделано

После перехода на Nitro v3 зависимости по умолчанию попадают в серверный вывод целиком (в Nitro v2 node_modules внешне подключались). Опция externalVue изначально держала Vue внешней зависимостью, чтобы не дублировать копии Vue в бандле; в Nitro v3 всё равно всё бандлится, и опция стала no-op.

Серверные сборки Vue тянут полный компилятор, включая @babel/parser (~465 КБ) и другие пакеты, хотя они нужны только при vue.runtimeCompiler.

Постоянная подмена этих зависимостей моками уменьшает размер серверного бандла по умолчанию примерно на 860 КБ (~59%).

Шаги миграции

Если вы явно задавали experimental.externalVue, удалите эту опцию.

  export default defineNuxtConfig({
    experimental: {
-     externalVue: false,
    },
  })
При vue.runtimeCompiler: true настоящие пакеты компилятора по-прежнему включаются в сборку.

@vitejs/plugin-vue-jsx стал опциональным

🚦 Уровень влияния: Минимальный

Что изменилось

@vitejs/plugin-vue-jsx больше не устанавливается по умолчанию вместе с @nuxt/vite-builder. Это опциональная peer-зависимость, подключаемая по требованию при появлении .jsx или .tsx в сборке.

Если в проекте есть JSX/TSX-компоненты, Nuxt сам предложит установить пакет.

Зачем это сделано

Плагин @vitejs/plugin-vue-jsx тянет тяжёлое дерево зависимостей (Babel, @vue/babel-plugin-jsx и др.), ненужное проектам без JSX. Опциональность уменьшает размер установки по умолчанию и ускоряет разрешение зависимостей для большинства проектов.

Шаги миграции

Если используются файлы .jsx или .tsx, добавьте @vitejs/plugin-vue-jsx в devDependencies:

npm install -D @vitejs/plugin-vue-jsx

Либо Nuxt сам предложит установить пакет при первой обработке JSX/TSX в dev.

Если JSX не используется, ничего менять не нужно.

Удаление устаревшей поддержки _renderResponse

🚦 Уровень влияния: Минимальный

Что изменилось

ssrContext._renderResponse больше не проверяется как запасной вариант. Используется только внутренний ssrContext['~renderResponse'] (выставляется композаблом роутера Nuxt).

Зачем это сделано

Свойство _renderResponse оставляли для обратной совместимости после #33896, где внутренний API перешёл на ~renderResponse. В TODO было указано удалить это в Nuxt v5.

Шаги миграции

Если вы напрямую задавали ssrContext._renderResponse (это никогда не было публичным API), используйте ssrContext['~renderResponse']. Композабл роутера Nuxt уже использует новое свойство; через navigateTo или middleware маршрута менять ничего не нужно.

Неасинхронный callHook

🚦 Уровень влияния: Минимальный

Что изменилось

После обновления до hookable v6 callHook может вернуть void вместо всегда возвращаемого Promise<void>. Это заметно снижает накладные расходы: лишние Promise не создаются, если хуков нет или все они синхронные.

По умолчанию (при compatibilityVersion: 4) Nuxt оборачивает callHook в Promise.resolve(), чтобы цепочки .then() и .catch() продолжали работать. При compatibilityVersion: 5 эта обёртка снимается.

Это касается и хуков сборки Nuxt (в модулях), и рантайм-хуков (в коде приложения).

Зачем это сделано

В hookable v6 callHook примерно в 20–40 раз быстрее, так как не создаёт Promise, когда он не нужен. Выигрывают приложения с большим числом вызовов хуков.

Шаги миграции

Если вы или модули используете callHook с .then() или .catch(), перейдите на await:

- nuxtApp.callHook('my:hook', data).then(() => { ... })
+ await nuxtApp.callHook('my:hook', data)
- nuxtApp.hooks.callHook('my:hook', data).catch(err => { ... })
+ try { await nuxtApp.hooks.callHook('my:hook', data) } catch (err) { ... }
Протестировать можно заранее, задав future.compatibilityVersion: 5 (см. Тестирование Nuxt 5) или явно experimental.asyncCallHook: false.

Либо можно заставить callHook всегда возвращать Promise:

nuxt.config.ts
export default defineNuxtConfig({
  experimental: {
    asyncCallHook: true,
  },
})

Плейсхолдеры клиентских компонентов в виде комментариев

🚦 Уровень влияния: Минимальный

Что изменилось

При compatibilityVersion: 5 клиентские компоненты (файлы .client.vue и обёртки createClientOnly()) на сервере рендерят HTML-комментарий (<!--placeholder-->) вместо пустого <div>.

Зачем это сделано

Если плейсхолдер <div> и корень компонента — один и тот же тег, рантайм Vue при гидратации может не пере-применить setScopeId, из-за чего пропадают scoped-стили после монтирования. Узел-комментарий устраняет конфликт имён тегов.

Шаги миграции

Если вы рассчитывали, что плейсхолдер <div> унаследует атрибуты (class, style и т.д.) для вёрстки (например, чтобы избежать сдвига), оберните компонент в <ClientOnly> со слотом #fallback:

- <MyComponent class="placeholder" style="min-height: 200px" />
+ <ClientOnly>
+   <MyComponent />
+   <template #fallback>
+     <div class="placeholder" style="min-height: 200px"></div>
+   </template>
+ </ClientOnly>
Протестировать можно заранее через future.compatibilityVersion: 5 (см. Тестирование Nuxt 5) или явно experimental.clientNodePlaceholder: true.

Либо вернуть прежнее поведение с плейсхолдером <div>:

nuxt.config.ts
export default defineNuxtConfig({
  experimental: {
    clientNodePlaceholder: false,
  },
})