Эта статья — перевод оригинальной статьи «Nuxt 4.5».
Также я веду телеграм канал «Frontend по‑флотски», где рассказываю про интересные вещи из мира разработки интерфейсов и AI.
Вступление
Nuxt 4.5 это действительно крупный релиз. В нём обновили сразу три ключевых компонента системы сборки: Vite 8, Rspack 2 и совершенно новый пайплайн на базе Rsbuild для сборщика Rspack.
Также появились экспериментальный режим потокового SSR, стабильная система кодов ошибок, несколько новых composables и соглашений, а ещё множество внутренних улучшений, которые приближают нас к Nuxt 5.
📣 Немного новостей
Подготовка к Nuxt 5
Значительная часть этого релиза это, будем надеяться, незаметные внутренние изменения, которые готовят Nuxt к версии 5.
Команда обновила несколько ключевых зависимостей до последних мажорных версий: unhead v3, unctx v3 и Vite 8. Сам Nuxt теперь собирается с помощью tsdown. Кроме того, появился стабильный контракт выходных файлов сборки nuxt/* с dev-экспортами. Благодаря этому проверка типов в монорепозитории Nuxt работает без предварительного этапа сборки (#35463, #35605).
Большая часть этих изменений сокращает внутренние различия между Nuxt 4 и Nuxt 5, чтобы будущая миграция прошла максимально спокойно и, возможно, даже скучно.
Уже сейчас можно протестировать некоторые потенциально несовместимые изменения Nuxt 5, включив следующую настройку:
future: { compatibilityVersion: 5}
Подробности будут постепенно появляться в руководстве по обновлению.
После выхода Nuxt 4.5 основное внимание команды переключится на стабилизацию Nuxt 5 и создание инструментов совместимости, которые сделают переход на новую версию максимально плавным.
Завершение поддержки Nuxt 3
Поддержка Nuxt 3 завершится 31 июля 2026 года, поэтому впереди осталось всего несколько релизов ветки 3.x.
Тем, кто всё ещё использует Nuxt 3, уже стоит задуматься о переходе. По отзывам большинства разработчиков, обновление с третьей версии на четвёртую прошло без серьёзных проблем, а руководство по миграции регулярно обновляется.
Одновременно с Nuxt 4.5.0 команда выпускает патч для ветки 3.x — версию 3.21.9. В неё перенесены совместимые исправления ошибок и небольшие улучшения из нового релиза.
Главные обновления: Vite 8, Rspack 2, unhead v3 и unctx v3, которые являются мажорными и останутся эксклюзивными для Nuxt 4. Благодаря этому ветка 3.x сохранит стабильность до окончания срока поддержки.
⚡️ Vite 8
Теперь Nuxt работает на Vite 8 (#34256). Обновление ускоряет холодный запуск, переводит внутренние механизмы на актуальную архитектуру Rolldown и включает множество улучшений от команды Vite.
Для большинства приложений переход пройдёт незаметно. Но если вы используете собственные Vite-плагины или нестандартную конфигурацию, стоит просмотреть руководство по миграции Vite и проверить, не затрагивают ли вас изменения.
Vite 8 это мажорное обновление. Если ваш проект напрямую зависит от Vite, например, использует кастомные плагины, изменения в vite.config или сторонние плагины с привязкой к конкретной версии Vite, то перед обновлением в продакшене убедитесь, что всё совместимо.
🦀 Rspack 2 и Rsbuild
Если вы используете сборщик Rspack, это обновление будет особенно заметным. Nuxt перешёл на Rspack 2 (#34929), который стал быстрее и легче, а сам сборщик теперь полностью построен поверх @rsbuild/core (#35489).
Публичный API при этом не изменился. Для подключения по-прежнему достаточно указать builder: 'rspack', а существующие хуки rspack:* продолжат работать:
export default defineNuxtConfig({ builder: 'rspack',})
Но внутри многое изменилось и в лучшую сторону:
-
dev-сервер теперь работает в middleware-режиме через Rsbuild, заменяя
webpack-dev-middlewareиwebpack-hot-middleware(#35575); -
используется отдельный Vue-loader для Rspack, который корректно генерирует идентификаторы scoped-стилей при SSR и строже обрабатывает разрешение ESM-модулей (
#35566); -
эти изменения закладывают основу для полноценной поддержки Rsbuild.
Пока сборщик по-прежнему называется rspack, чтобы не нарушать обратную совместимость. Но под капотом теперь всё целиком работает на Rsbuild.
🌊 Экспериментальный SSR-стриминг
Это одно из самых интересных нововведений релиза. Теперь в Nuxt можно включить потоковый SSR, чтобы заметно улучшить показатель Time to First Byte (#34411).
Вместо того чтобы сначала полностью отрендерить страницу в буфер, а затем отправить её целиком, Nuxt сразу передаёт HTML-оболочку: <head>, стили, preload-подсказки и подключение основных скриптов. После этого содержимое <body> отправляется частями по мере того, как Vue его рендерит.
export default defineNuxtConfig({ experimental: { ssrStreaming: true, },})
Для ботов и поисковых краулеров стриминг автоматически отключается, поэтому поисковые системы по-прежнему получают полностью отрендеренный HTML. При необходимости можно настроить регулярное выражение для определения краулеров, а также отключить стриминг для отдельных маршрутов:
export default defineNuxtConfig({ experimental: { ssrStreaming: { botRegex: /googlebot|bingbot|my-internal-crawler/i, }, }, routeRules: { '/no-stream/**': { streaming: false }, },})
Перед включением этой функции важно учитывать один нюанс. Как только сервер отправляет первый байт ответа, HTTP-статус и заголовки считаются зафиксированными. Поэтому любые изменения ответа после начала рендеринга уже не дойдут до клиента.
Например, это касается вызова setResponseStatus() внутри <script setup>, записи cookie в middleware и других подобных операций.
Распространённые сценарии Nuxt обрабатывает автоматически. Маршруты с правилами redirect, cache, isr, swr, noScripts или ssr: false переключаются обратно на обычный рендеринг с буферизацией. В режиме разработки Nuxt также выводит предупреждения обо всех изменениях ответа, которые не удалось отправить клиенту, поэтому такие проблемы не останутся незамеченными.
SSR-стриминг пока находится в экспериментальном статусе и по умолчанию отключён. Он может быть особенно полезен для страниц с большим объёмом контента, где критически важен быстрый TTFB. Но перед широким внедрением стоит проверить, как функция взаимодействует с логикой, изменяющей HTTP-ответ: статусами, заголовками и cookie.
Подробнее о правилах автоматического фоллбэка и известных ограничениях можно прочитать в документации: Guide → Going Further → Experimental Features → ssrStreaming.
🩺 Стабильные коды ошибок
Одно из самых приятных нововведений релиза в Nuxt: появилась стабильная система кодов ошибок (#35429), построенная на базе nostics.
Теперь предупреждения и ошибки, возникающие во время сборки и выполнения приложения, содержат:
-
стабильный код, например
NUXT_E1001илиNUXT_B5001; -
краткое объяснение причины;
-
конкретный вариант исправления.
Каждый код можно легко найти через поиск или сохранить в закладки. Если проблему нельзя решить одной строкой, сообщение сразу ведёт на отдельную страницу документации.
Например, знакомая ошибка «composable был вызван вне контекста Nuxt» теперь отображается как NUXT_E1001. Вместе с кодом Nuxt сразу показывает причину, возможное решение и ссылку на документацию, где подробно объясняются правила работы контекста и использование runWithContext().
Чтобы не увеличивать размер продакшен-сборки, подробные блоки с объяснением и рекомендациями удаляются. В продакшене остаётся только стабильный код ошибки.
Это фундамент для гораздо более понятных сообщений об ошибках во всём Nuxt. В следующих релизах команда продолжит переводить существующие предупреждения и ошибки на новую систему. 🔥
🎨 Composable useLayout
В Nuxt появился новый composable useLayout, который позволяет узнать, какой layout используется для текущего маршрута (#35623).
Раньше внутри компонента не было удобного и реактивного способа получить ответ на вопрос: «Какой layout сейчас использует эта страница?»
app/components/LayoutBadge.vue
<script setup lang="ts">const layout = useLayout()</script><template> <span>Текущий layout: {{ layout }}</span></template>
Composable возвращает вычисляемый ref только для чтения. Его значение автоматически обновляется при навигации, а также при изменении layout через route rules или definePageMeta.
Подробнее: Docs → API → Composables → Use Layout.
🪟 Именованные представления
Теперь Nuxt поддерживает именованные представления через специальное соглашение об именовании файлов (#35123).
Если родительская страница содержит несколько компонентов <NuxtPage>, каждому из них можно задать имя, а соответствующее представление вынести в соседний файл по схеме name@view.vue.
Структура директорий:
pages/├── parent/│ ├── child.vue│ └── child@sidebar.vue└── parent.vue
pages/parent.vue
<template> <div> <NuxtPage /> <aside> <NuxtPage name="sidebar" /> </aside> </div></template>
При переходе на /parent/child файл child.vue будет отрендерен в стандартном <NuxtPage>, а child@sidebar.vue, то есть в именованном представлении sidebar.
Такая возможность уже давно существует во Vue Router, а теперь она интегрирована и в файловую маршрутизацию Nuxt.
При этом definePageMeta считывается только из файла стандартного представления. Задавать отдельные режимы рендеринга для разных представлений нельзя, так как используется режим родительской страницы.
Подробнее: Docs → Guide → Directory Structure → App → Pages → Named Views.
🚦 Опция enabled для useFetch и useAsyncData
Теперь выполнение запросов можно контролировать с помощью реактивной опции enabled (#33260).
Пока enabled возвращает false, блокируются все способы запуска запроса:
-
первоначальная загрузка данных;
-
вызовы
execute()иrefresh(); -
срабатывания
watch.
Если переключить enabled с true на false, когда запрос уже выполняется, он будет отменён. При этом ранее полученные данные не очистятся.
app/pages/search.vue
<script setup lang="ts">const query = ref('')const { data } = await useFetch('/api/search', { query: { q: query }, // Отправляем запрос только после того, // как пользователь введёт достаточно символов enabled: () => query.value.length > 2,})</script>
Эта возможность особенно полезна для зависимых и условных запросов, которые не должны выполняться до тех пор, пока не будет выполнено определённое условие.
В enabled можно передать getter или ref, поэтому состояние остаётся полностью реактивным.
Подробнее: Docs → API → Composables → Use Async Data.
🔗 Управление prefetch в NuxtLink с кастомными слотами
При использовании <NuxtLink> с пропсом custom Nuxt больше не добавляет обработчики prefetch автоматически. Причина в том, что фреймворк не знает, как именно устроена ваша разметка.
Чтобы ручная настройка оставалась удобной, слот теперь предоставляет всё необходимое для управления предварительной загрузкой (#34539):
<template> <NuxtLink v-slot="{ href, navigate, prefetch, prefetched, shouldPrefetch }" to="/about" custom > <a :href="href" :class="{ 'is-prefetched': prefetched }" @click="navigate" @pointerenter="shouldPrefetch('interaction') && prefetch()" @focus="shouldPrefetch('interaction') && prefetch()" > Страница «О нас» </a> </NuxtLink></template>
Теперь в слоте доступны:
-
prefetch: запускает предварительную загрузку страницы; -
prefetched: показывает, была ли страница уже загружена заранее; удобно, например, для добавления CSS-класса; -
shouldPrefetch: проверяет настройки приложения и параметры соединения пользователя перед запуском prefetch.
Подробнее: Docs → API → Components → Nuxt Link.
⚡️ Передача preload-подсказок при prefetch
Ещё одно нововведение, которое команда предлагает протестировать.
Когда Nuxt заранее загружает ссылку на маршрут с включённым извлечением payload, он уже подготавливает данные и чанки целевой страницы. Теперь с новой экспериментальной опцией experimental.prefetchPreloadTags (#35144) Nuxt также переносит в текущий документ preload-подсказки следующей страницы:
-
<link rel="preload">; -
modulepreload; -
теги, добавленные через
useHead; -
подсказки от модулей, например
<NuxtImg preload>из@nuxt/image.
При этом они преобразуются в rel="prefetch", чтобы не конкурировать за ресурсы с контентом текущей страницы.
nuxt.config.ts
export default defineNuxtConfig({ experimental: { prefetchPreloadTags: true, },})
На практике это означает, что тяжёлые ресурсы из верхней части следующей страницы, например, hero-изображение или критически важный скрипт, начинают загружаться ещё до перехода. Благодаря этому навигация ощущается практически мгновенной.
Пока функция отключена по умолчанию: команда собирает обратную связь и предлагает протестировать её в реальных приложениях.
🌐 import.meta.envName
Теперь имя текущего окружения Nuxt доступно во время выполнения через import.meta.envName как в сборках на Vite, так и при использовании webpack или Rspack (#34844).
Это значение, переданное через флаг --envName, либо автоматически определённое значение по умолчанию. Его можно использовать для условной логики прямо в коде приложения:
if (import.meta.envName === 'staging') { // Включаем поведение только для staging-окружения}
Подробнее: Docs → API → Advanced → Import Meta.
🔭 Каналы трассировки для SSR-событий
Теперь Nuxt публикует трассировки своих серверных подсистем через diagnostics_channel (#35191).
Реализация не привязана к конкретному инструменту мониторинга: Nuxt предоставляет каналы nuxt.render, nuxt.island, nuxt.data и nuxt.plugin, следуя соглашению об именовании untracing. Поверх них можно подключить OpenTelemetry или любую другую систему наблюдаемости.
Функция работает в Node.js, Deno, Bun и Cloudflare Workers.
nuxt.config.ts
export default defineNuxtConfig({ // Включаем собственные каналы Nuxt tracingChannel: true,})
Также каналы можно включать выборочно. В Nuxt 5 появятся дополнительные каналы на уровне Nitro:
nuxt.config.ts
export default defineNuxtConfig({ tracingChannel: { nuxt: true, },})
Пока реестр untracing продолжает формироваться, названия каналов, структура передаваемых данных и ключи настроек ещё могут измениться.
📦 Обновление зависимостей
unhead v3
Система управления содержимым <head> в Nuxt теперь работает на unhead v3 (#34793).
Новая версия стала компактнее, использует внутри синхронный движок и предоставляет более строгую типизацию для useHead из коробки. Кроме того, это обновление позволило реализовать SSR-стриминг.
В unhead v3 появилась более точная типизация для useHead. Это может привести к несовместимым изменениям на уровне типов, если ваш проект полагался на менее строгие типы из второй версии.
Для подавляющего большинства приложений поведение во время выполнения останется прежним. При этом передача Promise в качестве значения, которая уже была помечена устаревшей в v2, больше не поддерживается.
Если после обновления появились ошибки TypeScript, скорее всего, это результат ужесточения типизации, а не регрессия.
unctx v3
Nuxt также перешёл на unctx v3 (#35541). Обновление устраняет целый класс давних проблем с асинхронным контекстом (#33644).
Это часть работы над повышением надёжности контекста composable-функций, которая продолжится в Nuxt 5.
И не только
Также были обновлены:
-
magic-stringдо версии 1; -
Babel до версии 8;
-
Rolldown до последних доступных версий во всех компонентах проекта.
Самый простой способ корректно подтянуть эти обновления — выполнить команду:
nuxt upgrade --dedupe
🛠️ Nuxt CLI
В этот релиз вошли улучшения из @nuxt/cli версий 3.36 и 3.37:
-
Команда
nuxt module removeпозволяет удалить модуль и очистить связанную с ним конфигурацию (nuxt/cli#1306). Это логичное дополнение кnuxt module add. -
nuxt initтеперь поддерживает неинтерактивный режим, что удобно для скриптов и CI (nuxt/cli#1341). Кроме того, команда использует пакетный менеджер, указанный в самом шаблоне, и больше не предлагает выбрать его вручную (nuxt/cli#1330). -
Улучшен процесс настройки проверки типов: если в проекте отсутствуют
vue-tscилиtypescript, командаnuxt typecheckпредложит установить их автоматически (nuxt/cli#1316). -
Появилась поддержка Golar для проверки типов: теперь
nuxt typecheckможет использовать его как альтернативуvue-tsc(nuxt/cli#1362). Golar выбирается автоматически, если он установлен или в проекте есть файлgolar.config.*.
При необходимости инструмент можно указать вручную с помощью флага --checker:
nuxt typecheck --checker=golar
Доступные варианты:
nuxt typecheck --checker=vue-tscnuxt typecheck --checker=golar
-
Dev-сервер теперь учитывает слои приложения: при изменении локального файла
nuxt.configвнутри layer он автоматически перезапускается (nuxt/cli#1345).
🧩 TypeScript-плагин и именованные слоты layouts
Экспериментальный TypeScript-плагин Nuxt работает на базе @dxup/nuxt, и в этот релиз вошла его обновлённая версия.
Если вы ещё не пробовали плагин, включение experimental.typescriptPlugin добавляет несколько полезных возможностей для редактора:
-
при переименовании автоматически импортированного компонента обновляются все места его использования;
-
работает переход к определению для glob-импортов;
-
можно переходить к определениям Nitro-маршрутов,
definePageMeta,runtimeConfigи типизированных имён маршрутов.
В новой версии также появилась экспериментальная runtime-функция — именованные слоты layouts (KazariEX/dxup#20).
Теперь на странице можно объявить именованный слот верхнего уровня, и его содержимое будет передано в соответствующий слот активного layout. Таким образом, страница может вставлять контент в слоты своего layout, а стандартные файловые layouts такой возможности не предоставляют.
Включить функцию можно вместе с TypeScript-плагином:
nuxt.config.ts
export default defineNuxtConfig({ experimental: { typescriptPlugin: true, }, dxup: { features: { namedLayoutSlots: true, }, },})
После этого layout может объявить именованные слоты:
layouts/center.vue
<template> <slot /> <slot name="side" one="one" /></template>
А любая страница, использующая этот layout, сможет передать в них свой контент:
pages/about.vue
<script setup lang="ts">definePageMeta({ layout: 'center' })</script><template> <template #side="{ one }"> Значение "{{ one }}" передано из слота layout. </template> <div>Страница «О нас»</div></template>
Подробнее: Docs → Guide → Going Further → Experimental Features → typescriptPlugin.
🔥 Производительность и стабильность
Как и всегда, большая часть работы в этом релизе была направлена на то, чтобы сделать Nuxt быстрее и надёжнее.
Одно из улучшений можно включить уже сейчас, это общий файловый watcher (#35143). Vite и так использует watcher на базе chokidar, поэтому Nuxt теперь может переиспользовать его вместо запуска второго процесса. Это снижает потребление памяти и уменьшает количество открытых файловых дескрипторов.
В режиме compatibilityVersion: 5 эта настройка станет стандартной, но протестировать её можно уже сейчас:
nuxt.config.ts
export default defineNuxtConfig({ experimental: { watcher: 'builder', },})
Также в релиз вошло несколько улучшений производительности, которые не требуют дополнительной настройки:
-
Ускорен запуск dev-сервера — часть операций Nitro и файловой маршрутизации, необходимых только в режиме разработки, теперь выполняется позже и более эффективно (
#35381,#35383). -
Продакшен-сборки стали компактнее — чанк
island-rendererбольше не создаётся, если приложение не использует islands (#35456), а код обработки плагинов удаляется из сборки с помощью tree shaking (#35278). -
Улучшено хеширование islands — хеши islands и ключей стали стабильнее, а функции
getIslandHashиhashKeyтеперь доступны публично (#35583). -
Оптимизированы параллельное выполнение и операции ввода-вывода при разрешении путей в Nuxt Kit и обработке встроенных стилей в Nitro (
#35511,#35514).
Кроме того, появилось несколько полезных исправлений:
-
$fetchтеперь автоматически импортируется в пользовательский код. Это устраняет пограничные случаи с вызовом$fetch.createна верхнем уровне при использовании формата вывода Rolldown (#35581). -
Nuxt теперь учитывает системные переменные окружения
HTTP_PROXYиHTTPS_PROXYв окружении сборщика (#35183). -
HMR теперь корректно работает с
defineNuxtComponentв JSX (#35620).
⚠️ Что стоит учесть перед обновлением
Как уже упоминалось выше, в этом релизе обновили сразу три ключевые зависимости до новых мажорных версий. Для большинства приложений переход пройдёт незаметно, но некоторые моменты всё же стоит проверить:
-
Vite 8: убедитесь, что ваши кастомные Vite-плагины и конфигурация совместимы с новой версией. Подробности есть в руководстве по миграции Vite.
-
Rspack 2: если вы используете
builder: 'rspack', учтите, что теперь сборщик внутри полностью основан на Rsbuild. Пользовательскую конфигурацию Rspack может потребоваться пересмотреть. -
unhead v3: из-за более строгой типизации
useHeadвозможны несовместимые изменения на уровне TypeScript.
⬆️ Обновление
Для перехода на новую версию команда рекомендует выполнить:
npx nuxt upgrade --dedupe
Если вы пока остаётесь на ветке 3.x:
npx nuxt@latest upgrade --dedupe --channel=v3
Команда удалит дублирующиеся зависимости из lock-файла и поможет корректно подтянуть обновления пакетов, от которых зависит Nuxt. Особенно это важно для экосистемы unjs, поскольку в этом релизе обновилось сразу несколько мажорных зависимостей.
Если вы переходите с более старой версии Nuxt, обязательно ознакомьтесь с руководством по обновлению.
👉 Полный список изменений
Спасибо всем контрибьюторам, которые приняли участие в подготовке этого релиза. Обновление получилось действительно крупным, и без вашего вклада оно бы не состоялось. 💚
ссылка на оригинал статьи https://habr.com/ru/articles/1060996/