Руководство по обновлению
Обновление Nuxt
Последний релиз
Чтобы обновить Nuxt до последней версии, используйте команду nuxt upgrade.
npx nuxt upgrade
yarn nuxt upgrade
pnpm nuxt upgrade
bun x nuxt upgrade
Канал ночных релизов (Nightly Release)
Чтобы использовать последнюю сборку Nuxt и тестировать функции до их выхода, ознакомьтесь с информацией о канале ночных релизов.
latest канала ночных релизов отслеживает ветку Nuxt v4, а это значит, что сейчас в ней особенно вероятны изменения - будьте осторожны! Вы можете подключиться к ночным релизам ветки 3.x с помощью тега "nuxt": "npm:nuxt-nightly@3x".Тестируем Nuxt 4
Nuxt 4 is scheduled for release in Q2 2025. It will include all the features currently available through compatibilityVersion: 4.
До выхода релиза многие критические изменения, которые войдут в Nuxt 4, уже можно протестировать в версиях Nuxt 3.12 и выше.
Внедрение Nuxt 4
Первым шагом необходимо обновить Nuxt до последней версии.
Затем вы можете установить compatibilityVersion для соответствия поведению Nuxt 4:
export default defineNuxtConfig({
future: {
compatibilityVersion: 4,
},
// Чтобы снова включить все поведение Nuxt v3, установите следующие параметры:
// srcDir: '.',
// dir: {
// app: 'app'
// },
// experimental: {
// scanPageMeta: 'after-resolve',
// sharedPrerenderData: false,
// compileTemplate: true,
// resetAsyncDataToUndefined: true,
// templateUtils: true,
// relativeWatchPaths: true,
// normalizeComponentNames: false,
// spaLoadingTemplateLocation: 'within',
// parseErrorData: false,
// pendingWhenIdle: true,
// alwaysRunFetchOnKeyChange: true,
// defaults: {
// useAsyncData: {
// deep: true
// }
// }
// },
// features: {
// inlineStyles: true
// },
// unhead: {
// renderSSRHeadOptions: {
// omitLineBreaks: false
// }
// }
})
Когда вы установите compatibilityVersion в 4, настройки по умолчанию во всей вашей конфигурации Nuxt изменятся, чтобы выбрать поведение Nuxt v4, но вы можете выборочно включить поведение Nuxt v3 при тестировании, следуя закомментированным строкам выше. Пожалуйста, пишите о проблемах, чтобы мы могли решить их в Nuxt или в экосистеме.
Ломающие или значительные изменения, а также шаги по миграции для обеспечения обратной/прямой совместимости, будут отмечены здесь.
compatibilityVersion: 4.Миграция с использованием Codemods
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
yarn dlx codemod@latest nuxt/4/migration-recipe
pnpm dlx codemod@latest nuxt/4/migration-recipe
bun x 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/
shared/
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/, в корне вашего проекта. - Remember to update any third-party configuration files to work with the new directory structure, such as your
tailwindcssoreslintconfiguration (if required -@nuxtjs/tailwindcssshould automatically configuretailwindcsscorrectly).
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'
}
})
Singleton Data Fetching Layer
🚦 Impact Level: Moderate
What Changed
Nuxt's data fetching system (useAsyncData and useFetch) has been significantly reorganized for better performance and consistency:
- Shared refs for the same key: All calls to
useAsyncDataoruseFetchwith the same key now share the samedata,errorandstatusrefs. This means that it is important that all calls with an explicit key must not have conflictingdeep,transform,pick,getCachedDataordefaultoptions. - More control over
getCachedData: ThegetCachedDatafunction is now called every time data is fetched, even if this is caused by a watcher or callingrefreshNuxtData. (Previously, new data was always fetched and this function was not called in these cases.) To allow more control over when to use cached data and when to refetch, the function now receives a context object with the cause of the request. - Reactive key support: You can now use computed refs, plain refs or getter functions as keys, which enables automatic data refetching (and stores data separately).
- Data cleanup: When the last component using data fetched with
useAsyncDatais unmounted, Nuxt will remove that data to avoid ever-growing memory usage.
Reasons for Change
These changes have been made to improve memory usage and increase consistency with loading states across calls of useAsyncData.
Migration Steps
- Check for inconsistent options: Review any components using the same key with different options or fetch functions.
// 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 })
It may be beneficial to extract any calls touseAsyncDatathat share an explicit key (and have custom options) into their own composable:composables/useUserData.tsexport function useUserData(userId: string) { return useAsyncData( `user-${userId}`, () => fetchUser(userId), { deep: true, transform: (user) => ({ ...user, lastAccessed: new Date() }) } ) } - Update
getCachedDataimplementations:useAsyncData('key', fetchFunction, { - getCachedData: (key, nuxtApp) => { - return cachedData[key] - } + getCachedData: (key, nuxtApp, ctx) => { + // ctx.cause - can be 'initial' | 'refresh:hook' | 'refresh:manual' | 'watch' + + // Example: Don't use cache on manual refresh + if (ctx.cause === 'refresh:manual') return undefined + + return cachedData[key] + } })
Alternatively, for now, you can disable this behaviour with:
export default defineNuxtConfig({
experimental: {
granularCachedData: false,
purgeCachedData: false
}
})
Corrected Module Loading Order in Layers
🚦 Impact Level: Minimal
What Changed
The order in which modules are loaded when using Nuxt layers has been corrected. Previously, modules from the project root were loaded before modules from extended layers, which was the reverse of the expected behavior.
Now modules are loaded in the correct order:
- Layer modules first (in extend order - deeper layers first)
- Project modules last (highest priority)
This affects both:
- Modules defined in the
modulesarray innuxt.config.ts - Auto-discovered modules from the
modules/directory
Reasons for Change
This change ensures that:
- Extended layers have lower priority than the consuming project
- Module execution order matches the intuitive layer inheritance pattern
- Module configuration and hooks work as expected in multi-layer setups
Migration Steps
Most projects will not need changes, as this corrects the loading order to match expected behavior.
However, if your project was relying on the previous incorrect order, you may need to:
- Review module dependencies: Check if any modules depend on specific loading order
- Adjust module configuration: If modules were configured to work around the incorrect order
- Test thoroughly: Ensure all functionality works as expected with the corrected order
Example of the new correct order:
// 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']
})
// Loading order (corrected):
// 1. layer-module-1
// 2. layer-module-2
// 3. project-module-1 (can override layer modules)
// 4. project-module-2 (can override layer modules)
If you encounter issues with module order dependencies due to needing to register a hook, consider using the modules:done hook for modules that need to call a hook. This is run after all other modules have been loaded, which means it is safe to use.
👉 See PR #31507 and issue #25719 for more details.
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
}
})
Unhead v2
🚦 Impact Level: Minimal
What Changed
Unhead, used to generate <head> tags, has been updated to version 2. While mostly compatible it includes several breaking changes
for lower-level APIs.
- Removed props:
vmid,hid,children,body. - Promise input no longer supported.
- Tags are now sorted using Capo.js by default.
Migration Steps
The above changes should have minimal impact on your app.
If you have issues you should verify:
- You're not using any of the removed props.
useHead({
meta: [{
name: 'description',
// meta tags don't need a vmid, or a key
- vmid: 'description'
- hid: 'description'
}]
})
- If you're using Template Params or Alias Tag Sorting, you will need to explicitly opt in to these features now.
import { TemplateParamsPlugin, AliasSortingPlugin } from '@unhead/vue/plugins'
export default defineNuxtPlugin({
setup() {
const unhead = injectHead()
unhead.use(TemplateParamsPlugin)
unhead.use(AliasSortingPlugin)
}
})
While not required it's recommended to update any imports from @unhead/vue to #imports or nuxt/app.
-import { useHead } from '@unhead/vue'
+import { useHead } from '#imports'
If you still have issues you may revert to the v1 behavior by enabling the head.legacy config.
export default defineNuxtConfig({
unhead: {
legacy: true,
}
})
New DOM Location for SPA Loading Screen
🚦 Impact Level: Minimal
What Changed
When rendering a client-only page (with ssr: false), we optionally render a loading screen (from app/spa-loading-template.html), within the Nuxt app root:
<div id="__nuxt">
<!-- spa loading template -->
</div>
Now, we default to rendering the template alongside the Nuxt app root:
<div id="__nuxt"></div>
<!-- spa loading template -->
Reasons for Change
This allows the spa loading template to remain in the DOM until the Vue app suspense resolves, preventing a flash of white.
Migration Steps
If you were targeting the spa loading template with CSS or document.queryElement you will need to update your selectors. For this purpose you can use the new app.spaLoaderTag and app.spaLoaderAttrs configuration options.
Alternatively, you can revert to the previous behaviour with:
export default defineNuxtConfig({
experimental: {
spaLoadingTemplateLocation: 'within',
}
})
Parsed error.data
🚦 Impact Level: Minimal
It was possible to throw an error with a data property, but this was not parsed. Now, it is parsed and made available in the error object. Although a fix, this is technically a breaking change if you were relying on the previous behavior and parsing it manually.
Migration Steps
Update your custom error.vue to remove any additional parsing of 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>
Alternatively, you can disable this change:
export default defineNuxtConfig({
experimental: {
parseErrorData: false
},
})
More Granular Inline Styles
🚦 Impact Level: Moderate
Nuxt will now only inline styles for Vue components, not global CSS.
What Changed
Previously, Nuxt would inline all CSS, including global styles, and remove <link> elements to separate CSS files. Now, Nuxt will only do this for Vue components (which previously produced separate chunks of CSS). We think this is a better balance of reducing separate network requests (just as before, there will not be separate requests for individual .css files per-page or per-component on the initial load), as well as allowing caching of a single global CSS file and reducing the document download size of the initial request.
Migration Steps
This feature is fully configurable and you can revert to the previous behavior by setting inlineStyles: true to inline global CSS as well as per-component CSS.
export default defineNuxtConfig({
features: {
inlineStyles: true
}
})
Scan Page Meta After Resolution
🚦 Impact Level: Minimal
What Changed
We now scan page metadata (defined in definePageMeta) after calling the pages:extend hook rather than before.
Reasons for Change
This was to allow scanning metadata for pages that users wanted to add in pages:extend. We still offer an opportunity to change or override page metadata in a new pages:resolved hook.
Migration Steps
If you want to override page metadata, do that in pages:resolved rather than in pages:extend.
export default defineNuxtConfig({
hooks: {
- 'pages:extend'(pages) {
+ 'pages:resolved'(pages) {
const myPage = pages.find(page => page.path === '/')
myPage.meta ||= {}
myPage.meta.layout = 'overridden-layout'
}
}
})
Alternatively, you can revert to the previous behaviour with:
export default defineNuxtConfig({
experimental: {
scanPageMeta: true
}
})
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.
Причины таких изменений
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Если у вас возникнут какие-либо проблемы, вы можете вернуться к прежнему поведению с помощью:
// @errors: 2353
export default defineNuxtConfig({
experimental: {
defaults: {
useAsyncData: {
value: 'null',
errorValue: 'null'
}
}
}
})
Пожалуйста, сообщите о проблеме, если вы делаете это, так как мы не планируем сохранять эту настройку.
Удаление устаревших значений boolean для опции dedupe при вызове refresh в useAsyncData и useFetch
🚦 Уровень воздействия: Минимальный
Что изменилось
Ранее в refresh можно было передавать dedupe: boolean. Это были псевдонимы cancel (true) и defer (false).
// @errors: 2322
const { refresh } = await useAsyncData(async () => ({ message: 'Привет, Nuxt!' }))
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 при итерации по нему. Это должно соблюдаться при сбросе/очистке данных.
Этапы миграции
Если у вас возникнут какие-либо проблемы, вы можете пока что вернуться к прежнему поведению, используя:
// @errors: 2353
export default defineNuxtConfig({
experimental: {
resetAsyncDataToUndefined: true,
}
})
Пожалуйста, сообщите о проблеме, если вы делаете это, так как мы не планируем сохранять эту настройку.
Alignment of pending value in useAsyncData and useFetch
🚦 Impact Level: Medium
The pending object returned from useAsyncData, useFetch, useLazyAsyncData and useLazyFetch is now a computed property that is true only when status is also pending.
What Changed
Now, when immediate: false is passed, pending will be false until the first request is made. This is a change from the previous behavior, where pending was always true until the first request was made.
Reasons for Change
This aligns the meaning of pending with the status property, which is also pending when the request is in progress.
Migration Steps
If you rely on the pending property, ensure that your logic accounts for the new behavior where pending will only be true when the status is also 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>
Alternatively, you can temporarily revert to the previous behavior with:
export default defineNuxtConfig({
experimental: {
pendingWhenIdle: true
}
})
Key Change Behavior in useAsyncData and useFetch
🚦 Impact Level: Medium
What Changed
When using reactive keys in useAsyncData or useFetch, Nuxt automatically refetches data when the key changes. When immediate: false is set, useAsyncData will only fetch data when the key changes if the data has already been fetched once.
Previously, useFetch had slightly different behavior. It would always fetch data when the key changed.
Now, useFetch and useAsyncData behave consistently - by only fetch data when the key changes if the data has already been fetched once.
Reasons for Change
This ensures consistent behavior between useAsyncData and useFetch, and prevents unexpected fetches. If you have set immediate: false, then you must call refresh or execute or data will never be fetched in useFetch or useAsyncData.
Migration Steps
This change should generally improve the expected behavior, but if you were expecting changing the key or options of a non-immediate useFetch, you now will need to trigger it manually the first time.
const id = ref('123')
const { data, execute } = await useFetch('/api/test', {
query: { id },
immediate: false
)
+ watch(id, () => execute(), { once: true })
To opt out of this behavior:
// Or globally in your Nuxt config
export default defineNuxtConfig({
experimental: {
alwaysRunFetchOnKeyChange: 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.
- Use
es-toolkit/compat(a drop-in replacement for lodash template), as a dependency of your project rather than Nuxt:
+ 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-changesDefault TypeScript Configuration Changes
🚦 Impact Level: Minimal
What Changed
compilerOptions.noUncheckedIndexedAccess is now true instead of false.
Reasons for Change
This change is a follow up to a prior 3.12 config update where we improved our defaults, mostly adhering to TotalTypeScript's recommendations.
Migration Steps
There are two approaches:
- Run a typecheck on your app and fix any new errors (recommended).
- Override the new default in your
nuxt.config.ts:export default defineNuxtConfig({ typescript: { tsConfig: { compilerOptions: { noUncheckedIndexedAccess: false } } } })
TypeScript Configuration Splitting
🚦 Impact Level: Minimal
What Changed
Nuxt now generates separate TypeScript configurations for different contexts to provide better type-checking experiences:
- New TypeScript configuration files: Nuxt now generates additional TypeScript configurations:
.nuxt/tsconfig.app.json- For your app code (Vue components, composables, etc.).nuxt/tsconfig.server.json- For your server-side code (Nitro/server directory).nuxt/tsconfig.node.json- For your build-time code (modules,nuxt.config.ts, etc.).nuxt/tsconfig.shared.json- For code shared between app and server contexts (like types and non-environment specific utilities).nuxt/tsconfig.json- Legacy configuration for backward compatibility
- Backward compatibility: Existing projects that extend
.nuxt/tsconfig.jsonwill continue to work as before. - Opt-in project references: New projects or those wanting better type checking can adopt TypeScript's project references feature.
- Context-specific type checking: Each context now has appropriate compiler options and includes/excludes for its specific environment.
- New
typescript.nodeTsConfigoption: You can now customize the TypeScript configuration for Node.js build-time code.
Reasons for Change
This change provides several benefits:
- Better type safety: Each context (app, server, build-time) gets appropriate type checking with context-specific globals and APIs.
- Improved IDE experience: Better IntelliSense and error reporting for different parts of your codebase.
- Cleaner separation: Server code won't incorrectly suggest client-side APIs and vice versa.
- Performance: TypeScript can more efficiently check code with properly scoped configurations.
For example, auto-imports are not available in your nuxt.config.ts (but previously this was not flagged by TypeScript). And while IDEs recognized the separate context hinted by tsconfig.json in your server/ directory, this was not reflected in type-checking (requiring a separate step).
Migration Steps
No migration is required - existing projects will continue to work as before.
However, to take advantage of improved type checking, you can opt in to the new project references approach:
- Update your root
tsconfig.jsonto use project references:{ "files": [], "references": [ { "path": "./.nuxt/tsconfig.app.json" }, { "path": "./.nuxt/tsconfig.server.json" }, { "path": "./.nuxt/tsconfig.shared.json" }, { "path": "./.nuxt/tsconfig.node.json" } ] } - Remove any manual server
tsconfig.jsonfiles (likeserver/tsconfig.json) that extended.nuxt/tsconfig.server.json. - Update your type checking scripts to use the build flag for project references:
- "typecheck": "nuxt prepare && vue-tsc --noEmit" + "typecheck": "nuxt prepare && vue-tsc -b --noEmit" - Configure Node.js TypeScript options if needed:
export default defineNuxtConfig({ typescript: { // Customize app/server TypeScript config tsConfig: { compilerOptions: { strict: true } }, // Customize build-time TypeScript config nodeTsConfig: { compilerOptions: { strict: true } } } }) - Update any CI/build scripts that run TypeScript checking to ensure they use the new project references approach.
The new configuration provides better type safety and IntelliSense for projects that opt in, while maintaining full backward compatibility for existing setups.
Удаление экспериментальных функций
🚦 Уровень воздействия: Минимальный
Что изменилось
Четыре экспериментальные функции больше не настраиваются в 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
Removal of Top-Level generate Configuration
🚦 Impact Level: Minimal
What Changed
The top-level generate configuration option is no longer available in Nuxt 4. This includes all of its properties:
generate.exclude- for excluding routes from prerenderinggenerate.routes- for specifying routes to prerender
Reasons for Change
The top level generate configuration was a holdover from Nuxt 2. We've supported nitro.prerender for a while now, and it is the preferred way to configure prerendering in Nuxt 3+.
Migration Steps
Replace generate configuration with the corresponding nitro.prerender options:
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+
В таблице ниже приведено краткое сравнение между 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 | ❌ Устарел | ✅ nuxt | ✅ nuxt |
| Статические сайты | ✅ | ✅ | ✅ |
С 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.