diff --git a/docs/1.getting-started/12.upgrade.md b/docs/1.getting-started/12.upgrade.md index 8d6a28477c..626d63ab42 100644 --- a/docs/1.getting-started/12.upgrade.md +++ b/docs/1.getting-started/12.upgrade.md @@ -5,11 +5,11 @@ navigation.icon: i-ph-arrow-circle-up-duotone --- -## Upgrading Nuxt 3 +## Upgrading Nuxt ### Latest release -To upgrade Nuxt 3 to the [latest release](https://github.com/nuxt/nuxt/releases), use the `nuxi upgrade` command. +To upgrade Nuxt to the [latest release](https://github.com/nuxt/nuxt/releases), use the `nuxi upgrade` command. ```bash [Terminal] npx nuxi upgrade @@ -17,7 +17,253 @@ npx nuxi upgrade ### Nightly Release Channel -To use the latest Nuxt 3 build and test features before their release, read about the [nightly release channel](/docs/guide/going-further/nightly-release-channel) guide. +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 on the nightly release channel. + +### 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 +export default defineNuxtConfig({ + future: { + compatibilityVersion: 4, + }, + // To re-enable _all_ Nuxt v3 behavior, set the following options: + // srcDir: '.', + // dir: { + // app: 'app' + // }, + // experimental: { + // compileTemplate: 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 `/server` rather than `/server` +* `modules` and `public` are resolved relative to `` 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 `/` + +
+ +An example v4 folder structure. + +```sh +.output/ +.nuxt/ +app/ + assets/ + components/ + composables/ + layouts/ + middleware/ + pages/ + plugins/ + utils/ + app.vue + router.options.ts +modules/ +node_modules/ +public/ +server/ + api/ + middleware/ + plugins/ + routes/ + utils/ +nuxt.config.ts +``` + +
+ +👉 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`. 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.) You can also force a v3 folder structure with the following configuration: + +```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' + } +}) +``` + +#### 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 + 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)) + // ... + }) +``` + +#### 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 +``` ## Nuxt 2 vs Nuxt 3