Вышел Nuxt 4.5

от автора

Эта статья — перевод оригинальной статьи «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/