Руководство по обновлению
Обновление Nuxt
Последний релиз
Чтобы обновить Nuxt до последней версии, используйте команду nuxi upgrade
.
npx nuxi upgrade
Канал ночных релизов (Nightly Release)
Чтобы использовать последнюю сборку Nuxt и тестировать функции до их выхода, ознакомьтесь с информацией о канале ночных релизов.
latest
канала ночных релизов отслеживает ветку Nuxt v4, а это значит, что сейчас в ней особенно вероятны изменения - будьте осторожны!Вы можете подключиться к ночным релизам ветки 3.x с помощью тега "nuxt": "npm:nuxt-nightly@3x"
.Тестируем Nuxt 4
Дата выхода Nuxt 4 будет объявлена позднее. Она зависит от наличия достаточного времени после основного релиза Nitro для тщательного тестирования сообществом. Вы можете следить за прогрессом выхода Nitro в этом PR.
До выхода релиза многие критические изменения, которые войдут в Nuxt 4, уже можно протестировать в версиях Nuxt 3.12 и выше.
Внедрение Nuxt 4
Первым шагом необходимо обновить Nuxt до последней версии.
Затем вы можете установить compatibilityVersion
для соответствия поведению Nuxt 4:
export default defineNuxtConfig({
future: {
compatibilityVersion: 4,
},
// Чтобы снова включить все поведение Nuxt v3, установите следующие параметры:
// srcDir: '.',
// dir: {
// app: 'app'
// },
// experimental: {
// sharedPrerenderData: false,
// compileTemplate: true,
// resetAsyncDataToUndefined: true,
// templateUtils: true,
// relativeWatchPaths: true,
// normalizeComponentNames: false
// defaults: {
// useAsyncData: {
// deep: true
// }
// }
// },
// unhead: {
// renderSSRHeadOptions: {
// omitLineBreaks: false
// }
// }
})
Когда вы установите compatibilityVersion
в 4
, настройки по умолчанию во всей вашей конфигурации Nuxt изменятся, чтобы выбрать поведение Nuxt v4, но вы можете выборочно включить поведение Nuxt v3 при тестировании, следуя закомментированным строкам выше. Пожалуйста, пишите о проблемах, чтобы мы могли решить их в Nuxt или в экосистеме.
Миграция на Nuxt 4
Ломающие или значительные изменения, а также шаги по миграции для обеспечения обратной/прямой совместимости, будут отмечены здесь.
compatibilityVersion: 4
.Миграция с использованием Codemod
To facilitate the upgrade process, we have collaborated with the Codemod team to automate many migration steps with some open-source codemods.
npx codemod feedback
🙏For a complete list of Nuxt 4 codemods, detailed information on each, their source, and various ways to run them, visit the Codemod Registry.
You can run all the codemods mentioned in this guide using the following codemod
recipe:
npx codemod@latest nuxt/4/migration-recipe
This command will execute all codemods in sequence, with the option to deselect any that you do not wish to run. Each codemod is also listed below alongside its respective change and can be executed independently.
Новая структура директорий
🚦 Уровень влияния: Значительный
Теперь Nuxt по умолчанию использует новую структуру каталогов с обратной совместимостью (если Nuxt обнаружит, что вы используете старую структуру, например, каталог верхнего уровня pages/
, то новая структура не будет применяться).
Что изменилось
- в новом Nuxt по умолчанию
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/
server/
api/
middleware/
plugins/
routes/
utils/
nuxt.config.ts
👉 Более подробную информацию можно найти в PR, реализующем это изменение.
Причины таких изменений
- Производительность - размещение всего кода в корне репозитория приводит к проблемам со сканированием/включением папок
.git/
иnode_modules/
наблюдателями FS (файловой системы), что может значительно замедлить запуск на не-Mac ОС. - Безопасность типов в IDE -
server/
и остальное приложение работают в двух совершенно разных контекстах с разными глобальными импортами, и убедиться, чтоserver/
не находится внутри той же папки, что и остальное приложение, — важный первый шаг к обеспечению хорошего автодополнения в вашей IDE.
Этапы миграции
- Создайте новую директорию с именем
app/
. - Переместите в нее папки
assets/
,components/
,composables/
,layouts/
,middleware/
,pages/
,plugins/
иutils/
, а такжеapp.vue
,error.vue
,app.config.ts
. Если у вас есть папкиapp/router-options.ts
илиapp/spa-loading-template.html
, эти пути остаются прежними. - Убедитесь, что папки
nuxt.config.ts
,content/
,layers/
,modules/
,public/
иserver/
находятся вне папкиapp/
, в корне вашего проекта.
npx codemod@latest nuxt/4/file-structure
Однако миграция не является обязательной. Если вы хотите сохранить текущую структуру папок, Nuxt автоматически определит ее. (Если это не так, пожалуйста, поднимите вопрос.) Единственное исключение - если у вас уже есть собственный srcDir
. В этом случае вы должны знать, что ваши папки modules/
, public/
и server/
будут разрешены из вашего rootDir
, а не из вашего пользовательского srcDir
. Вы можете отменить это, настроив dir.modules
, dir.public
и serverDir
, если вам это необходимо.
Вы также можете принудительно использовать структуру папок v3 с помощью следующей конфигурации:
export default defineNuxtConfig({
// Это вернет новый srcDir по умолчанию из `app` обратно в вашу корневую директорию
srcDir: '.',
// Это определяет префикс директории для `app/router.options.ts` и `app/spa-loading-template.html`.
dir: {
app: 'app'
}
})
Deduplication of Route Metadata
🚦 Impact Level: Minimal
What Changed
It's possible to set some route metadata using definePageMeta
, such as the name
, path
, and so on. Previously these were available both on the route and on route metadata (for example, route.name
and route.meta.name
).
Now, they are only accessible on the route object.
Reasons for Change
This is a result of enabling experimental.scanPageMeta
by default, and is a performance optimization.
Migration Steps
The migration should be straightforward:
const route = useRoute()
- console.log(route.meta.name)
+ console.log(route.name)
Normalized Component Names
🚦 Impact Level: Moderate
Vue will now generate component names that match the Nuxt pattern for component naming.
What Changed
By default, if you haven't set it manually, Vue will assign a component name that matches the filename of the component.
├─ components/
├─── SomeFolder/
├───── MyComponent.vue
In this case, the component name would be MyComponent
, as far as Vue is concerned. If you wanted to use <KeepAlive>
with it, or identify it in the Vue DevTools, you would need to use this name.
But in order to auto-import it, you would need to use SomeFolderMyComponent
.
With this change, these two values will match, and Vue will generate a component name that matches the Nuxt pattern for component naming.
Migration Steps
Ensure that you use the updated name in any tests which use findComponent
from @vue/test-utils
and in any <KeepAlive>
which depends on the name of your component.
Alternatively, for now, you can disable this behaviour with:
export default defineNuxtConfig({
experimental: {
normalizeComponentNames: false
}
})
Shared Prerender Data
🚦 Impact Level: Moderate
Vue will now generate component names that match the Nuxt pattern for component naming.
What Changed
By default, if you haven't set it manually, Vue will assign a component name that matches the filename of the component.
├─ components/
├─── SomeFolder/
├───── MyComponent.vue
In this case, the component name would be MyComponent
, as far as Vue is concerned. If you wanted to use <KeepAlive>
with it, or identify it in the Vue DevTools, you would need to use this name.
But in order to auto-import it, you would need to use SomeFolderMyComponent
.
With this change, these two values will match, and Vue will generate a component name that matches the Nuxt pattern for component naming.
Migration Steps
Ensure that you use the updated name in any tests which use findComponent
from @vue/test-utils
and in any <KeepAlive>
which depends on the name of your component.
Alternatively, for now, you can disable this behaviour with:
export default defineNuxtConfig({
experimental: {
normalizeComponentNames: false
}
})
Общие данные пререндера
🚦 Уровень воздействия: Средний
Что изменилось
Мы включили ранее экспериментальную возможность обмена данными из вызовов useAsyncData
и useFetch
на разных страницах. См. оригинальный PR.
Причины таких изменений
Эта функция автоматически разделяет данные между страницами, которые подвергаются пререндеру. Это может привести к значительному повышению производительности при предрендеринге сайтов, использующих useAsyncData
или useFetch
и получающих одни и те же данные на разных страницах.
Например, если ваш сайт требует вызова useFetch
для каждой страницы (например, для получения навигационных данных для меню или настроек сайта из CMS), эти данные будут получены только один раз при предварительном рендеринге первой страницы, которая их использует, а затем кэшированы для использования при предварительном рендеринге других страниц.
Этапы миграции
Убедитесь, что любой уникальный ключ ваших данных всегда можно разрешить в те же самые данные. Например, если вы используете useAsyncData
для получения данных, относящихся к определенной странице, вы должны предоставить ключ, который однозначно соответствует этим данным. (Функция useFetch
должна сделать это автоматически).
// Это было бы небезопасно на динамической странице (например, `[slug].vue`), потому что slug маршрута разный
// в получаемых данных, но Nuxt не может этого знать, потому что это не отражено в ключе.
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
или error.value
null
, вы можете обновить эти проверки, чтобы проверять на undefined
вместо этого.
npx codemod@latest nuxt/4/default-data-error-value
Если у вас возникнут какие-либо проблемы, вы можете вернуться к прежнему поведению с помощью:
export default defineNuxtConfig({
experimental: {
defaults: {
useAsyncData: {
value: 'null',
errorValue: 'null'
}
}
}
})
Пожалуйста, сообщите о проблеме, если вы делаете это, так как мы не планируем сохранять эту настройку.
Удаление устаревших значений boolean
для опции dedupe
при вызове refresh
в useAsyncData
и useFetch
🚦 Уровень воздействия: Минимальный
Что изменилось
Ранее в refresh
можно было передавать dedupe: boolean
. Это были псевдонимы cancel
(true
) и defer
(false
).
const { refresh } = await useAsyncData(async () => ({ message: 'Привет, Nuxt 3!' }))
async function refreshData () {
await refresh({ dedupe: true })
}
Причины таких изменений
Эти псевдонимы были удалены для большей ясности.
Проблема возникла при добавлении dedupe
в качестве опции к useAsyncData
, и мы удалили булевы значения, поскольку они оказались противоположными.
refresh({ dedupe: false })
означало "не отменять существующие запросы в пользу нового". Но передача dedupe: true
в опциях useAsyncData
означает "не делать никаких новых запросов, если есть существующий отложенный запрос". (См. PR.)
Этапы миграции
Миграция должна быть простой:
const { refresh } = await useAsyncData(async () => ({ message: 'Привет, 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
при итерации по нему. Это должно соблюдаться при сбросе/очистке данных.
Этапы миграции
Если у вас возникнут какие-либо проблемы, вы можете пока что вернуться к прежнему поведению, используя:
export default defineNuxtConfig({
experimental: {
resetAsyncDataToUndefined: true,
}
})
Пожалуйста, сообщите о проблеме, если вы делаете это, так как мы не планируем сохранять эту настройку.
Поверхностная реактивность данных в useAsyncData
и useFetch
🚦 Уровень воздействия: Минимальный
Объект data
, возвращаемый при использовании useAsyncData
, useFetch
, useLazyAsyncData
и useLazyFetch
, теперь представляет собой shallowRef
, а не ref
.
Что изменилось
При получении новых данных все, что зависит от data
, все равно будет реактивным, потому что весь объект будет заменен. Но если ваш код изменит свойство внутри этой структуры данных, это не вызовет никакой реактивности в вашем приложении.
Причины таких изменений
Это дает значительное повышение производительности для глубоко вложенных объектов и массивов, поскольку Vue не нужно следить за изменением каждого отдельного свойства/массива. В большинстве случаев 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-reactivity
Абсолютные пути наблюдения в builder:watch
🚦 Уровень воздействия: Минимальный
Что изменилось
Nuxt-хук builder:watch
теперь выдает путь, который является абсолютным, а не относительным к srcDir
вашего проекта.
Причины таких изменений
Это позволяет нам поддерживать просмотр путей, которые находятся за пределами вашего srcDir
, а также обеспечивает лучшую поддержку слоев и других более сложных паттернов.
Этапы миграции
Мы уже проактивно мигрировали публичные модули Nuxt, которые, как нам известно, используют этот хук. Смотрите issue #25339.
Однако, если вы являетесь автором модуля, использующего хук builder:watch
, и хотите сохранить обратную/будущую совместимость, вы можете использовать следующий код, чтобы убедиться, что ваш код работает одинаково как в Nuxt v3, так и в Nuxt 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) и позволяет нам сосредоточиться на единственном способе доступа к данным приложения Nuxt - useNuxtApp()
.
Этапы миграции
Данные по-прежнему доступны, но доступ к ним можно получить с помощью useNuxtApp().payload
:
- console.log(window.__NUXT__)
+ console.log(useNuxtApp().payload)
Сканирование индекса директории
🚦 Уровень воздействия: Средний
Что изменилось
Дочерние папки в папке middleware/
также проверяются на наличие файлов index
, и теперь они также регистрируются как middleware в вашем проекте.
Причины таких изменений
Nuxt автоматически сканирует ряд папок, включая middleware/
и plugins/
.
Дочерние папки в вашей папке 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
- это большая зависимость, и она не используется в большинстве проектов.
Наконец, предоставление функций сериализации кода непосредственно в Nuxt не является идеальным решением. Вместо этого мы поддерживаем такие проекты, как unjs/knitwork, которые могут быть зависимы от вашего проекта, и где о проблемах безопасности можно сообщать/решать напрямую, не требуя обновления самого Nuxt.
Этапы миграции
Мы подняли PR для обновления модулей с использованием синтаксиса EJS, но если вам нужно сделать это самостоятельно, у вас есть три обратно/будуще совместимые альтернативы:
- Перенос логики интерполяции строк непосредственно в
getContents()
. - Использование пользовательской функции для обработки замены, как, например, в https://github.com/nuxt-modules/color-mode/pull/240.
- Продолжение использования
lodash
, как зависимости от вашего проекта, а не Nuxt:
+ import { readFileSync } from 'node:fs'
+ import { template } from 'lodash-es'
// ...
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
Удаление экспериментальных функций
🚦 Уровень воздействия: Минимальный
Что изменилось
Четыре экспериментальные функции больше не настраиваются в 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
Nuxt 2 против Nuxt 3+
В таблице ниже приведено краткое сравнение между 3 версиями Nuxt:
Feature / Version | Nuxt 2 | Nuxt Bridge | Nuxt 3+ |
---|---|---|---|
Vue | 2 | 2 | 3 |
Стабильность | 😊 Стабильный | 😊 Стабильный | 😊 Стабильный |
Производительности | 🏎 Быстрый | ✈️ Быстрее | 🚀 Быстрейший |
Движок Nitro | ❌ | ✅ | ✅ |
Поддержка ESM | 🌙 Частично | 👍 Лучше | ✅ |
TypeScript | ☑️ Opt-in | 🚧 Частично | ✅ |
Composition API | ❌ | 🚧 Частично | ✅ |
Options API | ✅ | ✅ | ✅ |
Автоимпорт компонент | ✅ | ✅ | ✅ |
<script setup> синктаксис | ❌ | 🚧 Частично | ✅ |
Автоимпорты | ❌ | ✅ | ✅ |
webpack | 4 | 4 | 5 |
Vite | ⚠️ Частично | 🚧 Частично | ✅ |
Nuxi CLI | ❌ Устарел | ✅ nuxi | ✅ nuxi |
Статические сайты | ✅ | ✅ | ✅ |
С Nuxt 2 на Nuxt 3+
Руководство по миграции содержит пошаговое сравнение функций Nuxt 2 с функциями Nuxt 3+ и рекомендации по адаптации вашего текущего приложения.
С Nuxt 2 на Nuxt Bridge
Если вы предпочитаете постепенно переводить свои приложения Nuxt 2 на Nuxt 3, вы можете использовать Nuxt Bridge. Nuxt Bridge - это уровень совместимости, который позволяет вам использовать функции Nuxt 3+ в Nuxt 2 с помощью механизма opt-in.