mirror of
https://github.com/nuxt/nuxt.git
synced 2024-11-22 21:55:11 +00:00
542 lines
20 KiB
Markdown
542 lines
20 KiB
Markdown
---
|
|
title: Upgrade Guide
|
|
description: 'Learn how to upgrade to the latest Nuxt version.'
|
|
navigation.icon: i-ph-arrow-circle-up-duotone
|
|
---
|
|
|
|
|
|
## Upgrading Nuxt
|
|
|
|
### Latest release
|
|
|
|
To upgrade Nuxt to the [latest release](https://github.com/nuxt/nuxt/releases), use the `nuxi upgrade` command.
|
|
|
|
::code-group
|
|
|
|
```bash [npm]
|
|
npx nuxi upgrade
|
|
```
|
|
|
|
```bash [yarn]
|
|
yarn dlx nuxi upgrade
|
|
```
|
|
|
|
```bash [pnpm]
|
|
pnpm dlx nuxi upgrade
|
|
```
|
|
|
|
```bash [bun]
|
|
bunx nuxi upgrade
|
|
```
|
|
|
|
::
|
|
|
|
### Nightly Release Channel
|
|
|
|
To use the latest Nuxt build and test features before their release, read about the [nightly release channel](/docs/guide/going-further/nightly-release-channel) guide.
|
|
|
|
## Testing Nuxt 4
|
|
|
|
Nuxt 4 is planned to be released **on or before June 14** (though obviously this is dependent on having enough time after Nitro's major release to be properly tested in the community, so be aware that this is not an exact date).
|
|
|
|
Until then, it is possible to test many of Nuxt 4's breaking changes from Nuxt version 3.12 or via the nightly release channel.
|
|
|
|
::tip{icon="i-ph-video-duotone" to="https://www.youtube.com/watch?v=r4wFKlcJK6c" target="_blank"}
|
|
Watch a video from Alexander Lichter showing how to opt in to Nuxt 4's breaking changes already.
|
|
::
|
|
|
|
### Opting in to Nuxt 4
|
|
|
|
First, opt in to the nightly release channel [following these steps](/docs/guide/going-further/nightly-release-channel#opting-in).
|
|
|
|
Then you can set your `compatibilityVersion` to match Nuxt 4 behavior:
|
|
|
|
```ts twoslash [nuxt.config.ts]
|
|
export default defineNuxtConfig({
|
|
future: {
|
|
compatibilityVersion: 4,
|
|
},
|
|
// To re-enable _all_ Nuxt v3 behavior, set the following options:
|
|
// srcDir: '.',
|
|
// dir: {
|
|
// app: 'app'
|
|
// },
|
|
// experimental: {
|
|
// sharedPrerenderData: false,
|
|
// compileTemplate: true,
|
|
// resetAsyncDataToUndefined: true,
|
|
// templateUtils: true,
|
|
// relativeWatchPaths: true,
|
|
// defaults: {
|
|
// useAsyncData: {
|
|
// deep: true
|
|
// }
|
|
// }
|
|
// },
|
|
// unhead: {
|
|
// renderSSRHeadOptions: {
|
|
// omitLineBreaks: false
|
|
// }
|
|
// }
|
|
})
|
|
```
|
|
|
|
When you set your `compatibilityVersion` to `4`, defaults throughout your Nuxt configuration will change to opt in to Nuxt v4 behavior, but you can granularly re-enable Nuxt v3 behavior when testing, following the commented out lines above. Please file issues if so, so that we can address them in Nuxt or in the ecosystem.
|
|
|
|
### Migrating to Nuxt 4
|
|
|
|
Breaking or significant changes will be noted here along with migration steps for backward/forward compatibility.
|
|
|
|
::alert
|
|
This section is subject to change until the final release, so please check back here regularly if you are testing Nuxt 4 using `compatibilityVersion: 4`.
|
|
::
|
|
|
|
#### New Directory Structure
|
|
|
|
🚦 **Impact Level**: Significant
|
|
|
|
Nuxt now defaults to a new directory structure, with backwards compatibility (so if Nuxt detects you are using the old structure, such as with a top-level `pages/` directory, this new structure will not apply).
|
|
|
|
👉 [See full RFC](https://github.com/nuxt/nuxt/issues/26444)
|
|
|
|
##### What Changed
|
|
|
|
* the new Nuxt default `srcDir` is `app/` by default, and most things are resolved from there.
|
|
* `serverDir` now defaults to `<rootDir>/server` rather than `<srcDir>/server`
|
|
* `modules` and `public` are resolved relative to `<rootDir>` by default
|
|
* a new `dir.app` is added, which is the directory we look for `router.options.ts` and `spa-loading-template.html` - this defaults to `<srcDir>/`
|
|
|
|
<details>
|
|
|
|
<summary>An example v4 folder structure.</summary>
|
|
|
|
```sh
|
|
.output/
|
|
.nuxt/
|
|
app/
|
|
assets/
|
|
components/
|
|
composables/
|
|
layouts/
|
|
middleware/
|
|
pages/
|
|
plugins/
|
|
utils/
|
|
app.config.ts
|
|
app.vue
|
|
router.options.ts
|
|
modules/
|
|
node_modules/
|
|
public/
|
|
server/
|
|
api/
|
|
middleware/
|
|
plugins/
|
|
routes/
|
|
utils/
|
|
nuxt.config.ts
|
|
```
|
|
|
|
</details>
|
|
|
|
👉 For more details, see the [PR implementing this change](https://github.com/nuxt/nuxt/pull/27029).
|
|
|
|
##### Reasons for Change
|
|
|
|
1. **Performance** - placing all your code in the root of your repo causes issues with `.git/` and `node_modules/` folders being scanned/included by FS watchers which can significantly delay startup on non-Mac OSes.
|
|
1. **IDE type-safety** - `server/` and the rest of your app are running in two entirely different contexts with different global imports available, and making sure `server/` isn't _inside_ the same folder as the rest of your app is a big first step to ensuring you get good auto-completes in your IDE.
|
|
|
|
##### Migration Steps
|
|
|
|
1. Create a new directory called `app/`.
|
|
1. Move your `assets/`, `components/`, `composables/`, `layouts/`, `middleware/`, `pages/`, `plugins/` and `utils/` folders under it, as well as `app.vue`, `error.vue`, `app.config.ts`. If you have an `app/router-options.ts` or `app/spa-loading-template.html`, these paths remain the same.
|
|
1. Make sure your `nuxt.config.ts`, `modules/`, `public/` and `server/` folders remain outside the `app/` folder, in the root of your project.
|
|
|
|
However, migration is _not required_. If you wish to keep your current folder structure, Nuxt should auto-detect it. (If it does not, please raise an issue.) The one exception is that if you _already_ have a custom `srcDir`. In this case, you should be aware that your `modules/`, `public/` and `server/` folders will be resolved from your `rootDir` rather than from your custom `srcDir`. You can override this by configuring `dir.modules`, `dir.public` and `serverDir` if you need to.
|
|
|
|
You can also force a v3 folder structure with the following configuration:
|
|
|
|
```ts [nuxt.config.ts]
|
|
export default defineNuxtConfig({
|
|
// This reverts the new srcDir default from `app` back to your root directory
|
|
srcDir: '.',
|
|
// This specifies the directory prefix for `app/router.options.ts` and `app/spa-loading-template.html`
|
|
dir: {
|
|
app: 'app'
|
|
}
|
|
})
|
|
```
|
|
|
|
#### Shared Prerender Data
|
|
|
|
🚦 **Impact Level**: Medium
|
|
|
|
##### What Changed
|
|
|
|
We enabled a previously experimental feature to share data from `useAsyncData` and `useFetch` calls, across different pages. See [original PR](https://github.com/nuxt/nuxt/pull/24894).
|
|
|
|
##### Reasons for Change
|
|
|
|
This feature automatically shares payload _data_ between pages that are prerendered. This can result in a significant performance improvement when prerendering sites that use `useAsyncData` or `useFetch` and fetch the same data in different pages.
|
|
|
|
For example, if your site requires a `useFetch` call for every page (for example, to get navigation data for a menu, or site settings from a CMS), this data would only be fetched once when prerendering the first page that uses it, and then cached for use when prerendering other pages.
|
|
|
|
##### Migration Steps
|
|
|
|
Make sure that any unique key of your data is always resolvable to the same data. For example, if you are using `useAsyncData` to fetch data related to a particular page, you should provide a key that uniquely matches that data. (`useFetch` should do this automatically for you.)
|
|
|
|
```ts [app/pages/test/[slug\\].vue]
|
|
// This would be unsafe in a dynamic page (e.g. `[slug].vue`) because the route slug makes a difference
|
|
// to the data fetched, but Nuxt can't know that because it's not reflected in the key.
|
|
const route = useRoute()
|
|
const { data } = await useAsyncData(async () => {
|
|
return await $fetch(`/api/my-page/${route.params.slug}`)
|
|
})
|
|
// Instead, you should use a key that uniquely identifies the data fetched.
|
|
const { data } = await useAsyncData(route.params.slug, async () => {
|
|
return await $fetch(`/api/my-page/${route.params.slug}`)
|
|
})
|
|
```
|
|
|
|
Alternatively, you can disable this feature with:
|
|
|
|
```ts twoslash [nuxt.config.ts]
|
|
export default defineNuxtConfig({
|
|
experimental: {
|
|
sharedPrerenderData: false
|
|
}
|
|
})
|
|
```
|
|
|
|
#### Default `data` and `error` values in `useAsyncData` and `useFetch`
|
|
|
|
🚦 **Impact Level**: Minimal
|
|
|
|
##### What Changed
|
|
|
|
`data` and `error` objects returned from `useAsyncData` will now default to `undefined`.
|
|
|
|
##### Reasons for Change
|
|
|
|
Previously `data` was initialized to `null` but reset in `clearNuxtData` to `undefined`. `error` was initialized to `null`. This change is to bring greater consistency.
|
|
|
|
##### Migration Steps
|
|
|
|
If you encounter any issues you can revert back to the previous behavior with:
|
|
|
|
```ts twoslash [nuxt.config.ts]
|
|
// @errors: 2353
|
|
export default defineNuxtConfig({
|
|
experimental: {
|
|
defaults: {
|
|
useAsyncData: {
|
|
value: 'null',
|
|
errorValue: 'null'
|
|
}
|
|
}
|
|
}
|
|
})
|
|
```
|
|
|
|
Please report an issue if you are doing this, as we do not plan to keep this as configurable.
|
|
|
|
#### Removal of deprecated `boolean` values for `dedupe` option when calling `refresh` in `useAsyncData` and `useFetch`
|
|
|
|
🚦 **Impact Level**: Minimal
|
|
|
|
##### What Changed
|
|
|
|
Previously it was possible to pass `dedupe: boolean` to `refresh`. These were aliases of `cancel` (`true`) and `defer` (`false`).
|
|
|
|
```ts twoslash [app.vue]
|
|
// @errors: 2322
|
|
const { refresh } = await useAsyncData(async () => ({ message: 'Hello, Nuxt 3!' }))
|
|
|
|
async function refreshData () {
|
|
await refresh({ dedupe: true })
|
|
}
|
|
```
|
|
|
|
##### Reasons for Change
|
|
|
|
These aliases were removed, for greater clarity.
|
|
|
|
The issue came up when adding `dedupe` as an option to `useAsyncData`, and we removed the boolean values as they ended up being _opposites_.
|
|
|
|
`refresh({ dedupe: false })` meant 'do not _cancel_ existing requests in favour of this new one'. But passing `dedupe: true` within the options of `useAsyncData` means 'do not make any new requests if there is an existing pending request.' (See [PR](https://github.com/nuxt/nuxt/pull/24564#pullrequestreview-1764584361).)
|
|
|
|
##### Migration Steps
|
|
|
|
The migration should be straightforward:
|
|
|
|
```diff
|
|
const { refresh } = await useAsyncData(async () => ({ message: 'Hello, Nuxt 3!' }))
|
|
|
|
async function refreshData () {
|
|
- await refresh({ dedupe: true })
|
|
+ await refresh({ dedupe: 'cancel' })
|
|
|
|
- await refresh({ dedupe: false })
|
|
+ await refresh({ dedupe: 'defer' })
|
|
}
|
|
```
|
|
|
|
#### Respect defaults when clearing `data` in `useAsyncData` and `useFetch`
|
|
|
|
🚦 **Impact Level**: Minimal
|
|
|
|
##### What Changed
|
|
|
|
If you provide a custom `default` value for `useAsyncData`, this will now be used when calling `clear` or `clearNuxtData` and it will be reset to its default value rather than simply unset.
|
|
|
|
##### Reasons for Change
|
|
|
|
Often users set an appropriately empty value, such as an empty array, to avoid the need to check for `null`/`undefined` when iterating over it. This should be respected when resetting/clearing the data.
|
|
|
|
##### Migration Steps
|
|
|
|
If you encounter any issues you can revert back to the previous behavior, for now, with:
|
|
|
|
```ts twoslash [nuxt.config.ts]
|
|
// @errors: 2353
|
|
export default defineNuxtConfig({
|
|
experimental: {
|
|
resetAsyncDataToUndefined: true,
|
|
}
|
|
})
|
|
```
|
|
|
|
Please report an issue if you are doing so, as we do not plan to keep this as configurable.
|
|
|
|
#### Shallow Data Reactivity in `useAsyncData` and `useFetch`
|
|
|
|
🚦 **Impact Level**: Minimal
|
|
|
|
The `data` object returned from `useAsyncData`, `useFetch`, `useLazyAsyncData` and `useLazyFetch` is now a `shallowRef` rather than a `ref`.
|
|
|
|
##### What Changed
|
|
|
|
When new data is fetched, anything depending on `data` will still be reactive because the entire object is replaced. But if your code changes a property _within_ that data structure, this will not trigger any reactivity in your app.
|
|
|
|
##### Reasons for Change
|
|
|
|
This brings a **significant** performance improvement for deeply nested objects and arrays because Vue does not need to watch every single property/array for modification. In most cases, `data` should also be immutable.
|
|
|
|
##### Migration Steps
|
|
|
|
In most cases, no migration steps are required, but if you rely on the reactivity of the data object then you have two options:
|
|
|
|
1. You can granularly opt in to deep reactivity on a per-composable basis:
|
|
```diff
|
|
- const { data } = useFetch('/api/test')
|
|
+ const { data } = useFetch('/api/test', { deep: true })
|
|
```
|
|
1. You can change the default behavior on a project-wide basis (not recommended):
|
|
```ts twoslash [nuxt.config.ts]
|
|
export default defineNuxtConfig({
|
|
experimental: {
|
|
defaults: {
|
|
useAsyncData: {
|
|
deep: true
|
|
}
|
|
}
|
|
}
|
|
})
|
|
```
|
|
|
|
#### Absolute Watch Paths in `builder:watch`
|
|
|
|
🚦 **Impact Level**: Minimal
|
|
|
|
##### What Changed
|
|
|
|
The Nuxt `builder:watch` hook now emits a path which is absolute rather than relative to your project `srcDir`.
|
|
|
|
##### Reasons for Change
|
|
|
|
This allows us to support watching paths which are outside your `srcDir`, and offers better support for layers and other more complex patterns.
|
|
|
|
##### Migration Steps
|
|
|
|
We have already proactively migrated the public Nuxt modules which we are aware use this hook. See [issue #25339](https://github.com/nuxt/nuxt/issues/25339).
|
|
|
|
However, if you are a module author using the `builder:watch` hook and wishing to remain backwards/forwards compatible, you can use the following code to ensure that your code works the same in both Nuxt v3 and Nuxt v4:
|
|
|
|
```diff
|
|
+ import { relative, resolve } from 'node:fs'
|
|
// ...
|
|
nuxt.hook('builder:watch', async (event, path) => {
|
|
+ path = relative(nuxt.options.srcDir, resolve(nuxt.options.srcDir, path))
|
|
// ...
|
|
})
|
|
```
|
|
|
|
#### Removal of `window.__NUXT__` object
|
|
|
|
##### What Changed
|
|
|
|
We are removing the global `window.__NUXT__` object after the app finishes hydration.
|
|
|
|
##### Reasons for Change
|
|
|
|
This opens the way to multi-app patterns ([#21635](https://github.com/nuxt/nuxt/issues/21635)) and enables us to focus on a single way to access Nuxt app data - `useNuxtApp()`.
|
|
|
|
##### Migration Steps
|
|
|
|
The data is still available, but can be accessed with `useNuxtApp().payload`:
|
|
|
|
```diff
|
|
- console.log(window.__NUXT__)
|
|
+ console.log(useNuxtApp().payload)
|
|
```
|
|
|
|
#### Directory index scanning
|
|
|
|
🚦 **Impact Level**: Medium
|
|
|
|
##### What Changed
|
|
|
|
Child folders in your `middleware/` folder are also scanned for `index` files and these are now also registered as middleware in your project.
|
|
|
|
##### Reasons for Change
|
|
|
|
Nuxt scans a number of folders automatically, including `middleware/` and `plugins/`.
|
|
|
|
Child folders in your `plugins/` folder are scanned for `index` files and we wanted to make this behavior consistent between scanned directories.
|
|
|
|
##### Migration Steps
|
|
|
|
Probably no migration is necessary but if you wish to revert to previous behavior you can add a hook to filter out these middleware:
|
|
|
|
```ts
|
|
export default defineNuxtConfig({
|
|
hooks: {
|
|
'app:resolve'(app) {
|
|
app.middleware = app.middleware.filter(mw => !/\/index\.[^/]+$/.test(mw.path))
|
|
}
|
|
}
|
|
})
|
|
```
|
|
|
|
#### Template Compilation Changes
|
|
|
|
🚦 **Impact Level**: Minimal
|
|
|
|
##### What Changed
|
|
|
|
Previously, Nuxt used `lodash/template` to compile templates located on the file system using the `.ejs` file format/syntax.
|
|
|
|
In addition, we provided some template utilities (`serialize`, `importName`, `importSources`) which could be used for code-generation within these templates, which are now being removed.
|
|
|
|
##### Reasons for Change
|
|
|
|
In Nuxt v3 we moved to a 'virtual' syntax with a `getContents()` function which is much more flexible and performant.
|
|
|
|
In addition, `lodash/template` has had a succession of security issues. These do not really apply to Nuxt projects because it is being used at build-time, not runtime, and by trusted code. However, they still appear in security audits. Moreover, `lodash` is a hefty dependency and is unused by most projects.
|
|
|
|
Finally, providing code serialization functions directly within Nuxt is not ideal. Instead, we maintain projects like [unjs/knitwork](http://github.com/unjs/knitwork) which can be dependencies of your project, and where security issues can be reported/resolved directly without requiring an upgrade of Nuxt itself.
|
|
|
|
##### Migration Steps
|
|
|
|
We have raised PRs to update modules using EJS syntax, but if you need to do this yourself, you have three backwards/forwards-compatible alternatives:
|
|
|
|
* Moving your string interpolation logic directly into `getContents()`.
|
|
* Using a custom function to handle the replacement, such as in https://github.com/nuxt-modules/color-mode/pull/240.
|
|
* Continuing to use `lodash`, as a dependency of _your_ project rather than Nuxt:
|
|
|
|
```diff
|
|
+ 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 })
|
|
+ },
|
|
})
|
|
```
|
|
|
|
Finally, if you are using the template utilities (`serialize`, `importName`, `importSources`), you can replace them as follows with utilities from `knitwork`:
|
|
|
|
```ts
|
|
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
|
|
```
|
|
|
|
#### Removal of Experimental Features
|
|
|
|
🚦 **Impact Level**: Minimal
|
|
|
|
##### What Changed
|
|
|
|
Four experimental features are no longer configurable in Nuxt 4:
|
|
|
|
* `experimental.treeshakeClientOnly` will be `true` (default since v3.0)
|
|
* `experimental.configSchema` will be `true` (default since v3.3)
|
|
* `experimental.polyfillVueUseHead` will be `false` (default since v3.4)
|
|
* `experimental.respectNoSSRHeader` will be `false` (default since v3.4)
|
|
* `vite.devBundler` is no longer configurable - it will use `vite-node` by default
|
|
|
|
##### Reasons for Change
|
|
|
|
These options have been set to their current values for some time and we do not have a reason to believe that they need to remain configurable.
|
|
|
|
##### Migration Steps
|
|
|
|
* `polyfillVueUseHead` is implementable in user-land with [this plugin](https://github.com/nuxt/nuxt/blob/f209158352b09d1986aa320e29ff36353b91c358/packages/nuxt/src/head/runtime/plugins/vueuse-head-polyfill.ts#L10-L11)
|
|
|
|
* `respectNoSSRHeader`is implementable in user-land with [server middleware](https://github.com/nuxt/nuxt/blob/c660b39447f0d5b8790c0826092638d321cd6821/packages/nuxt/src/core/runtime/nitro/no-ssr.ts#L8-L9)
|
|
|
|
## Nuxt 2 vs Nuxt 3
|
|
|
|
In the table below, there is a quick comparison between 3 versions of Nuxt:
|
|
|
|
Feature / Version | Nuxt 2 | Nuxt Bridge | Nuxt 3
|
|
-------------------------|-----------------|------------------|---------
|
|
Vue | 2 | 2 | 3
|
|
Stability | 😊 Stable | 😊 Stable | 😊 Stable
|
|
Performance | 🏎 Fast | ✈️ Faster | 🚀 Fastest
|
|
Nitro Engine | ❌ | ✅ | ✅
|
|
ESM support | 🌙 Partial | 👍 Better | ✅
|
|
TypeScript | ☑️ Opt-in | 🚧 Partial | ✅
|
|
Composition API | ❌ | 🚧 Partial | ✅
|
|
Options API | ✅ | ✅ | ✅
|
|
Components Auto Import | ✅ | ✅ | ✅
|
|
`<script setup>` syntax | ❌ | 🚧 Partial | ✅
|
|
Auto Imports | ❌ | ✅ | ✅
|
|
webpack | 4 | 4 | 5
|
|
Vite | ⚠️ Partial | 🚧 Partial | ✅
|
|
Nuxi CLI | ❌ Old | ✅ nuxi | ✅ nuxi
|
|
Static sites | ✅ | ✅ | ✅
|
|
|
|
## Nuxt 2 to Nuxt 3
|
|
|
|
The migration guide provides a step-by-step comparison of Nuxt 2 features to Nuxt 3 features and guidance to adapt your current application.
|
|
|
|
::read-more{to="/docs/migration/overview"}
|
|
Check out the **guide to migrating from Nuxt 2 to Nuxt 3**.
|
|
::
|
|
|
|
## Nuxt 2 to Nuxt Bridge
|
|
|
|
If you prefer to progressively migrate your Nuxt 2 application to Nuxt 3, you can use Nuxt Bridge. Nuxt Bridge is a compatibility layer that allows you to use Nuxt 3 features in Nuxt 2 with an opt-in mechanism.
|
|
|
|
::read-more{to="/docs/bridge/overview"}
|
|
**Migrate from Nuxt 2 to Nuxt Bridge**
|
|
::
|