diff --git a/docs/1.getting-started/11.testing.md b/docs/1.getting-started/11.testing.md
index f9eea6d0cd..9ae5b6264c 100644
--- a/docs/1.getting-started/11.testing.md
+++ b/docs/1.getting-started/11.testing.md
@@ -10,6 +10,10 @@ If you are a module author, you can find more specific information in the [Modul
Nuxt offers first-class support for end-to-end and unit testing of your Nuxt application via `@nuxt/test-utils`, a library of test utilities and configuration that currently powers the [tests we use on Nuxt itself](https://github.com/nuxt/nuxt/tree/main/test) and tests throughout the module ecosystem.
+::tip{icon="i-ph-video-duotone" to="https://www.youtube.com/watch?v=yGzwk9xi9gU" target="_blank"}
+Watch a video from Alexander Lichter about getting started with the `@nuxt/test-utils`.
+::
+
## Installation
In order to allow you to manage your other testing dependencies, `@nuxt/test-utils` ships with various optional peer dependencies. For example:
@@ -160,21 +164,32 @@ export default defineVitestConfig({
`mountSuspended` allows you to mount any Vue component within the Nuxt environment, allowing async setup and access to injections from your Nuxt plugins. For example:
```ts twoslash
-import type { Component } from 'vue'
import { it, expect } from 'vitest'
-declare const SomeComponent: Component
-declare const App: Component
+import type { Component } from 'vue'
+declare module '#components' {
+ export const SomeComponent: Component
+}
// ---cut---
// tests/components/SomeComponents.nuxt.spec.ts
import { mountSuspended } from '@nuxt/test-utils/runtime'
+import { SomeComponent } from '#components'
it('can mount some component', async () => {
const component = await mountSuspended(SomeComponent)
expect(component.text()).toMatchInlineSnapshot(
- 'This is an auto-imported component'
+ '"This is an auto-imported component"'
)
})
+```
+
+```ts twoslash
+import { it, expect } from 'vitest'
+// ---cut---
+// tests/components/SomeComponents.nuxt.spec.ts
+import { mountSuspended } from '@nuxt/test-utils/runtime'
+import App from '~/app.vue'
+
// tests/App.nuxt.spec.ts
it('can also mount an app', async () => {
const component = await mountSuspended(App, { route: '/test' })
@@ -199,13 +214,15 @@ The passed in component will be rendered inside a `
Examples:
```ts twoslash
-import type { Component } from 'vue'
import { it, expect } from 'vitest'
-declare const SomeComponent: Component
-declare const App: Component
+import type { Component } from 'vue'
+declare module '#components' {
+ export const SomeComponent: Component
+}
// ---cut---
// tests/components/SomeComponents.nuxt.spec.ts
import { renderSuspended } from '@nuxt/test-utils/runtime'
+import { SomeComponent } from '#components'
import { screen } from '@testing-library/vue'
it('can render some component', async () => {
@@ -215,13 +232,11 @@ it('can render some component', async () => {
```
```ts twoslash
-import type { Component } from 'vue'
import { it, expect } from 'vitest'
-declare const SomeComponent: Component
-declare const App: Component
// ---cut---
// tests/App.nuxt.spec.ts
import { renderSuspended } from '@nuxt/test-utils/runtime'
+import App from '~/app.vue'
it('can also render an app', async () => {
const html = await renderSuspended(App, { route: '/test' })
@@ -358,7 +373,7 @@ registerEndpoint('/test/', {
})
```
-> **Note**: If your requests in a component go to external API, you can use `baseURL` and then make it empty using Nuxt Environment Config (`$test`) so all your requests will go to Nitro server.
+> **Note**: If your requests in a component go to an external API, you can use `baseURL` and then make it empty using [Nuxt Environment Override Config](/docs/getting-started/configuration#environment-overrides) (`$test`) so all your requests will go to Nitro server.
#### Conflict with End-To-End Testing
diff --git a/docs/1.getting-started/12.upgrade.md b/docs/1.getting-started/12.upgrade.md
index 626d63ab42..c8e6c66ef0 100644
--- a/docs/1.getting-started/12.upgrade.md
+++ b/docs/1.getting-started/12.upgrade.md
@@ -25,13 +25,17 @@ Nuxt 4 is planned to be released **on or before June 14** (though obviously this
Until then, it is possible to test many of Nuxt 4's breaking changes on 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
+```ts twoslash [nuxt.config.ts]
export default defineNuxtConfig({
future: {
compatibilityVersion: 4,
@@ -42,7 +46,9 @@ export default defineNuxtConfig({
// app: 'app'
// },
// experimental: {
+ // sharedPrerenderData: false,
// compileTemplate: true,
+ // resetAsyncDataToUndefined: true,
// templateUtils: true,
// relativeWatchPaths: true,
// defaults: {
@@ -100,6 +106,7 @@ app/
pages/
plugins/
utils/
+ app.config.ts
app.vue
router.options.ts
modules/
@@ -126,12 +133,12 @@ nuxt.config.ts
##### 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. 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.) You can also force a v3 folder structure with the following configuration:
-```ts
+```ts [nuxt.config.ts]
export default defineNuxtConfig({
// This reverts the new srcDir default from `app` back to your root directory
srcDir: '.',
@@ -142,6 +149,104 @@ export default defineNuxtConfig({
})
```
+#### 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]
+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.
+
+#### 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]
+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
@@ -166,7 +271,7 @@ In most cases, no migration steps are required, but if you rely on the reactivit
+ const { data } = useFetch('/api/test', { deep: true })
```
1. You can change the default behavior on a project-wide basis (not recommended):
- ```ts
+ ```ts twoslash [nuxt.config.ts]
export default defineNuxtConfig({
experimental: {
defaults: {
@@ -205,6 +310,34 @@ However, if you are a module author using the `builder:watch` hook and wishing t
})
```
+#### 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
@@ -265,6 +398,29 @@ const importSources = (sources: string | string[], { lazy = false } = {}) => {
const importName = genSafeVariableName
```
+#### Removal of Experimental Features
+
+π¦ **Impact Level**: Minimal
+
+##### What Changed
+
+Four experimental features are no longer configurable in Nuxt 4:
+
+* `treeshakeClientOnly` will be `true` (default since v3.0)
+* `configSchema` will be `true` (default since v3.3)
+* `polyfillVueUseHead` will be `false` (default since v3.4)
+* `respectNoSSRHeader` will be `false` (default since v3.4)
+
+##### 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:
@@ -272,7 +428,7 @@ 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 | π Semi-stable | π Stable
+Stability | π Stable | π Stable | π Stable
Performance | π Fast | βοΈ Faster | π Fastest
Nitro Engine | β | β | β
ESM support | π Partial | π Better | β
diff --git a/docs/1.getting-started/3.configuration.md b/docs/1.getting-started/3.configuration.md
index c14e01f2f4..49a0822ecb 100644
--- a/docs/1.getting-started/3.configuration.md
+++ b/docs/1.getting-started/3.configuration.md
@@ -46,6 +46,10 @@ export default defineNuxtConfig({
})
```
+::tip{icon="i-ph-video-duotone" to="https://www.youtube.com/watch?v=DFZI2iVCrNc" target="_blank"}
+Watch a video from Alexander Lichter about the env-aware `nuxt.config.ts`.
+::
+
::note
If you're authoring layers, you can also use the `$meta` key to provide metadata that you or the consumers of your layer might use.
::
diff --git a/docs/1.getting-started/6.data-fetching.md b/docs/1.getting-started/6.data-fetching.md
index 3a26848aef..32976b1f29 100644
--- a/docs/1.getting-started/6.data-fetching.md
+++ b/docs/1.getting-started/6.data-fetching.md
@@ -54,6 +54,10 @@ const { data: count } = await useFetch('/api/count')
This composable is a wrapper around the [`useAsyncData`](/docs/api/composables/use-async-data) composable and `$fetch` utility.
+::tip{icon="i-ph-video-duotone" to="https://www.youtube.com/watch?v=njsGVmcWviY" target="_blank"}
+Watch the video from Alexander Lichter to avoid using `useFetch` the wrong way!
+::
+
:read-more{to="/docs/api/composables/use-fetch"}
:link-example{to="/docs/examples/features/data-fetching"}
@@ -93,6 +97,10 @@ The `useAsyncData` composable is responsible for wrapping async logic and return
It's developer experience sugar for the most common use case.
::
+::tip{icon="i-ph-video-duotone" to="https://www.youtube.com/watch?v=0X-aOpSGabA" target="_blank"}
+Watch a video from Alexander Lichter to dig deeper into the difference between `useFetch` and `useAsyncData`.
+::
+
There are some cases when using the [`useFetch`](/docs/api/composables/use-fetch) composable is not appropriate, for example when a CMS or a third-party provide their own query layer. In this case, you can use [`useAsyncData`](/docs/api/composables/use-async-data) to wrap your calls and still keep the benefits provided by the composable.
```vue [pages/users.vue]
diff --git a/docs/1.getting-started/7.state-management.md b/docs/1.getting-started/7.state-management.md
index d9edf3f161..4957fb4a14 100644
--- a/docs/1.getting-started/7.state-management.md
+++ b/docs/1.getting-started/7.state-management.md
@@ -8,6 +8,10 @@ Nuxt provides the [`useState`](/docs/api/composables/use-state) composable to cr
[`useState`](/docs/api/composables/use-state) is an SSR-friendly [`ref`](https://vuejs.org/api/reactivity-core.html#ref) replacement. Its value will be preserved after server-side rendering (during client-side hydration) and shared across all components using a unique key.
+::tip{icon="i-ph-video-duotone" to="https://www.youtube.com/watch?v=mv0WcBABcIk" target="_blank"}
+Watch a video from Alexander Lichter about why and when to use `useState()`.
+::
+
::important
Because the data inside [`useState`](/docs/api/composables/use-state) will be serialized to JSON, it is important that it does not contain anything that cannot be serialized, such as classes, functions or symbols.
::
@@ -208,6 +212,10 @@ const color = useColor() // Same as useState('color')
```
+::tip{icon="i-ph-video-duotone" to="https://www.youtube.com/watch?v=dZSNW07sO-A" target="_blank"}
+Watch a video from Daniel Roe on how to deal with global state and SSR in Nuxt.
+::
+
## Using third-party libraries
Nuxt **used to rely** on the Vuex library to provide global state management. If you are migrating from Nuxt 2, please head to [the migration guide](/docs/migration/configuration#vuex).
diff --git a/docs/1.getting-started/8.server.md b/docs/1.getting-started/8.server.md
index 49e0f21464..2a67cc3945 100644
--- a/docs/1.getting-started/8.server.md
+++ b/docs/1.getting-started/8.server.md
@@ -20,6 +20,10 @@ Using Nitro gives Nuxt superpowers:
Nitro is internally using [h3](https://github.com/unjs/h3), a minimal H(TTP) framework built for high performance and portability.
+::tip{icon="i-ph-video-duotone" to="https://www.youtube.com/watch?v=DkvgJa-X31k" target="_blank"}
+Watch a video from Alexander Lichter to understand the responsibilities of Nuxt and Nitro in your application.
+::
+
## Server Endpoints & Middleware
You can easily manage the server-only part of your Nuxt app, from API endpoints to middleware.
diff --git a/docs/1.getting-started/9.layers.md b/docs/1.getting-started/9.layers.md
index 0c25507946..7e05dadaf6 100644
--- a/docs/1.getting-started/9.layers.md
+++ b/docs/1.getting-started/9.layers.md
@@ -51,7 +51,7 @@ Read more about layers in the **Layer Author Guide**.
Watch a video from Learn Vue about Nuxt Layers.
::
-::tip{icon="i-ph-video-duotone" to="https://www.youtube.com/watch?v=fr5yo3aVkfA&t=271s" target="_blank"}
+::tip{icon="i-ph-video-duotone" to="https://www.youtube.com/watch?v=fr5yo3aVkfA" target="_blank"}
Watch a video from Alexander Lichter about Nuxt Layers.
::
diff --git a/docs/2.guide/0.index.md b/docs/2.guide/0.index.md
index b45915e129..a93d01cdb6 100644
--- a/docs/2.guide/0.index.md
+++ b/docs/2.guide/0.index.md
@@ -16,4 +16,7 @@ surround: false
::card{icon="i-ph-star-duotone" title="Going Further" to="/docs/guide/going-further"}
Master Nuxt with advanced concepts like experimental features, hooks, modules, and more.
::
+ ::card{icon="i-ph-book-open-duotone" title="Recipes" to="/docs/guide/recipes"}
+ Find solutions to common problems and learn how to implement them in your Nuxt project.
+ ::
::
diff --git a/docs/2.guide/1.concepts/1.auto-imports.md b/docs/2.guide/1.concepts/1.auto-imports.md
index 0fd8a3b63f..a7e24cd843 100644
--- a/docs/2.guide/1.concepts/1.auto-imports.md
+++ b/docs/2.guide/1.concepts/1.auto-imports.md
@@ -60,6 +60,10 @@ That means that (with very few exceptions) you cannot use them outside a Nuxt pl
If you get an error message like `Nuxt instance is unavailable` then it probably means you are calling a Nuxt composable in the wrong place in the Vue or Nuxt lifecycle.
+::tip{icon="i-ph-video-duotone" to="https://www.youtube.com/watch?v=ofuKRZLtOdY" target="_blank"}
+Watch a video from Alexander Lichter about handling async code in composables and fixing `Nuxt instance is unavailable` in your app.
+::
+
::read-more{to="/docs/guide/going-further/experimental-features#asynccontext" icon="i-ph-star-duotone"}
Checkout the `asyncContext` experimental feature to use Nuxt composables in async functions.
::
@@ -168,3 +172,7 @@ export default defineNuxtConfig({
}
})
```
+
+::tip{icon="i-ph-video-duotone" to="https://www.youtube.com/watch?v=FT2LQJ2NvVI" target="_blank"}
+Watch a video from Alexander Lichter on how to easily set up custom auto imports.
+::
diff --git a/docs/2.guide/1.concepts/8.typescript.md b/docs/2.guide/1.concepts/8.typescript.md
index 3c57bd7464..4e07f35631 100644
--- a/docs/2.guide/1.concepts/8.typescript.md
+++ b/docs/2.guide/1.concepts/8.typescript.md
@@ -65,6 +65,10 @@ This file contains the recommended basic TypeScript configuration for your proje
[Read more about how to extend this configuration](/docs/guide/directory-structure/tsconfig).
+::tip{icon="i-ph-video-duotone" to="https://youtu.be/umLI7SlPygY" target="_blank"}
+Watch a video from Daniel Roe explaining built-in Nuxt aliases.
+::
+
::note
Nitro also [auto-generates types](/docs/guide/concepts/server-engine#typed-api-routes) for API routes. Plus, Nuxt also generates types for globally available components and [auto-imports from your composables](/docs/guide/directory-structure/composables), plus other core functionality.
::
diff --git a/docs/2.guide/2.directory-structure/1.components.md b/docs/2.guide/2.directory-structure/1.components.md
index 0685a3389c..0cdf5cb4b6 100644
--- a/docs/2.guide/2.directory-structure/1.components.md
+++ b/docs/2.guide/2.directory-structure/1.components.md
@@ -259,7 +259,7 @@ Watch Learn Vue video about Nuxt Server Components.
::
::tip{icon="i-ph-article-duotone" to="https://roe.dev/blog/nuxt-server-components" target="_blank"}
-Read Daniel Roe's guide to Nuxt server components
+Read Daniel Roe's guide to Nuxt Server Components.
::
### Standalone server components
diff --git a/docs/2.guide/2.directory-structure/1.modules.md b/docs/2.guide/2.directory-structure/1.modules.md
index 2664e5601e..ce9de8f9f2 100644
--- a/docs/2.guide/2.directory-structure/1.modules.md
+++ b/docs/2.guide/2.directory-structure/1.modules.md
@@ -46,7 +46,11 @@ export default defineEventHandler(() => {
When starting Nuxt, the `hello` module will be registered and the `/api/hello` route will be available.
-Local modules are registered in alphabetical order. You can change the order by adding a number to the front of each directory name:
+Modules are executed in the following sequence:
+- First, the modules defined in [`nuxt.config.ts`](/docs/api/nuxt-config#modules-1) are loaded.
+- Then, modules found in the `modules/` directory are executed, and they load in alphabetical order.
+
+You can change the order of local module by adding a number to the front of each directory name:
```bash [Directory structure]
modules/
diff --git a/docs/2.guide/2.directory-structure/1.plugins.md b/docs/2.guide/2.directory-structure/1.plugins.md
index 3753847b5b..b525b1f4c4 100644
--- a/docs/2.guide/2.directory-structure/1.plugins.md
+++ b/docs/2.guide/2.directory-structure/1.plugins.md
@@ -76,6 +76,10 @@ export default defineNuxtPlugin({
})
```
+::tip{icon="i-ph-video-duotone" to="https://www.youtube.com/watch?v=2aXZyXB1QGQ" target="_blank"}
+Watch a video from Alexander Lichter about the Object Syntax for Nuxt plugins.
+::
+
::note
If you are using the object-syntax, the properties may be statically analyzed in future to produce a more optimized build. So you should not define them at runtime. :br
For example, setting `enforce: import.meta.server ? 'pre' : 'post'` would defeat any future optimization Nuxt is able to do for your plugins.
diff --git a/docs/2.guide/2.directory-structure/2.env.md b/docs/2.guide/2.directory-structure/2.env.md
index 660a8138d5..0a39c625a3 100644
--- a/docs/2.guide/2.directory-structure/2.env.md
+++ b/docs/2.guide/2.directory-structure/2.env.md
@@ -33,11 +33,25 @@ npx nuxi dev --dotenv .env.local
When updating `.env` in development mode, the Nuxt instance is automatically restarted to apply new values to the `process.env`.
-## Production Preview
+## Production
**After your server is built**, you are responsible for setting environment variables when you run the server.
-Your `.env` file will not be read at this point. How you do this is different for every environment.
+Your `.env` files will not be read at this point. How you do this is different for every environment.
+
+This design decision was made to ensure compatibility across various deployment environments, some of which may not have a traditional file system available, such as serverless platforms or edge networks like Cloudflare Workers.
+
+Since `.env` files are not used in production, you must explicitly set environment variables using the tools and methods provided by your hosting environment. Here are some common approaches:
+
+* You can pass the environment variables as arguments using the terminal:
+
+ `$ DATABASE_HOST=mydatabaseconnectionstring node .output/server/index.mjs`
+
+* You can set environment variables in shell configuration files like `.bashrc` or `.profile`.
+
+* Many cloud service providers, such as Vercel, Netlify, and AWS, provide interfaces for setting environment variables via their dashboards, CLI tools or configuration files.
+
+## Production Preview
For local production preview purpose, we recommend using [`nuxi preview`](/docs/api/commands/preview) since using this command, the `.env` file will be loaded into `process.env` for convenience. Note that this command requires dependencies to be installed in the package directory.
diff --git a/docs/2.guide/3.going-further/1.experimental-features.md b/docs/2.guide/3.going-further/1.experimental-features.md
index fd6ad8d44a..691a2187c3 100644
--- a/docs/2.guide/3.going-further/1.experimental-features.md
+++ b/docs/2.guide/3.going-further/1.experimental-features.md
@@ -306,6 +306,10 @@ Out of the box, this will enable typed usage of [`navigateTo`](/docs/api/utils/n
You can even get typed params within a page by using `const route = useRoute('route-name')`.
+::tip{icon="i-ph-video-duotone" to="https://www.youtube.com/watch?v=SXk-L19gTZk" target="_blank"}
+Watch a video from Daniel Roe explaining type-safe routing in Nuxt.
+::
+
## watcher
Set an alternative watcher that will be used as the watching service for Nuxt.
@@ -340,6 +344,10 @@ export default defineNuxtConfig({
})
```
+::tip{icon="i-ph-video-duotone" to="https://www.youtube.com/watch?v=1jUupYHVvrU" target="_blank"}
+Watch a video from Alexander Lichter about the experimental `sharedPrerenderData` setting.
+::
+
It is particularly important when enabling this feature to 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`
@@ -378,6 +386,16 @@ This option allows exposing some route metadata defined in `definePageMeta` at b
This only works with static or strings/arrays rather than variables or conditional assignment. See [original issue](https://github.com/nuxt/nuxt/issues/24770) for more information and context.
+
+
## cookieStore
Enables CookieStore support to listen for cookie updates (if supported by the browser) and refresh `useCookie` ref values.
diff --git a/docs/2.guide/3.going-further/10.runtime-config.md b/docs/2.guide/3.going-further/10.runtime-config.md
index e84823704c..9a1832ad50 100644
--- a/docs/2.guide/3.going-further/10.runtime-config.md
+++ b/docs/2.guide/3.going-further/10.runtime-config.md
@@ -61,6 +61,10 @@ Setting the default of `runtimeConfig` values to *differently named environment
It is advised to use environment variables that match the structure of your `runtimeConfig` object.
::
+::tip{icon="i-ph-video-duotone" to="https://youtu.be/_FYV5WfiWvs" target="_blank"}
+Watch a video from Alexander Lichter showcasing the top mistake developers make using runtimeConfig.
+::
+
#### Example
```sh [.env]
diff --git a/docs/2.guide/3.going-further/4.kit.md b/docs/2.guide/3.going-further/4.kit.md
index 0610737513..226762b899 100644
--- a/docs/2.guide/3.going-further/4.kit.md
+++ b/docs/2.guide/3.going-further/4.kit.md
@@ -15,6 +15,10 @@ Discover all Nuxt Kit utilities.
You can install the latest Nuxt Kit by adding it to the `dependencies` section of your `package.json`. However, please consider always explicitly installing the `@nuxt/kit` package even if it is already installed by Nuxt.
+::note
+`@nuxt/kit` and `@nuxt/schema` are key dependencies for Nuxt. If you are installing it separately, make sure that the versions of `@nuxt/kit` and `@nuxt/schema` are equal to or greater than your `nuxt` version to avoid any unexpected behavior.
+::
+
```json [package.json]
{
"dependencies": {
diff --git a/docs/2.guide/4.recipes/3.custom-usefetch.md b/docs/2.guide/4.recipes/3.custom-usefetch.md
new file mode 100644
index 0000000000..e27af2712f
--- /dev/null
+++ b/docs/2.guide/4.recipes/3.custom-usefetch.md
@@ -0,0 +1,105 @@
+---
+navigation.title: 'Custom useFetch'
+title: Custom useFetch in Nuxt
+description: How to create a custom fetcher for calling your external API in Nuxt 3.
+---
+
+When working with Nuxt, you might be making the frontend and fetching an external API, and you might want to set some default options for fetching from your API.
+
+The [`$fetch`](/docs/api/utils/dollarfetch) utility function (used by the [`useFetch`](/docs/api/composables/use-fetch) composable) is intentionally not globally configurable. This is important so that fetching behavior throughout your application remains consistent, and other integrations (like modules) can rely on the behavior of core utilities like `$fetch`.
+
+However, Nuxt provides a way to create a custom fetcher for your API (or multiple fetchers if you have multiple APIs to call).
+
+## Custom `$fetch`
+
+Let's create a custom `$fetch` instance with a [Nuxt plugin](/docs/guide/directory-structure/plugins).
+
+::note
+`$fetch` is a configured instance of [ofetch](https://github.com/unjs/ofetch) which supports adding the base URL of your Nuxt server as well as direct function calls during SSR (avoiding HTTP roundtrips).
+::
+
+Let's pretend here that:
+- The main API is https://api.nuxt.com
+- We are storing the JWT token in a session with [nuxt-auth-utils](https://github.com/atinux/nuxt-auth-utils)
+- If the API responds with a `401` status code, we redirect the user to the `/login` page
+
+```ts [plugins/api.ts]
+export default defineNuxtPlugin(() => {
+ const { session } = useUserSession()
+
+ const api = $fetch.create({
+ baseURL: 'https://api.nuxt.com',
+ onRequest({ request, options, error }) {
+ if (session.value?.token) {
+ const headers = options.headers ||= {}
+ if (Array.isArray(headers)) {
+ headers.push(['Authorization', `Bearer ${session.value?.token}`])
+ } else if (headers instanceof Headers) {
+ headers.set('Authorization', `Bearer ${session.value?.token}`)
+ } else {
+ headers.Authorization = `Bearer ${session.value?.token}`
+ }
+ }
+ },
+ async onResponseError({ response }) {
+ if (response.status === 401) {
+ await navigateTo('/login')
+ }
+ }
+ })
+
+ // Expose to useNuxtApp().$api
+ return {
+ provide: {
+ api
+ }
+ }
+})
+```
+
+With this Nuxt plugin, `$api` is exposed from `useNuxtApp()` to make API calls directly from the Vue components:
+
+```vue [app.vue]
+
+```
+
+::callout
+Wrapping with [`useAsyncData`](/docs/api/composables/use-async-data) **avoid double data fetching when doing server-side rendering** (server & client on hydration).
+::
+
+## Custom `useFetch`
+
+Now that `$api` has the logic we want, let's create a `useAPI` composable to replace the usage of `useAsyncData` + `$api`:
+
+```ts [composables/useAPI.ts]
+import type { UseFetchOptions } from 'nuxt/app'
+
+export function useAPI(
+ url: string | (() => string),
+ options: Omit, 'default'> & { default: () => T | Ref },
+) {
+ return useFetch(url, {
+ ...options,
+ $fetch: useNuxtApp().$api
+ })
+}
+```
+
+Let's use the new composable and have a nice and clean component:
+
+```vue [app.vue]
+
+```
+
+::callout{icon="i-simple-icons-youtube" color="red" to="https://www.youtube.com/watch?v=jXH8Tr-exhI"}
+Watch a video about custom `$fetch` and Repository Pattern in Nuxt.
+::
+
+::note
+We are currently discussing to find a cleaner way to let you create a custom fetcher, see https://github.com/nuxt/nuxt/issues/14736.
+::
diff --git a/docs/3.api/1.components/4.nuxt-link.md b/docs/3.api/1.components/4.nuxt-link.md
index 5e54807145..b623d1ce5a 100644
--- a/docs/3.api/1.components/4.nuxt-link.md
+++ b/docs/3.api/1.components/4.nuxt-link.md
@@ -25,6 +25,22 @@ In this example, we use `` component to link to another page of the ap
```
+### Passing Params to Dynamic Routes
+
+In this example, we pass the `id` param to link to the route `~/pages/posts/[id].vue`.
+
+```vue [pages/index.vue]
+
+
+ Post 123
+
+
+```
+
+::tip
+Check out the Pages panel in Nuxt DevTools to see the route name and the params it might take.
+::
+
### Handling 404s
When using `` for `/public` directory files or when pointing to a different app on the same domain, you should use the `external` prop.
diff --git a/docs/3.api/1.components/5.nuxt-loading-indicator.md b/docs/3.api/1.components/5.nuxt-loading-indicator.md
index e22b582422..ad2f6936cb 100644
--- a/docs/3.api/1.components/5.nuxt-loading-indicator.md
+++ b/docs/3.api/1.components/5.nuxt-loading-indicator.md
@@ -30,6 +30,7 @@ You can pass custom HTML or components through the loading indicator's default s
## Props
- `color`: The color of the loading bar. It can be set to `false` to turn off explicit color styling.
+- `errorColor`: The color of the loading bar when `error` is set to `true`.
- `height`: Height of the loading bar, in pixels (default `3`).
- `duration`: Duration of the loading bar, in milliseconds (default `2000`).
- `throttle`: Throttle the appearing and hiding, in milliseconds (default `200`).
diff --git a/docs/3.api/2.composables/use-cookie.md b/docs/3.api/2.composables/use-cookie.md
index 4319f7f05b..804d3e2784 100644
--- a/docs/3.api/2.composables/use-cookie.md
+++ b/docs/3.api/2.composables/use-cookie.md
@@ -59,13 +59,10 @@ Use these options to set the expiration of the cookie.
The given number will be converted to an integer by rounding down. By default, no maximum age is set.
`expires`: Specifies the `Date` object to be the value for the [`Expires` `Set-Cookie` attribute](https://tools.ietf.org/html/rfc6265#section-5.2.1).
-By default, no expiration is set. Most clients will consider this a "non-persistent cookie" and
-will delete it on a condition like exiting a web browser application.
+By default, no expiration is set. Most clients will consider this a "non-persistent cookie" and will delete it on a condition like exiting a web browser application.
::note
-The [cookie storage model specification](https://tools.ietf.org/html/rfc6265#section-5.3) states that if both `expires` and
-`maxAge` is set, then `maxAge` takes precedence, but not all clients may obey this,
-so if both are set, they should point to the same date and time!
+The [cookie storage model specification](https://tools.ietf.org/html/rfc6265#section-5.3) states that if both `expires` and `maxAge` is set, then `maxAge` takes precedence, but not all clients may obey this, so if both are set, they should point to the same date and time!
::
::note
@@ -74,22 +71,29 @@ If neither of `expires` and `maxAge` is set, the cookie will be session-only and
### `httpOnly`
-Specifies the `boolean` value for the [`HttpOnly` `Set-Cookie` attribute](https://tools.ietf.org/html/rfc6265#section-5.2.6). When truthy,
-the `HttpOnly` attribute is set; otherwise it is not. By default, the `HttpOnly` attribute is not set.
+Specifies the `boolean` value for the [`HttpOnly` `Set-Cookie` attribute](https://tools.ietf.org/html/rfc6265#section-5.2.6). When truthy, the `HttpOnly` attribute is set; otherwise it is not. By default, the `HttpOnly` attribute is not set.
::warning
-Be careful when setting this to `true`, as compliant clients will not allow client-side
-JavaScript to see the cookie in `document.cookie`.
+Be careful when setting this to `true`, as compliant clients will not allow client-side JavaScript to see the cookie in `document.cookie`.
::
### `secure`
-Specifies the `boolean` value for the [`Secure` `Set-Cookie` attribute](https://tools.ietf.org/html/rfc6265#section-5.2.5). When truthy,
-the `Secure` attribute is set; otherwise it is not. By default, the `Secure` attribute is not set.
+Specifies the `boolean` value for the [`Secure` `Set-Cookie` attribute](https://tools.ietf.org/html/rfc6265#section-5.2.5). When truthy, the `Secure` attribute is set; otherwise it is not. By default, the `Secure` attribute is not set.
::warning
-Be careful when setting this to `true`, as compliant clients will not send the cookie back to
-the server in the future if the browser does not have an HTTPS connection. This can lead to hydration errors.
+Be careful when setting this to `true`, as compliant clients will not send the cookie back to the server in the future if the browser does not have an HTTPS connection. This can lead to hydration errors.
+::
+
+### `partitioned`
+
+Specifies the `boolean` value for the [`Partitioned` `Set-Cookie`](https://datatracker.ietf.org/doc/html/draft-cutler-httpbis-partitioned-cookies#section-2.1) attribute. When truthy, the `Partitioned` attribute is set, otherwise it is not. By default, the `Partitioned` attribute is not set.
+
+::note
+This is an attribute that has not yet been fully standardized, and may change in the future.
+This also means many clients may ignore this attribute until they understand it.
+
+More information can be found in the [proposal](https://github.com/privacycg/CHIPS).
::
### `domain`
@@ -114,23 +118,18 @@ More information about the different enforcement levels can be found in [the spe
### `encode`
-Specifies a function that will be used to encode a cookie's value. Since the value of a cookie
-has a limited character set (and must be a simple string), this function can be used to encode
-a value into a string suited for a cookie's value.
+Specifies a function that will be used to encode a cookie's value. Since the value of a cookie has a limited character set (and must be a simple string), this function can be used to encode a value into a string suited for a cookie's value.
The default encoder is the `JSON.stringify` + `encodeURIComponent`.
### `decode`
-Specifies a function that will be used to decode a cookie's value. Since the value of a cookie
-has a limited character set (and must be a simple string), this function can be used to decode
-a previously encoded cookie value into a JavaScript string or other object.
+Specifies a function that will be used to decode a cookie's value. Since the value of a cookie has a limited character set (and must be a simple string), this function can be used to decode a previously encoded cookie value into a JavaScript string or other object.
The default decoder is `decodeURIComponent` + [destr](https://github.com/unjs/destr).
::note
-If an error is thrown from this function, the original, non-decoded cookie value will
-be returned as the cookie's value.
+If an error is thrown from this function, the original, non-decoded cookie value will be returned as the cookie's value.
::
### `default`
diff --git a/docs/3.api/2.composables/use-fetch.md b/docs/3.api/2.composables/use-fetch.md
index e0149ce11f..84a73d0fc9 100644
--- a/docs/3.api/2.composables/use-fetch.md
+++ b/docs/3.api/2.composables/use-fetch.md
@@ -66,6 +66,10 @@ const { data, pending, error, refresh } = await useFetch('/api/auth/login', {
`useFetch` is a reserved function name transformed by the compiler, so you should not name your own function `useFetch`.
::
+::tip{icon="i-ph-video-duotone" to="https://www.youtube.com/watch?v=njsGVmcWviY" target="_blank"}
+Watch the video from Alexander Lichter to avoid using `useFetch` the wrong way!
+::
+
:link-example{to="/docs/examples/advanced/use-custom-fetch-composable"}
:read-more{to="/docs/getting-started/data-fetching"}
@@ -83,6 +87,8 @@ const { data, pending, error, refresh } = await useFetch('/api/auth/login', {
- `headers`: Request headers.
- `baseURL`: Base URL for the request.
- `timeout`: Milliseconds to automatically abort request
+ - `cache`: Handles cache control according to [Fetch API](https://developer.mozilla.org/en-US/docs/Web/API/fetch#cache)
+ - You can pass boolean to disable the cache or you can pass one of the following values: `default`, `no-store`, `reload`, `no-cache`, `force-cache`, and `only-if-cached`.
::note
All fetch options can be given a `computed` or `ref` value. These will be watched and new requests made automatically with any new values if they are updated.
diff --git a/docs/3.api/2.composables/use-loading-indicator.md b/docs/3.api/2.composables/use-loading-indicator.md
index 2e4a5bb97b..357c260bbd 100644
--- a/docs/3.api/2.composables/use-loading-indicator.md
+++ b/docs/3.api/2.composables/use-loading-indicator.md
@@ -26,6 +26,11 @@ It hooks into [`page:loading:start`](/docs/api/advanced/hooks#app-hooks-runtime)
- **type**: `Ref`
- **description**: The loading state
+### `error`
+
+- **type**: `Ref`
+- **description**: The error state
+
### `progress`
- **type**: `Ref`
@@ -39,7 +44,7 @@ Set `isLoading` to true and start to increase the `progress` value.
### `finish()`
-Set the `progress` value to `100`, stop all timers and intervals then reset the loading state `500` ms later. `finish` accepts a `{ force: true }` option to skip the interval before the state is reset.
+Set the `progress` value to `100`, stop all timers and intervals then reset the loading state `500` ms later. `finish` accepts a `{ force: true }` option to skip the interval before the state is reset, and `{ error: true }` to change the loading bar color and set the error property to true.
### `clear()`
diff --git a/docs/3.api/2.composables/use-nuxt-app.md b/docs/3.api/2.composables/use-nuxt-app.md
index 87c7be7418..860cb89c22 100644
--- a/docs/3.api/2.composables/use-nuxt-app.md
+++ b/docs/3.api/2.composables/use-nuxt-app.md
@@ -138,6 +138,10 @@ Nuxt exposes the following properties through `ssrContext`:
Since [Nuxt v3.4](https://nuxt.com/blog/v3-4#payload-enhancements), it is possible to define your own reducer/reviver for types that are not supported by Nuxt.
+ ::tip{icon="i-ph-video-duotone" to="https://www.youtube.com/watch?v=8w6ffRBs8a4" target="_blank"}
+ Watch a video from Alexander Lichter about serializing payloads, especially with regards to classes.
+ ::
+
In the example below, we define a reducer (or a serializer) and a reviver (or deserializer) for the [Luxon](https://moment.github.io/luxon/#/) DateTime class, using a payload plugin.
```ts [plugins/date-time-payload.ts]
diff --git a/docs/3.api/2.composables/use-state.md b/docs/3.api/2.composables/use-state.md
index dccccae072..513bd407c6 100644
--- a/docs/3.api/2.composables/use-state.md
+++ b/docs/3.api/2.composables/use-state.md
@@ -25,6 +25,10 @@ Because the data inside `useState` will be serialized to JSON, it is important t
`useState` is a reserved function name transformed by the compiler, so you should not name your own function `useState`.
::
+::tip{icon="i-ph-video-duotone" to="https://www.youtube.com/watch?v=mv0WcBABcIk" target="_blank"}
+Watch a video from Alexander Lichter about why and when to use `useState()`.
+::
+
## Using `shallowRef`
If you don't need your state to be deeply reactive, you can combine `useState` with [`shallowRef`](https://vuejs.org/api/reactivity-advanced.html#shallowref). This can improve performance when your state contains large objects and arrays.
diff --git a/docs/5.community/4.contribution.md b/docs/5.community/4.contribution.md
index 3702d845a8..a601e4a51d 100644
--- a/docs/5.community/4.contribution.md
+++ b/docs/5.community/4.contribution.md
@@ -32,6 +32,10 @@ We'll do our best to follow our [internal issue decision making flowchart](https
### Send a Pull Request
+::Tip
+On windows, you need to clone the repository with `git clone -c core.symlinks=true https://github.com/nuxt/nuxt.git` to make symlinks work.
+::
+
We always welcome pull requests! β€οΈ
#### Before You Start
diff --git a/docs/5.community/6.roadmap.md b/docs/5.community/6.roadmap.md
index 6337f81fe0..f485be077d 100644
--- a/docs/5.community/6.roadmap.md
+++ b/docs/5.community/6.roadmap.md
@@ -38,12 +38,12 @@ Translations | - | [nuxt/translations#4](https://github.com/nuxt/tra
In addition to the Nuxt framework, there are modules that are vital for the ecosystem. Their status will be updated below.
-Module | Status | Nuxt Support | Repository | Description
----------------|---------------------|--------------|------------|-------------------
-Scripts | April 2024 | 3.x | `nuxt/scripts` to be announced | Easy 3rd party script management. [nuxt/nuxt#22016](https://github.com/nuxt/nuxt/discussions/22016)
-A11y | Planned | 3.x | `nuxt/a11y` to be announced | Accessibility hinting and utilities [nuxt/nuxt#23255](https://github.com/nuxt/nuxt/issues/23255)
-Auth | Planned | 3.x | `nuxt/auth` to be announced | Nuxt 3 support is planned after session support
-Hints | Planned | 3.x | `nuxt/hints` to be announced | Guidance and suggestions for enhancing development practices
+Module | Status | Nuxt Support | Repository | Description
+------------------------------------|---------------------|--------------|------------|-------------------
+[Scripts](https://scripts.nuxt.com) | Public Preview | 3.x | [nuxt/scripts](https://github.com/nuxt/scripts) | Easy 3rd party script management.
+A11y | Planned | 3.x | `nuxt/a11y` to be announced | Accessibility hinting and utilities [nuxt/nuxt#23255](https://github.com/nuxt/nuxt/issues/23255)
+Auth | Planned | 3.x | `nuxt/auth` to be announced | Nuxt 3 support is planned after session support.
+Hints | Planned | 3.x | `nuxt/hints` to be announced | Guidance and suggestions for enhancing development practices.
## Release Cycle
@@ -64,9 +64,9 @@ Each active version has its own nightly releases which are generated automatical
Release | | Initial release | End Of Life | Docs
----------------------------------------|---------------------------------------------------------------------------------------------------|-----------------|--------------|-------
**4.x** (scheduled) | | 2024 Q2 | |
-**3.x** (stable) | | 2022-11-16 | TBA | [nuxt.com](/docs)
-**2.x** (maintenance) | | 2018-09-21 | 2024-06-30 | [v2.nuxt.com](https://v2.nuxt.com/docs)
-**1.x** (unsupported) | | 2018-01-08 | 2019-09-21 |
+**3.x** (stable) | | 2022-11-16 | TBA | [nuxt.com](/docs)
+**2.x** (maintenance) | | 2018-09-21 | 2024-06-30 | [v2.nuxt.com](https://v2.nuxt.com/docs)
+**1.x** (unsupported) | | 2018-01-08 | 2019-09-21 |
### Support Status
diff --git a/docs/5.community/7.changelog.md b/docs/5.community/7.changelog.md
index 3016d4f686..5daf444f3b 100644
--- a/docs/5.community/7.changelog.md
+++ b/docs/5.community/7.changelog.md
@@ -68,6 +68,16 @@ navigation.icon: i-ph-notification-duotone
::card
---
icon: i-simple-icons-github
+ title: nuxt/scripts
+ to: https://github.com/nuxt/scripts/tags
+ target: _blank
+ ui.icon.base: text-black dark:text-white
+ ---
+ Nuxt Scripts releases. (Public Preview)
+ ::
+ ::card
+ ---
+ icon: i-simple-icons-github
title: nuxt/ui
to: https://github.com/nuxt/ui/releases
target: _blank
diff --git a/package.json b/package.json
index 07b5a76a7f..6dd9b42cd8 100644
--- a/package.json
+++ b/package.json
@@ -20,6 +20,7 @@
"lint:knip": "pnpx knip",
"play": "nuxi dev playground",
"play:build": "nuxi build playground",
+ "play:generate": "nuxi generate playground",
"play:preview": "nuxi preview playground",
"test": "pnpm test:fixtures && pnpm test:fixtures:dev && pnpm test:fixtures:webpack && pnpm test:unit && pnpm test:runtime && pnpm test:types && pnpm typecheck",
"test:prepare": "jiti ./test/prepare.ts",
@@ -40,20 +41,20 @@
"@nuxt/webpack-builder": "workspace:*",
"magic-string": "^0.30.10",
"nuxt": "workspace:*",
- "rollup": "^4.17.2",
- "vite": "5.2.11",
+ "rollup": "^4.18.0",
+ "vite": "5.2.12",
"vue": "3.4.27"
},
"devDependencies": {
- "@eslint/js": "9.2.0",
- "@nuxt/eslint-config": "0.3.12",
+ "@eslint/js": "9.3.0",
+ "@nuxt/eslint-config": "0.3.13",
"@nuxt/kit": "workspace:*",
- "@nuxt/test-utils": "3.12.1",
+ "@nuxt/test-utils": "3.13.1",
"@nuxt/webpack-builder": "workspace:*",
- "@testing-library/vue": "8.0.3",
+ "@testing-library/vue": "8.1.0",
"@types/eslint__js": "8.42.3",
"@types/fs-extra": "11.0.4",
- "@types/node": "20.12.11",
+ "@types/node": "20.12.13",
"@types/semver": "7.5.8",
"@vitest/coverage-v8": "1.6.0",
"@vue/test-utils": "2.4.6",
@@ -61,25 +62,25 @@
"changelogen": "0.5.5",
"consola": "3.2.3",
"devalue": "5.0.0",
- "eslint": "9.2.0",
+ "eslint": "9.3.0",
"eslint-plugin-no-only-tests": "3.1.0",
"eslint-plugin-perfectionist": "2.10.0",
"eslint-typegen": "0.2.4",
- "execa": "9.0.1",
+ "execa": "9.1.0",
"fs-extra": "11.2.0",
"globby": "14.0.1",
"h3": "1.11.1",
- "happy-dom": "14.10.1",
+ "happy-dom": "14.12.0",
"jiti": "1.21.0",
- "markdownlint-cli": "0.40.0",
+ "markdownlint-cli": "0.41.0",
"nitropack": "2.9.6",
"nuxi": "3.11.1",
"nuxt": "workspace:*",
"nuxt-content-twoslash": "0.0.10",
"ofetch": "1.3.4",
"pathe": "1.1.2",
- "playwright-core": "1.44.0",
- "rimraf": "5.0.5",
+ "playwright-core": "1.44.1",
+ "rimraf": "5.0.7",
"semver": "7.6.2",
"std-env": "3.7.0",
"typescript": "5.4.5",
@@ -88,9 +89,9 @@
"vitest-environment-nuxt": "1.0.0",
"vue": "3.4.27",
"vue-router": "4.3.2",
- "vue-tsc": "2.0.16"
+ "vue-tsc": "2.0.19"
},
- "packageManager": "pnpm@9.1.0",
+ "packageManager": "pnpm@9.1.3",
"engines": {
"node": "^16.10.0 || >=18.0.0"
},
diff --git a/packages/kit/package.json b/packages/kit/package.json
index 017b34cbce..62d145b029 100644
--- a/packages/kit/package.json
+++ b/packages/kit/package.json
@@ -44,7 +44,7 @@
"semver": "^7.6.2",
"ufo": "^1.5.3",
"unctx": "^2.3.1",
- "unimport": "^3.7.1",
+ "unimport": "^3.7.2",
"untyped": "^1.4.2"
},
"devDependencies": {
@@ -54,7 +54,7 @@
"lodash-es": "4.17.21",
"nitropack": "2.9.6",
"unbuild": "latest",
- "vite": "5.2.11",
+ "vite": "5.2.12",
"vitest": "1.6.0",
"webpack": "5.91.0"
},
diff --git a/packages/kit/src/compatibility.ts b/packages/kit/src/compatibility.ts
index a03813dd48..b2720f05c9 100644
--- a/packages/kit/src/compatibility.ts
+++ b/packages/kit/src/compatibility.ts
@@ -4,7 +4,7 @@ import type { Nuxt, NuxtCompatibility, NuxtCompatibilityIssues } from '@nuxt/sch
import { useNuxt } from './context'
export function normalizeSemanticVersion (version: string) {
- return version.replace(/-[0-9]+\.[0-9a-f]+/, '') // Remove edge prefix
+ return version.replace(/-\d+\.[0-9a-f]+/, '') // Remove edge prefix
}
const builderMap = {
diff --git a/packages/kit/src/internal/template.ts b/packages/kit/src/internal/template.ts
index 9349f704df..5b40a2a214 100644
--- a/packages/kit/src/internal/template.ts
+++ b/packages/kit/src/internal/template.ts
@@ -27,7 +27,7 @@ export async function compileTemplate (template: NuxtTemplate, ctx: any) {
}
/** @deprecated */
-const serialize = (data: any) => JSON.stringify(data, null, 2).replace(/"{(.+)}"(?=,?$)/gm, r => JSON.parse(r).replace(/^{(.*)}$/, '$1'))
+const serialize = (data: any) => JSON.stringify(data, null, 2).replace(/"\{(.+)\}"(?=,?$)/gm, r => JSON.parse(r).replace(/^\{(.*)\}$/, '$1'))
/** @deprecated */
const importSources = (sources: string | string[], { lazy = false } = {}) => {
diff --git a/packages/kit/src/loader/config.ts b/packages/kit/src/loader/config.ts
index 3c42525c4d..fab118bf6c 100644
--- a/packages/kit/src/loader/config.ts
+++ b/packages/kit/src/loader/config.ts
@@ -4,6 +4,8 @@ import type { ConfigLayer, ConfigLayerMeta, LoadConfigOptions } from 'c12'
import { loadConfig } from 'c12'
import type { NuxtConfig, NuxtOptions } from '@nuxt/schema'
import { NuxtConfigSchema } from '@nuxt/schema'
+import { globby } from 'globby'
+import defu from 'defu'
export interface LoadNuxtConfigOptions extends LoadConfigOptions {}
@@ -11,12 +13,19 @@ const layerSchemaKeys = ['future', 'srcDir', 'rootDir', 'dir']
const layerSchema = Object.fromEntries(Object.entries(NuxtConfigSchema).filter(([key]) => layerSchemaKeys.includes(key)))
export async function loadNuxtConfig (opts: LoadNuxtConfigOptions): Promise {
+ // Automatically detect and import layers from `~~/layers/` directory
+ opts.overrides = defu(opts.overrides, {
+ _extends: await globby('layers/*', {
+ onlyDirectories: true,
+ cwd: opts.cwd || process.cwd(),
+ }),
+ });
(globalThis as any).defineNuxtConfig = (c: any) => c
const result = await loadConfig({
name: 'nuxt',
configFile: 'nuxt.config',
rcFile: '.nuxtrc',
- extend: { extendKey: ['theme', 'extends'] },
+ extend: { extendKey: ['theme', 'extends', '_extends'] },
dotenv: true,
globalRc: true,
...opts,
diff --git a/packages/kit/src/module/define.ts b/packages/kit/src/module/define.ts
index 3327ca4ad5..ff2a56d2d4 100644
--- a/packages/kit/src/module/define.ts
+++ b/packages/kit/src/module/define.ts
@@ -73,8 +73,8 @@ export function defineNuxtModule (definition: Mo
const key = `nuxt:module:${uniqueKey || (Math.round(Math.random() * 10000))}`
const mark = performance.mark(key)
const res = await module.setup?.call(null as any, _options, nuxt) ?? {}
- const perf = performance.measure(key, mark?.name) // TODO: remove when Node 14 reaches EOL
- const setupTime = perf ? Math.round((perf.duration * 100)) / 100 : 0 // TODO: remove when Node 14 reaches EOL
+ const perf = performance.measure(key, mark.name)
+ const setupTime = Math.round((perf.duration * 100)) / 100
// Measure setup time
if (setupTime > 5000 && uniqueKey !== '@nuxt/telemetry') {
diff --git a/packages/kit/src/plugin.ts b/packages/kit/src/plugin.ts
index 5421c3027f..8905c11274 100644
--- a/packages/kit/src/plugin.ts
+++ b/packages/kit/src/plugin.ts
@@ -3,7 +3,6 @@ import type { NuxtPlugin, NuxtPluginTemplate } from '@nuxt/schema'
import { useNuxt } from './context'
import { addTemplate } from './template'
import { resolveAlias } from './resolve'
-import { logger } from './logger'
/**
* Normalize a nuxt plugin object
@@ -20,12 +19,6 @@ export function normalizePlugin (plugin: NuxtPlugin | string): NuxtPlugin {
throw new Error('Invalid plugin. src option is required: ' + JSON.stringify(plugin))
}
- // TODO: only scan top-level files #18418
- const nonTopLevelPlugin = plugin.src.match(/\/plugins\/[^/]+\/index\.[^/]+$/i)
- if (nonTopLevelPlugin && nonTopLevelPlugin.length > 0 && !useNuxt().options.plugins.find(i => (typeof i === 'string' ? i : i.src).endsWith(nonTopLevelPlugin[0]))) {
- logger.warn(`[deprecation] You are using a plugin that is within a subfolder of your plugins directory without adding it to your config explicitly. You can move it to the top-level plugins directory, or include the file '~${nonTopLevelPlugin[0]}' in your plugins config (https://nuxt.com/docs/api/nuxt-config#plugins-1) to remove this warning.`)
- }
-
// Normalize full path to plugin
plugin.src = normalize(resolveAlias(plugin.src))
diff --git a/packages/kit/src/runtime-config.ts b/packages/kit/src/runtime-config.ts
index a83358f11c..034c59fa14 100644
--- a/packages/kit/src/runtime-config.ts
+++ b/packages/kit/src/runtime-config.ts
@@ -36,12 +36,12 @@ export function updateRuntimeConfig (runtimeConfig: Record) {
}
/**
- * @internal
- *
* https://github.com/unjs/nitro/blob/main/src/runtime/utils.env.ts.
- *
+*
* These utils will be replaced by util exposed from nitropack. See https://github.com/unjs/nitro/pull/2404
* for more context and future plans.)
+ *
+ * @internal
*/
type EnvOptions = {
@@ -94,7 +94,7 @@ function applyEnv (
return obj
}
-const envExpandRx = /{{(.*?)}}/g
+const envExpandRx = /\{\{(.*?)\}\}/g
function _expandFromEnv (value: string, env: Record = process.env) {
return value.replace(envExpandRx, (match, key) => {
diff --git a/packages/kit/src/template.ts b/packages/kit/src/template.ts
index 9290e62fd5..4aa9ec147b 100644
--- a/packages/kit/src/template.ts
+++ b/packages/kit/src/template.ts
@@ -202,7 +202,7 @@ export async function _generateTypes (nuxt: Nuxt) {
} else {
const path = stats?.isFile()
// remove extension
- ? relativePath.replace(/(?<=\w)\.\w+$/g, '')
+ ? relativePath.replace(/\b\.\w+$/g, '')
// non-existent file probably shouldn't be resolved
: aliases[alias]
@@ -230,7 +230,7 @@ export async function _generateTypes (nuxt: Nuxt) {
tsConfig.compilerOptions!.paths[alias] = await Promise.all(paths.map(async (path: string) => {
if (!isAbsolute(path)) { return path }
const stats = await fsp.stat(path).catch(() => null /* file does not exist */)
- return relativeWithDot(nuxt.options.buildDir, stats?.isFile() ? path.replace(/(?<=\w)\.\w+$/g, '') /* remove extension */ : path)
+ return relativeWithDot(nuxt.options.buildDir, stats?.isFile() ? path.replace(/\b\.\w+$/g, '') /* remove extension */ : path)
}))
}
diff --git a/packages/nuxt/package.json b/packages/nuxt/package.json
index ecb5f44470..403b6c4d87 100644
--- a/packages/nuxt/package.json
+++ b/packages/nuxt/package.json
@@ -60,14 +60,14 @@
},
"dependencies": {
"@nuxt/devalue": "^2.0.2",
- "@nuxt/devtools": "^1.2.0",
+ "@nuxt/devtools": "^1.3.2",
"@nuxt/kit": "workspace:*",
"@nuxt/schema": "workspace:*",
"@nuxt/telemetry": "^2.5.4",
"@nuxt/vite-builder": "workspace:*",
- "@unhead/dom": "^1.9.10",
- "@unhead/ssr": "^1.9.10",
- "@unhead/vue": "^1.9.10",
+ "@unhead/dom": "^1.9.11",
+ "@unhead/ssr": "^1.9.11",
+ "@unhead/vue": "^1.9.11",
"@vue/shared": "^3.4.27",
"acorn": "8.11.3",
"c12": "^1.10.0",
@@ -76,7 +76,7 @@
"defu": "^6.1.4",
"destr": "^2.0.3",
"devalue": "^5.0.0",
- "esbuild": "^0.21.1",
+ "esbuild": "^0.21.4",
"escape-string-regexp": "^5.0.0",
"estree-walker": "^3.0.3",
"fs-extra": "^11.2.0",
@@ -99,6 +99,7 @@
"pkg-types": "^1.1.1",
"radix3": "^1.1.2",
"scule": "^1.3.0",
+ "semver": "^7.6.2",
"std-env": "^3.7.0",
"strip-literal": "^2.1.0",
"ufo": "^1.5.3",
@@ -106,7 +107,7 @@
"uncrypto": "^0.1.3",
"unctx": "^2.3.1",
"unenv": "^1.9.0",
- "unimport": "^3.7.1",
+ "unimport": "^3.7.2",
"unplugin": "^1.10.1",
"unplugin-vue-router": "^0.7.0",
"unstorage": "^1.10.2",
@@ -117,13 +118,13 @@
"vue-router": "^4.3.2"
},
"devDependencies": {
- "@nuxt/ui-templates": "1.3.3",
+ "@nuxt/ui-templates": "1.3.4",
"@parcel/watcher": "2.4.1",
"@types/estree": "1.0.5",
"@types/fs-extra": "11.0.4",
"@vitejs/plugin-vue": "5.0.4",
"unbuild": "latest",
- "vite": "5.2.11",
+ "vite": "5.2.12",
"vitest": "1.6.0"
},
"peerDependencies": {
diff --git a/packages/nuxt/src/app/components/nuxt-loading-indicator.ts b/packages/nuxt/src/app/components/nuxt-loading-indicator.ts
index 2b51dd6584..a0273a8428 100644
--- a/packages/nuxt/src/app/components/nuxt-loading-indicator.ts
+++ b/packages/nuxt/src/app/components/nuxt-loading-indicator.ts
@@ -20,20 +20,24 @@ export default defineComponent({
type: [String, Boolean],
default: 'repeating-linear-gradient(to right,#00dc82 0%,#34cdfe 50%,#0047e1 100%)',
},
+ errorColor: {
+ type: String,
+ default: 'repeating-linear-gradient(to right,#f87171 0%,#ef4444 100%)',
+ },
estimatedProgress: {
type: Function as unknown as () => (duration: number, elapsed: number) => number,
required: false,
},
},
setup (props, { slots, expose }) {
- const { progress, isLoading, start, finish, clear } = useLoadingIndicator({
+ const { progress, isLoading, error, start, finish, clear } = useLoadingIndicator({
duration: props.duration,
throttle: props.throttle,
estimatedProgress: props.estimatedProgress,
})
expose({
- progress, isLoading, start, finish, clear,
+ progress, isLoading, error, start, finish, clear,
})
return () => h('div', {
@@ -47,7 +51,7 @@ export default defineComponent({
width: 'auto',
height: `${props.height}px`,
opacity: isLoading.value ? 1 : 0,
- background: props.color || undefined,
+ background: error.value ? props.errorColor : props.color || undefined,
backgroundSize: `${(100 / progress.value) * 100}% auto`,
transform: `scaleX(${progress.value}%)`,
transformOrigin: 'left',
diff --git a/packages/nuxt/src/app/components/nuxt-root.vue b/packages/nuxt/src/app/components/nuxt-root.vue
index 9c8c91516e..eefe5fee7f 100644
--- a/packages/nuxt/src/app/components/nuxt-root.vue
+++ b/packages/nuxt/src/app/components/nuxt-root.vue
@@ -1,7 +1,8 @@
+
i && 'then' in i)) {
// error handling
const error = useError()
+// render an empty
when plugins have thrown an error but we're not yet rendering the error page
+const abortRender = import.meta.server && error.value && !nuxtApp.ssrContext.error
onErrorCaptured((err, target, info) => {
nuxtApp.hooks.callHook('vue:error', err, target, info).catch(hookError => console.error('[nuxt] Error in `vue:error` hook', hookError))
if (import.meta.server || (isNuxtError(err) && (err.fatal || err.unhandled))) {
diff --git a/packages/nuxt/src/app/composables/asyncData.ts b/packages/nuxt/src/app/composables/asyncData.ts
index 5e9f9bf081..aafb729e14 100644
--- a/packages/nuxt/src/app/composables/asyncData.ts
+++ b/packages/nuxt/src/app/composables/asyncData.ts
@@ -8,7 +8,10 @@ import { createError } from './error'
import { onNuxtReady } from './ready'
// @ts-expect-error virtual file
-import { asyncDataDefaults } from '#build/nuxt.config.mjs'
+import { asyncDataDefaults, resetAsyncDataToUndefined } from '#build/nuxt.config.mjs'
+
+// TODO: temporary module for backwards compatibility
+import type { DefaultAsyncDataErrorValue, DefaultAsyncDataValue } from '#app/defaults'
export type AsyncDataRequestStatus = 'idle' | 'pending' | 'success' | 'error'
@@ -42,7 +45,7 @@ export interface AsyncDataOptions<
ResT,
DataT = ResT,
PickKeys extends KeysOf = KeysOf,
- DefaultT = null,
+ DefaultT = DefaultAsyncDataValue,
> {
/**
* Whether to fetch on the server side.
@@ -117,7 +120,7 @@ export interface _AsyncData {
refresh: (opts?: AsyncDataExecuteOptions) => Promise
execute: (opts?: AsyncDataExecuteOptions) => Promise
clear: () => void
- error: Ref
+ error: Ref
status: Ref
}
@@ -138,11 +141,11 @@ export function useAsyncData<
NuxtErrorDataT = unknown,
DataT = ResT,
PickKeys extends KeysOf = KeysOf,
- DefaultT = null,
+ DefaultT = DefaultAsyncDataValue,
> (
handler: (ctx?: NuxtApp) => Promise,
options?: AsyncDataOptions
-): AsyncData | DefaultT, (NuxtErrorDataT extends Error | NuxtError ? NuxtErrorDataT : NuxtError) | null>
+): AsyncData | DefaultT, (NuxtErrorDataT extends Error | NuxtError ? NuxtErrorDataT : NuxtError) | DefaultAsyncDataErrorValue>
/**
* Provides access to data that resolves asynchronously in an SSR-friendly composable.
* See {@link https://nuxt.com/docs/api/composables/use-async-data}
@@ -158,7 +161,7 @@ export function useAsyncData<
> (
handler: (ctx?: NuxtApp) => Promise,
options?: AsyncDataOptions
-): AsyncData | DefaultT, (NuxtErrorDataT extends Error | NuxtError ? NuxtErrorDataT : NuxtError) | null>
+): AsyncData | DefaultT, (NuxtErrorDataT extends Error | NuxtError ? NuxtErrorDataT : NuxtError) | DefaultAsyncDataErrorValue>
/**
* Provides access to data that resolves asynchronously in an SSR-friendly composable.
* See {@link https://nuxt.com/docs/api/composables/use-async-data}
@@ -171,12 +174,12 @@ export function useAsyncData<
NuxtErrorDataT = unknown,
DataT = ResT,
PickKeys extends KeysOf = KeysOf,
- DefaultT = null,
+ DefaultT = DefaultAsyncDataValue,
> (
key: string,
handler: (ctx?: NuxtApp) => Promise,
options?: AsyncDataOptions
-): AsyncData | DefaultT, (NuxtErrorDataT extends Error | NuxtError ? NuxtErrorDataT : NuxtError) | null>
+): AsyncData | DefaultT, (NuxtErrorDataT extends Error | NuxtError ? NuxtErrorDataT : NuxtError) | DefaultAsyncDataErrorValue>
/**
* Provides access to data that resolves asynchronously in an SSR-friendly composable.
* See {@link https://nuxt.com/docs/api/composables/use-async-data}
@@ -194,14 +197,14 @@ export function useAsyncData<
key: string,
handler: (ctx?: NuxtApp) => Promise,
options?: AsyncDataOptions
-): AsyncData | DefaultT, (NuxtErrorDataT extends Error | NuxtError ? NuxtErrorDataT : NuxtError) | null>
+): AsyncData | DefaultT, (NuxtErrorDataT extends Error | NuxtError ? NuxtErrorDataT : NuxtError) | DefaultAsyncDataErrorValue>
export function useAsyncData<
ResT,
NuxtErrorDataT = unknown,
DataT = ResT,
PickKeys extends KeysOf = KeysOf,
- DefaultT = null,
-> (...args: any[]): AsyncData, (NuxtErrorDataT extends Error | NuxtError ? NuxtErrorDataT : NuxtError) | null> {
+ DefaultT = DefaultAsyncDataValue,
+> (...args: any[]): AsyncData, (NuxtErrorDataT extends Error | NuxtError ? NuxtErrorDataT : NuxtError) | DefaultAsyncDataErrorValue> {
const autoKey = typeof args[args.length - 1] === 'string' ? args.pop() : undefined
if (typeof args[0] !== 'string') { args.unshift(autoKey) }
@@ -226,14 +229,14 @@ export function useAsyncData<
const value = nuxtApp.ssrContext!._sharedPrerenderCache!.get(key)
if (value) { return value as Promise }
- const promise = nuxtApp.runWithContext(_handler)
+ const promise = Promise.resolve().then(() => nuxtApp.runWithContext(_handler))
nuxtApp.ssrContext!._sharedPrerenderCache!.set(key, promise)
return promise
}
// Used to get default values
- const getDefault = () => null
+ const getDefault = () => asyncDataDefaults.value
const getDefaultCachedData = () => nuxtApp.isHydrating ? nuxtApp.payload.data[key] : nuxtApp.static.data[key]
// Apply defaults
@@ -250,11 +253,12 @@ export function useAsyncData<
console.warn('[nuxt] `boolean` values are deprecated for the `dedupe` option of `useAsyncData` and will be removed in the future. Use \'cancel\' or \'defer\' instead.')
}
+ // TODO: make more precise when v4 lands
const hasCachedData = () => options.getCachedData!(key, nuxtApp) != null
// Create or use a shared asyncData entity
if (!nuxtApp._asyncData[key] || !options.immediate) {
- nuxtApp.payload._errors[key] ??= null
+ nuxtApp.payload._errors[key] ??= asyncDataDefaults.errorValue
const _ref = options.deep ? ref : shallowRef
@@ -263,11 +267,15 @@ export function useAsyncData<
pending: ref(!hasCachedData()),
error: toRef(nuxtApp.payload._errors, key),
status: ref('idle'),
+ _default: options.default!,
}
}
// TODO: Else, somehow check for conflicting keys with different defaults or fetcher
- const asyncData = { ...nuxtApp._asyncData[key] } as AsyncData)>
+ const asyncData = { ...nuxtApp._asyncData[key] } as { _default?: unknown } & AsyncData)>
+
+ // Don't expose default function to end user
+ delete asyncData._default
asyncData.refresh = asyncData.execute = (opts = {}) => {
if (nuxtApp._asyncDataPromises[key]) {
@@ -307,7 +315,7 @@ export function useAsyncData<
nuxtApp.payload.data[key] = result
asyncData.data.value = result
- asyncData.error.value = null
+ asyncData.error.value = asyncDataDefaults.errorValue
asyncData.status.value = 'success'
})
.catch((error: any) => {
@@ -404,11 +412,11 @@ export function useLazyAsyncData<
DataE = Error,
DataT = ResT,
PickKeys extends KeysOf = KeysOf,
- DefaultT = null,
+ DefaultT = DefaultAsyncDataValue,
> (
handler: (ctx?: NuxtApp) => Promise,
options?: Omit, 'lazy'>
-): AsyncData | DefaultT, DataE | null>
+): AsyncData | DefaultT, DataE | DefaultAsyncDataValue>
export function useLazyAsyncData<
ResT,
DataE = Error,
@@ -418,18 +426,18 @@ export function useLazyAsyncData<
> (
handler: (ctx?: NuxtApp) => Promise,
options?: Omit, 'lazy'>
-): AsyncData | DefaultT, DataE | null>
+): AsyncData | DefaultT, DataE | DefaultAsyncDataValue>
export function useLazyAsyncData<
ResT,
DataE = Error,
DataT = ResT,
PickKeys extends KeysOf = KeysOf,
- DefaultT = null,
+ DefaultT = DefaultAsyncDataValue,
> (
key: string,
handler: (ctx?: NuxtApp) => Promise,
options?: Omit, 'lazy'>
-): AsyncData | DefaultT, DataE | null>
+): AsyncData | DefaultT, DataE | DefaultAsyncDataValue>
export function useLazyAsyncData<
ResT,
DataE = Error,
@@ -440,15 +448,15 @@ export function useLazyAsyncData<
key: string,
handler: (ctx?: NuxtApp) => Promise,
options?: Omit, 'lazy'>
-): AsyncData | DefaultT, DataE | null>
+): AsyncData | DefaultT, DataE | DefaultAsyncDataValue>
export function useLazyAsyncData<
ResT,
DataE = Error,
DataT = ResT,
PickKeys extends KeysOf = KeysOf,
- DefaultT = null,
-> (...args: any[]): AsyncData | DefaultT, DataE | null> {
+ DefaultT = DefaultAsyncDataValue,
+> (...args: any[]): AsyncData | DefaultT, DataE | DefaultAsyncDataValue> {
const autoKey = typeof args[args.length - 1] === 'string' ? args.pop() : undefined
if (typeof args[0] !== 'string') { args.unshift(autoKey) }
const [key, handler, options = {}] = args as [string, (ctx?: NuxtApp) => Promise, AsyncDataOptions]
@@ -463,12 +471,12 @@ export function useLazyAsyncData<
}
/** @since 3.1.0 */
-export function useNuxtData (key: string): { data: Ref } {
+export function useNuxtData (key: string): { data: Ref } {
const nuxtApp = useNuxtApp()
// Initialize value when key is not already set
if (!(key in nuxtApp.payload.data)) {
- nuxtApp.payload.data[key] = null
+ nuxtApp.payload.data[key] = asyncDataDefaults.value
}
return {
@@ -520,12 +528,12 @@ function clearNuxtDataByKey (nuxtApp: NuxtApp, key: string): void {
}
if (key in nuxtApp.payload._errors) {
- nuxtApp.payload._errors[key] = null
+ nuxtApp.payload._errors[key] = asyncDataDefaults.errorValue
}
if (nuxtApp._asyncData[key]) {
- nuxtApp._asyncData[key]!.data.value = undefined
- nuxtApp._asyncData[key]!.error.value = null
+ nuxtApp._asyncData[key]!.data.value = resetAsyncDataToUndefined ? undefined : nuxtApp._asyncData[key]!._default()
+ nuxtApp._asyncData[key]!.error.value = asyncDataDefaults.errorValue
nuxtApp._asyncData[key]!.pending.value = false
nuxtApp._asyncData[key]!.status.value = 'idle'
}
diff --git a/packages/nuxt/src/app/composables/error.ts b/packages/nuxt/src/app/composables/error.ts
index 2a76c389a0..69b56e1c2b 100644
--- a/packages/nuxt/src/app/composables/error.ts
+++ b/packages/nuxt/src/app/composables/error.ts
@@ -4,6 +4,9 @@ import { toRef } from 'vue'
import { useNuxtApp } from '../nuxt'
import { useRouter } from './router'
+// @ts-expect-error virtual file
+import { nuxtDefaultErrorValue } from '#build/nuxt.config.mjs'
+
export const NUXT_ERROR_SIGNATURE = '__nuxt_error'
/** @since 3.0.0 */
@@ -47,7 +50,7 @@ export const clearError = async (options: { redirect?: string } = {}) => {
await useRouter().replace(options.redirect)
}
- error.value = null
+ error.value = nuxtDefaultErrorValue
}
/** @since 3.0.0 */
diff --git a/packages/nuxt/src/app/composables/fetch.ts b/packages/nuxt/src/app/composables/fetch.ts
index d7b50ab717..a083c47789 100644
--- a/packages/nuxt/src/app/composables/fetch.ts
+++ b/packages/nuxt/src/app/composables/fetch.ts
@@ -8,6 +8,9 @@ import { useRequestFetch } from './ssr'
import type { AsyncData, AsyncDataOptions, KeysOf, MultiWatchSources, PickFrom } from './asyncData'
import { useAsyncData } from './asyncData'
+// TODO: temporary module for backwards compatibility
+import type { DefaultAsyncDataErrorValue, DefaultAsyncDataValue } from '#app/defaults'
+
// @ts-expect-error virtual file
import { fetchDefaults } from '#build/nuxt.config.mjs'
@@ -30,7 +33,7 @@ export interface UseFetchOptions<
ResT,
DataT = ResT,
PickKeys extends KeysOf = KeysOf,
- DefaultT = null,
+ DefaultT = DefaultAsyncDataValue,
R extends NitroFetchRequest = string & {},
M extends AvailableRouterMethod = AvailableRouterMethod,
> extends Omit, 'watch'>, ComputedFetchOptions {
@@ -54,11 +57,11 @@ export function useFetch<
_ResT = ResT extends void ? FetchResult : ResT,
DataT = _ResT,
PickKeys extends KeysOf = KeysOf,
- DefaultT = null,
+ DefaultT = DefaultAsyncDataValue,
> (
request: Ref | ReqT | (() => ReqT),
opts?: UseFetchOptions<_ResT, DataT, PickKeys, DefaultT, ReqT, Method>
-): AsyncData | DefaultT, ErrorT | null>
+): AsyncData | DefaultT, ErrorT | DefaultAsyncDataErrorValue>
/**
* Fetch data from an API endpoint with an SSR-friendly composable.
* See {@link https://nuxt.com/docs/api/composables/use-fetch}
@@ -77,7 +80,7 @@ export function useFetch<
> (
request: Ref | ReqT | (() => ReqT),
opts?: UseFetchOptions<_ResT, DataT, PickKeys, DefaultT, ReqT, Method>
-): AsyncData | DefaultT, ErrorT | null>
+): AsyncData | DefaultT, ErrorT | DefaultAsyncDataErrorValue>
export function useFetch<
ResT = void,
ErrorT = FetchError,
@@ -86,7 +89,7 @@ export function useFetch<
_ResT = ResT extends void ? FetchResult : ResT,
DataT = _ResT,
PickKeys extends KeysOf = KeysOf,
- DefaultT = null,
+ DefaultT = DefaultAsyncDataValue,
> (
request: Ref | ReqT | (() => ReqT),
arg1?: string | UseFetchOptions<_ResT, DataT, PickKeys, DefaultT, ReqT, Method>,
@@ -161,8 +164,10 @@ export function useFetch<
* @see https://github.com/unjs/ofetch/blob/bb2d72baa5d3f332a2185c20fc04e35d2c3e258d/src/fetch.ts#L152
*/
const timeoutLength = toValue(opts.timeout)
+ let timeoutId: NodeJS.Timeout
if (timeoutLength) {
- setTimeout(() => controller.abort(), timeoutLength)
+ timeoutId = setTimeout(() => controller.abort(), timeoutLength)
+ controller.signal.onabort = () => clearTimeout(timeoutId)
}
let _$fetch = opts.$fetch || globalThis.$fetch
@@ -175,7 +180,7 @@ export function useFetch<
}
}
- return _$fetch(_request.value, { signal: controller.signal, ..._fetchOptions } as any) as Promise<_ResT>
+ return _$fetch(_request.value, { signal: controller.signal, ..._fetchOptions } as any).finally(() => { clearTimeout(timeoutId) }) as Promise<_ResT>
}, _asyncDataOptions)
return asyncData
@@ -190,11 +195,11 @@ export function useLazyFetch<
_ResT = ResT extends void ? FetchResult : ResT,
DataT = _ResT,
PickKeys extends KeysOf = KeysOf,
- DefaultT = null,
+ DefaultT = DefaultAsyncDataValue,
> (
request: Ref | ReqT | (() => ReqT),
opts?: Omit, 'lazy'>
-): AsyncData | DefaultT, ErrorT | null>
+): AsyncData | DefaultT, ErrorT | DefaultAsyncDataErrorValue>
export function useLazyFetch<
ResT = void,
ErrorT = FetchError,
@@ -207,7 +212,7 @@ export function useLazyFetch<
> (
request: Ref | ReqT | (() => ReqT),
opts?: Omit, 'lazy'>
-): AsyncData | DefaultT, ErrorT | null>
+): AsyncData | DefaultT, ErrorT | DefaultAsyncDataErrorValue>
export function useLazyFetch<
ResT = void,
ErrorT = FetchError,
@@ -216,7 +221,7 @@ export function useLazyFetch<
_ResT = ResT extends void ? FetchResult : ResT,
DataT = _ResT,
PickKeys extends KeysOf = KeysOf,
- DefaultT = null,
+ DefaultT = DefaultAsyncDataValue,
> (
request: Ref | ReqT | (() => ReqT),
arg1?: string | Omit, 'lazy'>,
diff --git a/packages/nuxt/src/app/composables/loading-indicator.ts b/packages/nuxt/src/app/composables/loading-indicator.ts
index 5bae3c98b5..b3c0a86455 100644
--- a/packages/nuxt/src/app/composables/loading-indicator.ts
+++ b/packages/nuxt/src/app/composables/loading-indicator.ts
@@ -23,9 +23,10 @@ export type LoadingIndicator = {
_cleanup: () => void
progress: Ref
isLoading: Ref
+ error: Ref
start: () => void
set: (value: number) => void
- finish: (opts?: { force?: boolean }) => void
+ finish: (opts?: { force?: boolean, error?: boolean }) => void
clear: () => void
}
@@ -40,6 +41,7 @@ function createLoadingIndicator (opts: Partial = {}) {
const nuxtApp = useNuxtApp()
const progress = ref(0)
const isLoading = ref(false)
+ const error = ref(false)
let done = false
let rafId: number
@@ -47,7 +49,10 @@ function createLoadingIndicator (opts: Partial = {}) {
let hideTimeout: number | NodeJS.Timeout
let resetTimeout: number | NodeJS.Timeout
- const start = () => set(0)
+ const start = () => {
+ error.value = false
+ set(0)
+ }
function set (at = 0) {
if (nuxtApp.isHydrating) {
@@ -76,11 +81,14 @@ function createLoadingIndicator (opts: Partial = {}) {
}
}
- function finish (opts: { force?: boolean } = {}) {
+ function finish (opts: { force?: boolean, error?: boolean } = {}) {
progress.value = 100
done = true
clear()
_clearTimeouts()
+ if (opts.error) {
+ error.value = true
+ }
if (opts.force) {
progress.value = 0
isLoading.value = false
@@ -145,6 +153,7 @@ function createLoadingIndicator (opts: Partial = {}) {
_cleanup,
progress: computed(() => progress.value),
isLoading: computed(() => isLoading.value),
+ error: computed(() => error.value),
start,
set,
finish,
diff --git a/packages/nuxt/src/app/composables/manifest.ts b/packages/nuxt/src/app/composables/manifest.ts
index 13a60e8d82..58b53745c9 100644
--- a/packages/nuxt/src/app/composables/manifest.ts
+++ b/packages/nuxt/src/app/composables/manifest.ts
@@ -1,7 +1,6 @@
import type { MatcherExport, RouteMatcher } from 'radix3'
import { createMatcherFromExport, createRouter as createRadixRouter, toRouteMatcher } from 'radix3'
import { defu } from 'defu'
-import { useAppConfig } from '../config'
import { useRuntimeConfig } from '../nuxt'
// @ts-expect-error virtual file
import { appManifest as isAppManifestEnabled } from '#build/nuxt.config.mjs'
@@ -25,9 +24,7 @@ function fetchManifest () {
if (!isAppManifestEnabled) {
throw new Error('[nuxt] app manifest should be enabled with `experimental.appManifest`')
}
- // @ts-expect-error private property
- const buildId = useAppConfig().nuxt?.buildId
- manifest = $fetch(buildAssetsURL(`builds/meta/${buildId}.json`))
+ manifest = $fetch(buildAssetsURL(`builds/meta/${useRuntimeConfig().app.buildId}.json`))
manifest.then((m) => {
matcher = createMatcherFromExport(m.matcher)
})
diff --git a/packages/nuxt/src/app/composables/payload.ts b/packages/nuxt/src/app/composables/payload.ts
index 4e8cd677d4..7a7d8fd2e7 100644
--- a/packages/nuxt/src/app/composables/payload.ts
+++ b/packages/nuxt/src/app/composables/payload.ts
@@ -1,9 +1,8 @@
import { hasProtocol, joinURL, withoutTrailingSlash } from 'ufo'
import { parse } from 'devalue'
import { useHead } from '@unhead/vue'
-import { getCurrentInstance } from 'vue'
+import { getCurrentInstance, onServerPrefetch } from 'vue'
import { useNuxtApp, useRuntimeConfig } from '../nuxt'
-import { useAppConfig } from '../config'
import { useRoute } from './router'
import { getAppManifest, getRouteRules } from './manifest'
@@ -17,9 +16,9 @@ interface LoadPayloadOptions {
}
/** @since 3.0.0 */
-export function loadPayload (url: string, opts: LoadPayloadOptions = {}): Record | Promise> | null {
+export async function loadPayload (url: string, opts: LoadPayloadOptions = {}): Promise | null> {
if (import.meta.server || !payloadExtraction) { return null }
- const payloadURL = _getPayloadURL(url, opts)
+ const payloadURL = await _getPayloadURL(url, opts)
const nuxtApp = useNuxtApp()
const cache = nuxtApp._payloadCache = nuxtApp._payloadCache || {}
if (payloadURL in cache) {
@@ -40,25 +39,34 @@ export function loadPayload (url: string, opts: LoadPayloadOptions = {}): Record
return cache[payloadURL]
}
/** @since 3.0.0 */
-export function preloadPayload (url: string, opts: LoadPayloadOptions = {}) {
- const payloadURL = _getPayloadURL(url, opts)
- useHead({
- link: [
- { rel: 'modulepreload', href: payloadURL },
- ],
+export function preloadPayload (url: string, opts: LoadPayloadOptions = {}): Promise {
+ const nuxtApp = useNuxtApp()
+ const promise = _getPayloadURL(url, opts).then((payloadURL) => {
+ nuxtApp.runWithContext(() => useHead({
+ link: [
+ { rel: 'modulepreload', href: payloadURL },
+ ],
+ }))
})
+ if (import.meta.server) {
+ onServerPrefetch(() => promise)
+ }
+ return promise
}
// --- Internal ---
const filename = renderJsonPayloads ? '_payload.json' : '_payload.js'
-function _getPayloadURL (url: string, opts: LoadPayloadOptions = {}) {
+async function _getPayloadURL (url: string, opts: LoadPayloadOptions = {}) {
const u = new URL(url, 'http://localhost')
if (u.host !== 'localhost' || hasProtocol(u.pathname, { acceptRelative: true })) {
throw new Error('Payload URL must not include hostname: ' + url)
}
- const hash = opts.hash || (opts.fresh ? Date.now() : (useAppConfig().nuxt as any)?.buildId)
- return joinURL(useRuntimeConfig().app.baseURL, u.pathname, filename + (hash ? `?${hash}` : ''))
+ const config = useRuntimeConfig()
+ const hash = opts.hash || (opts.fresh ? Date.now() : config.app.buildId)
+ const cdnURL = config.app.cdnURL
+ const baseOrCdnURL = cdnURL && await isPrerendered(url) ? cdnURL : config.app.baseURL
+ return joinURL(baseOrCdnURL, u.pathname, filename + (hash ? `?${hash}` : ''))
}
async function _importPayload (payloadURL: string) {
diff --git a/packages/nuxt/src/app/composables/router.ts b/packages/nuxt/src/app/composables/router.ts
index 750a1aeaf6..d6395e5832 100644
--- a/packages/nuxt/src/app/composables/router.ts
+++ b/packages/nuxt/src/app/composables/router.ts
@@ -169,8 +169,7 @@ export const navigateTo = (to: RouteLocationRaw | undefined | null, options?: Na
nuxtApp.ssrContext!._renderResponse = {
statusCode: sanitizeStatusCode(options?.redirectCode || 302, 302),
body: ``,
- // do not encode as this would break some modules and some environments
- headers: { location },
+ headers: { location: encodeURI(location) },
}
return response
}
diff --git a/packages/nuxt/src/app/defaults.ts b/packages/nuxt/src/app/defaults.ts
new file mode 100644
index 0000000000..c83129cca6
--- /dev/null
+++ b/packages/nuxt/src/app/defaults.ts
@@ -0,0 +1,7 @@
+// TODO: temporary module for backwards compatibility
+
+export type DefaultAsyncDataErrorValue = null
+export type DefaultAsyncDataValue = null
+export type DefaultErrorValue = null
+
+export {}
diff --git a/packages/nuxt/src/app/nuxt.ts b/packages/nuxt/src/app/nuxt.ts
index 75821e7b55..8222527514 100644
--- a/packages/nuxt/src/app/nuxt.ts
+++ b/packages/nuxt/src/app/nuxt.ts
@@ -1,4 +1,4 @@
-import { effectScope, getCurrentInstance, getCurrentScope, hasInjectionContext, reactive } from 'vue'
+import { effectScope, getCurrentInstance, getCurrentScope, hasInjectionContext, reactive, shallowReactive } from 'vue'
import type { App, EffectScope, Ref, VNode, onErrorCaptured } from 'vue'
import type { RouteLocationNormalizedLoaded } from '#vue-router'
import type { HookCallback, Hookable } from 'hookable'
@@ -20,13 +20,15 @@ import type { LoadingIndicator } from '../app/composables/loading-indicator'
import type { RouteAnnouncer } from '../app/composables/route-announcer'
import type { ViewTransition } from './plugins/view-transitions.client'
+// @ts-expect-error virtual file
+import { appId } from '#build/nuxt.config.mjs'
+
+// TODO: temporary module for backwards compatibility
+import type { DefaultAsyncDataErrorValue, DefaultErrorValue } from '#app/defaults'
import type { NuxtAppLiterals } from '#app'
-// @ts-expect-error virtual import
-import { buildId } from '#build/nuxt.config.mjs'
-
-function getNuxtAppCtx (appName?: string) {
- return getContext(appName || buildId || 'nuxt-app', {
+function getNuxtAppCtx (appName = appId || 'nuxt-app') {
+ return getContext(appName, {
asyncContext: !!__NUXT_ASYNC_CONTEXT__ && import.meta.server,
})
}
@@ -92,8 +94,8 @@ export interface NuxtPayload {
state: Record
once: Set
config?: Pick
- error?: NuxtError | null
- _errors: Record
+ error?: NuxtError | DefaultErrorValue
+ _errors: Record
[key: string]: unknown
}
@@ -120,10 +122,12 @@ interface _NuxtApp {
_asyncDataPromises: Record | undefined>
/** @internal */
_asyncData: Record
+ data: Ref
pending: Ref
- error: Ref
+ error: Ref
status: Ref
+ /** @internal */
+ _default: () => unknown
} | undefined>
/** @internal */
@@ -244,7 +248,7 @@ export interface CreateOptions {
export function createNuxtApp (options: CreateOptions) {
let hydratingCount = 0
const nuxtApp: NuxtApp = {
- name: buildId,
+ _name: appId || 'nuxt-app',
_scope: effectScope(),
provide: undefined,
globalName: 'nuxt',
@@ -252,12 +256,11 @@ export function createNuxtApp (options: CreateOptions) {
get nuxt () { return __NUXT_VERSION__ },
get vue () { return nuxtApp.vueApp.version },
},
- payload: reactive({
- data: {},
- state: {},
+ payload: shallowReactive({
+ data: shallowReactive({}),
+ state: reactive({}),
once: new Set(),
- _errors: {},
- ...(import.meta.client ? window.__NUXT__ ?? {} : { serverRendered: true }),
+ _errors: shallowReactive({}),
}),
static: {
data: {},
@@ -288,11 +291,32 @@ export function createNuxtApp (options: CreateOptions) {
}
},
_asyncDataPromises: {},
- _asyncData: {},
+ _asyncData: shallowReactive({}),
_payloadRevivers: {},
...options,
} as any as NuxtApp
+ if (import.meta.server) {
+ nuxtApp.payload.serverRendered = true
+ }
+
+ // TODO: remove/refactor in https://github.com/nuxt/nuxt/issues/25336
+ if (import.meta.client && window.__NUXT__) {
+ for (const key in window.__NUXT__) {
+ switch (key) {
+ case 'data':
+ case 'state':
+ case '_errors':
+ // Preserve reactivity for non-rich payload support
+ Object.assign(nuxtApp.payload[key], window.__NUXT__[key])
+ break
+
+ default:
+ nuxtApp.payload[key] = window.__NUXT__[key]
+ }
+ }
+ }
+
nuxtApp.hooks = createHooks()
nuxtApp.hook = nuxtApp.hooks.hook
diff --git a/packages/nuxt/src/app/plugins/dev-server-logs.ts b/packages/nuxt/src/app/plugins/dev-server-logs.ts
index cccb105794..eeae8d7562 100644
--- a/packages/nuxt/src/app/plugins/dev-server-logs.ts
+++ b/packages/nuxt/src/app/plugins/dev-server-logs.ts
@@ -1,13 +1,21 @@
-import { consola, createConsola } from 'consola'
+import { createConsola } from 'consola'
import type { LogObject } from 'consola'
import { parse } from 'devalue'
+import { h } from 'vue'
import { defineNuxtPlugin } from '../nuxt'
// @ts-expect-error virtual file
import { devLogs, devRootDir } from '#build/nuxt.config.mjs'
-export default defineNuxtPlugin((nuxtApp) => {
+const devRevivers: Record any> = import.meta.server
+ ? {}
+ : {
+ VNode: data => h(data.type, data.props),
+ URL: data => new URL(data),
+ }
+
+export default defineNuxtPlugin(async (nuxtApp) => {
if (import.meta.test) { return }
if (import.meta.server) {
@@ -23,42 +31,18 @@ export default defineNuxtPlugin((nuxtApp) => {
date: true,
},
})
- const hydrationLogs = new Set()
- consola.wrapConsole()
- consola.addReporter({
- log (logObj) {
- try {
- hydrationLogs.add(JSON.stringify(logObj.args))
- } catch {
- // silently ignore - the worst case is a user gets log twice
- }
- },
- })
nuxtApp.hook('dev:ssr-logs', (logs) => {
for (const log of logs) {
- // deduplicate so we don't print out things that are logged on client
- try {
- if (!hydrationLogs.size || !hydrationLogs.has(JSON.stringify(log.args))) {
- logger.log(normalizeServerLog({ ...log }))
- }
- } catch {
- logger.log(normalizeServerLog({ ...log }))
- }
+ logger.log(normalizeServerLog({ ...log }))
}
})
-
- nuxtApp.hooks.hook('app:suspense:resolve', () => consola.restoreAll())
- nuxtApp.hooks.hookOnce('dev:ssr-logs', () => hydrationLogs.clear())
}
- // pass SSR logs after hydration
- nuxtApp.hooks.hook('app:suspense:resolve', async () => {
- if (typeof window !== 'undefined') {
- const content = document.getElementById('__NUXT_LOGS__')?.textContent
- const logs = content ? parse(content, nuxtApp._payloadRevivers) as LogObject[] : []
- await nuxtApp.hooks.callHook('dev:ssr-logs', logs)
- }
- })
+ if (typeof window !== 'undefined') {
+ const content = document.getElementById('__NUXT_LOGS__')?.textContent
+ const logs = content ? parse(content, { ...devRevivers, ...nuxtApp._payloadRevivers }) as LogObject[] : []
+ await nuxtApp.hooks.callHook('dev:ssr-logs', logs)
+ }
})
function normalizeFilenames (stack?: string) {
diff --git a/packages/nuxt/src/components/client-fallback-auto-id.ts b/packages/nuxt/src/components/client-fallback-auto-id.ts
index 19b5a39984..7aa42f1d0a 100644
--- a/packages/nuxt/src/components/client-fallback-auto-id.ts
+++ b/packages/nuxt/src/components/client-fallback-auto-id.ts
@@ -10,7 +10,7 @@ interface LoaderOptions {
transform?: ComponentsOptions['transform']
rootDir: string
}
-const CLIENT_FALLBACK_RE = /<(NuxtClientFallback|nuxt-client-fallback)( [^>]*)?>/
+const CLIENT_FALLBACK_RE = /<(?:NuxtClientFallback|nuxt-client-fallback)(?: [^>]*)?>/
const CLIENT_FALLBACK_GLOBAL_RE = /<(NuxtClientFallback|nuxt-client-fallback)( [^>]*)?>/g
export const clientFallbackAutoIdPlugin = createUnplugin((options: LoaderOptions) => {
const exclude = options.transform?.exclude || []
@@ -37,7 +37,7 @@ export const clientFallbackAutoIdPlugin = createUnplugin((options: LoaderOptions
s.replace(CLIENT_FALLBACK_GLOBAL_RE, (full, name, attrs) => {
count++
- if (/ :?uid=/g.test(attrs)) { return full }
+ if (/ :?uid=/.test(attrs)) { return full }
return `<${name} :uid="'${hash(relativeID)}' + JSON.stringify($props) + '${count}'" ${attrs ?? ''}>`
})
diff --git a/packages/nuxt/src/components/islandsTransform.ts b/packages/nuxt/src/components/islandsTransform.ts
index 6e018dc167..ac5a954932 100644
--- a/packages/nuxt/src/components/islandsTransform.ts
+++ b/packages/nuxt/src/components/islandsTransform.ts
@@ -25,7 +25,7 @@ interface ComponentChunkOptions {
}
const SCRIPT_RE = /`)
+ htmlContext.bodyAppend.unshift(``)
} catch (e) {
- console.warn('[nuxt] Failed to stringify dev server logs. You can define your own reducer/reviver for rich types following the instructions in https://nuxt.com/docs/api/composables/use-nuxt-app#payload.', e)
+ const shortError = e instanceof Error && 'toString' in e ? ` Received \`${e.toString()}\`.` : ''
+ console.warn(`[nuxt] Failed to stringify dev server logs.${shortError} You can define your own reducer/reviver for rich types following the instructions in https://nuxt.com/docs/api/composables/use-nuxt-app#payload.`)
}
})
}
@@ -70,8 +77,8 @@ function getStack () {
return stack.stack?.replace(EXCLUDE_TRACE_RE, '').replace(/^Error.*\n/, '') || ''
}
-const FILENAME_RE = /at.*\(([^:)]+)[):]/
-const FILENAME_RE_GLOBAL = /at.*\(([^)]+)\)/g
+const FILENAME_RE = /at[^(]*\(([^:)]+)[):]/
+const FILENAME_RE_GLOBAL = /at[^(]*\(([^)]+)\)/g
function extractFilenameFromStack (stacktrace: string) {
return stacktrace.match(FILENAME_RE)?.[1].replace(withTrailingSlash(rootDir), '')
}
diff --git a/packages/nuxt/src/core/runtime/nitro/renderer.ts b/packages/nuxt/src/core/runtime/nitro/renderer.ts
index eb0b599948..ebd84414a7 100644
--- a/packages/nuxt/src/core/runtime/nitro/renderer.ts
+++ b/packages/nuxt/src/core/runtime/nitro/renderer.ts
@@ -21,7 +21,7 @@ import type { HeadEntryOptions } from '@unhead/schema'
import type { Link, Script, Style } from '@unhead/vue'
import { createServerHead } from '@unhead/vue'
-import { defineRenderHandler, getRouteRules, useAppConfig, useRuntimeConfig, useStorage } from '#internal/nitro'
+import { defineRenderHandler, getRouteRules, useRuntimeConfig, useStorage } from '#internal/nitro'
import { useNitroApp } from '#internal/nitro/app'
// @ts-expect-error virtual file
@@ -327,7 +327,7 @@ export default defineRenderHandler(async (event): Promise window?.__NUXT__?.config || {}
export const appConfigDeclarationTemplate: NuxtTemplate = {
filename: 'types/app.config.d.ts',
getContents ({ app, nuxt }) {
+ const typesDir = join(nuxt.options.buildDir, 'types')
+ const configPaths = app.configs.map(path => relative(typesDir, path).replace(/\b\.\w+$/g, ''))
+
return `
import type { CustomAppConfig } from 'nuxt/schema'
import type { Defu } from 'defu'
-${app.configs.map((id: string, index: number) => `import ${`cfg${index}`} from ${JSON.stringify(id.replace(/(?<=\w)\.\w+$/g, ''))}`).join('\n')}
+${configPaths.map((id: string, index: number) => `import ${`cfg${index}`} from ${JSON.stringify(id)}`).join('\n')}
declare const inlineConfig = ${JSON.stringify(nuxt.options.appConfig, null, 2)}
type ResolvedAppConfig = Defu `typeof cfg${index}`).join(', ')}]>
@@ -393,11 +402,43 @@ export const nuxtConfigTemplate: NuxtTemplate = {
`export const devRootDir = ${ctx.nuxt.options.dev ? JSON.stringify(ctx.nuxt.options.rootDir) : 'null'}`,
`export const devLogs = ${JSON.stringify(ctx.nuxt.options.features.devLogs)}`,
`export const nuxtLinkDefaults = ${JSON.stringify(ctx.nuxt.options.experimental.defaults.nuxtLink)}`,
- `export const asyncDataDefaults = ${JSON.stringify(ctx.nuxt.options.experimental.defaults.useAsyncData)}`,
+ `export const asyncDataDefaults = ${JSON.stringify({
+ ...ctx.nuxt.options.experimental.defaults.useAsyncData,
+ value: ctx.nuxt.options.experimental.defaults.useAsyncData.value === 'null' ? null : undefined,
+ errorValue: ctx.nuxt.options.experimental.defaults.useAsyncData.errorValue === 'null' ? null : undefined,
+ })}`,
+ `export const resetAsyncDataToUndefined = ${ctx.nuxt.options.experimental.resetAsyncDataToUndefined}`,
+ `export const nuxtDefaultErrorValue = ${ctx.nuxt.options.future.compatibilityVersion === 4 ? 'undefined' : 'null'}`,
`export const fetchDefaults = ${JSON.stringify(fetchDefaults)}`,
`export const vueAppRootContainer = ${ctx.nuxt.options.app.rootId ? `'#${ctx.nuxt.options.app.rootId}'` : `'body > ${ctx.nuxt.options.app.rootTag}'`}`,
`export const viewTransition = ${ctx.nuxt.options.experimental.viewTransition}`,
- `export const buildId = ${JSON.stringify(ctx.nuxt.options.buildId)}`,
+ `export const appId = ${JSON.stringify(ctx.nuxt.options.appId)}`,
].join('\n\n')
},
}
+
+const TYPE_FILENAME_RE = /\.([cm])?[jt]s$/
+const DECLARATION_RE = /\.d\.[cm]?ts$/
+export const buildTypeTemplate: NuxtTemplate = {
+ filename: 'types/build.d.ts',
+ getContents ({ app }) {
+ let declarations = ''
+
+ for (const file of app.templates) {
+ if (file.write || !file.filename || DECLARATION_RE.test(file.filename)) {
+ continue
+ }
+
+ if (TYPE_FILENAME_RE.test(file.filename)) {
+ const typeFilenames = new Set([file.filename.replace(TYPE_FILENAME_RE, '.d.$1ts'), file.filename.replace(TYPE_FILENAME_RE, '.d.ts')])
+ if (app.templates.some(f => f.filename && typeFilenames.has(f.filename))) {
+ continue
+ }
+ }
+
+ declarations += 'declare module ' + JSON.stringify(join('#build', file.filename)) + ';\n'
+ }
+
+ return declarations
+ },
+}
diff --git a/packages/nuxt/src/core/utils/plugins.ts b/packages/nuxt/src/core/utils/plugins.ts
index 54f214b8a3..b8edfe9bd7 100644
--- a/packages/nuxt/src/core/utils/plugins.ts
+++ b/packages/nuxt/src/core/utils/plugins.ts
@@ -34,7 +34,7 @@ export function isVue (id: string, opts: { type?: Array<'template' | 'script' |
return true
}
-const JS_RE = /\.((c|m)?j|t)sx?$/
+const JS_RE = /\.(?:[cm]?j|t)sx?$/
export function isJS (id: string) {
// JavaScript files
diff --git a/packages/nuxt/src/imports/presets.ts b/packages/nuxt/src/imports/presets.ts
index aeb0a7bf62..ba5a513d3c 100644
--- a/packages/nuxt/src/imports/presets.ts
+++ b/packages/nuxt/src/imports/presets.ts
@@ -135,6 +135,7 @@ export const scriptsStubsPreset = {
'useScriptGoogleMaps',
'useScriptNpm',
],
+ priority: -1,
from: '#app/composables/script-stubs',
} satisfies InlinePreset
diff --git a/packages/nuxt/src/pages/module.ts b/packages/nuxt/src/pages/module.ts
index a4f5e9d474..ee5b8c1c79 100644
--- a/packages/nuxt/src/pages/module.ts
+++ b/packages/nuxt/src/pages/module.ts
@@ -422,11 +422,6 @@ export default defineNuxtModule({
getContents: () => 'export { START_LOCATION, useRoute } from \'vue-router\'',
})
- // Optimize vue-router to ensure we share the same injection symbol
- nuxt.options.vite.optimizeDeps = nuxt.options.vite.optimizeDeps || {}
- nuxt.options.vite.optimizeDeps.include = nuxt.options.vite.optimizeDeps.include || []
- nuxt.options.vite.optimizeDeps.include.push('vue-router')
-
nuxt.options.vite.resolve = nuxt.options.vite.resolve || {}
nuxt.options.vite.resolve.dedupe = nuxt.options.vite.resolve.dedupe || []
nuxt.options.vite.resolve.dedupe.push('vue-router')
diff --git a/packages/nuxt/src/pages/plugins/route-injection.ts b/packages/nuxt/src/pages/plugins/route-injection.ts
index 257f104e19..41d235b2da 100644
--- a/packages/nuxt/src/pages/plugins/route-injection.ts
+++ b/packages/nuxt/src/pages/plugins/route-injection.ts
@@ -1,10 +1,13 @@
import { createUnplugin } from 'unplugin'
import MagicString from 'magic-string'
import type { Nuxt } from '@nuxt/schema'
+import { stripLiteral } from 'strip-literal'
import { isVue } from '../../core/utils'
-const INJECTION_RE = /\b_ctx\.\$route\b/g
-const INJECTION_SINGLE_RE = /\b_ctx\.\$route\b/
+const INJECTION_RE_TEMPLATE = /\b_ctx\.\$route\b/g
+const INJECTION_RE_SCRIPT = /\bthis\.\$route\b/g
+
+const INJECTION_SINGLE_RE = /\bthis\.\$route\b|\b_ctx\.\$route\b/
export const RouteInjectionPlugin = (nuxt: Nuxt) => createUnplugin(() => {
return {
@@ -14,14 +17,30 @@ export const RouteInjectionPlugin = (nuxt: Nuxt) => createUnplugin(() => {
return isVue(id, { type: ['template', 'script'] })
},
transform (code) {
- if (!INJECTION_SINGLE_RE.test(code) || code.includes('_ctx._.provides[__nuxt_route_symbol')) { return }
+ if (!INJECTION_SINGLE_RE.test(code) || code.includes('_ctx._.provides[__nuxt_route_symbol') || code.includes('this._.provides[__nuxt_route_symbol')) { return }
let replaced = false
const s = new MagicString(code)
- s.replace(INJECTION_RE, () => {
- replaced = true
- return '(_ctx._.provides[__nuxt_route_symbol] || _ctx.$route)'
- })
+ const strippedCode = stripLiteral(code)
+
+ // Local helper function for regex-based replacements using `strippedCode`
+ const replaceMatches = (regExp: RegExp, replacement: string) => {
+ for (const match of strippedCode.matchAll(regExp)) {
+ const start = match.index!
+ const end = start + match[0].length
+ s.overwrite(start, end, replacement)
+ if (!replaced) {
+ replaced = true
+ }
+ }
+ }
+
+ // handles `$route` in template
+ replaceMatches(INJECTION_RE_TEMPLATE, '(_ctx._.provides[__nuxt_route_symbol] || _ctx.$route)')
+
+ // handles `this.$route` in script
+ replaceMatches(INJECTION_RE_SCRIPT, '(this._.provides[__nuxt_route_symbol] || this.$route)')
+
if (replaced) {
s.prepend('import { PageRouteSymbol as __nuxt_route_symbol } from \'#app/components/injections\';\n')
}
diff --git a/packages/nuxt/src/pages/runtime/plugins/prerender.server.ts b/packages/nuxt/src/pages/runtime/plugins/prerender.server.ts
index 31467d17b0..a8cf539151 100644
--- a/packages/nuxt/src/pages/runtime/plugins/prerender.server.ts
+++ b/packages/nuxt/src/pages/runtime/plugins/prerender.server.ts
@@ -23,7 +23,7 @@ export default defineNuxtPlugin(async () => {
// Implementation
-const OPTIONAL_PARAM_RE = /^\/?:.*(\?|\(\.\*\)\*)$/
+const OPTIONAL_PARAM_RE = /^\/?:.*(?:\?|\(\.\*\)\*)$/
function processRoutes (routes: RouteRecordRaw[], currentPath = '/', routesToPrerender = new Set()) {
for (const route of routes) {
diff --git a/packages/nuxt/src/pages/utils.ts b/packages/nuxt/src/pages/utils.ts
index dd0dbf8bfb..9bd65069fe 100644
--- a/packages/nuxt/src/pages/utils.ts
+++ b/packages/nuxt/src/pages/utils.ts
@@ -135,7 +135,7 @@ export async function generateRoutesFromFiles (files: ScannedFile[], options: Ge
return prepareRoutes(routes)
}
-const SFC_SCRIPT_RE = /
+ `
+
+ const res = compileScript(parse(sfc).descriptor, { id: 'test.vue' })
+ const transformResult = await transform(res.content)
+ expect(transformResult).toMatchInlineSnapshot(`
+ "import { PageRouteSymbol as __nuxt_route_symbol } from '#app/components/injections';
+
+ export default {
+ computed: {
+ thing () {
+ return (this._.provides[__nuxt_route_symbol] || this.$route).path
+ }
+ }
+ }
+ "
+ `)
+ })
+})
diff --git a/packages/nuxt/test/treeshake-client.test.ts b/packages/nuxt/test/treeshake-client.test.ts
index 9306bec381..e0b076d950 100644
--- a/packages/nuxt/test/treeshake-client.test.ts
+++ b/packages/nuxt/test/treeshake-client.test.ts
@@ -182,7 +182,7 @@ describe('treeshake client only in ssr', () => {
expect(treeshaken).not.toContain('ssrRenderComponent(_unref(HelloWorld')
expect(treeshaken).toContain('ssrRenderComponent(_unref(Glob')
}
- expect(treeshaken.replace(/data-v-[\d\w]{8}/g, 'data-v-one-hash').replace(/scoped=[\d\w]{8}/g, 'scoped=one-hash')).toMatchSnapshot()
+ expect(treeshaken.replace(/data-v-\w{8}/g, 'data-v-one-hash').replace(/scoped=\w{8}/g, 'scoped=one-hash')).toMatchSnapshot()
})
}
diff --git a/packages/schema/package.json b/packages/schema/package.json
index 302afdfb4e..df4e1fa7af 100644
--- a/packages/schema/package.json
+++ b/packages/schema/package.json
@@ -35,16 +35,16 @@
},
"devDependencies": {
"@nuxt/telemetry": "2.5.4",
- "@nuxt/ui-templates": "1.3.3",
+ "@nuxt/ui-templates": "1.3.4",
"@types/file-loader": "5.0.4",
"@types/pug": "2.0.10",
"@types/sass-loader": "8.0.8",
- "@unhead/schema": "1.9.10",
+ "@unhead/schema": "1.9.11",
"@vitejs/plugin-vue": "5.0.4",
"@vitejs/plugin-vue-jsx": "3.1.0",
"@vue/compiler-core": "3.4.27",
"@vue/compiler-sfc": "3.4.27",
- "@vue/language-core": "2.0.16",
+ "@vue/language-core": "2.0.19",
"c12": "1.10.0",
"esbuild-loader": "4.1.0",
"h3": "1.11.1",
@@ -54,7 +54,7 @@
"unbuild": "latest",
"unctx": "2.3.1",
"unenv": "1.9.0",
- "vite": "5.2.11",
+ "vite": "5.2.12",
"vue": "3.4.27",
"vue-bundle-renderer": "2.1.0",
"vue-loader": "17.4.2",
@@ -71,7 +71,7 @@
"scule": "^1.3.0",
"std-env": "^3.7.0",
"ufo": "^1.5.3",
- "unimport": "^3.7.1",
+ "unimport": "^3.7.2",
"uncrypto": "^0.1.3",
"untyped": "^1.4.2"
},
diff --git a/packages/schema/src/config/app.ts b/packages/schema/src/config/app.ts
index f9ad11e8c7..ad4026c157 100644
--- a/packages/schema/src/config/app.ts
+++ b/packages/schema/src/config/app.ts
@@ -45,7 +45,17 @@ export default defineUntypedSchema({
/**
* The base path of your Nuxt application.
*
- * This can be set at runtime by setting the NUXT_APP_BASE_URL environment variable.
+ * For example:
+ * @example
+ * ```ts
+ * export default defineNuxtConfig({
+ * app: {
+ * baseURL: '/prefix/'
+ * }
+ * })
+ * ```
+ *
+ * This can also be set at runtime by setting the NUXT_APP_BASE_URL environment variable.
* @example
* ```bash
* NUXT_APP_BASE_URL=/prefix/ node .output/server/index.mjs
@@ -63,6 +73,16 @@ export default defineUntypedSchema({
/**
* An absolute URL to serve the public folder from (production-only).
*
+ * For example:
+ * @example
+ * ```ts
+ * export default defineNuxtConfig({
+ * app: {
+ * cdnURL: 'https://mycdn.org/'
+ * }
+ * })
+ * ```
+ *
* This can be set to a different value at runtime by setting the `NUXT_APP_CDN_URL` environment variable.
* @example
* ```bash
@@ -241,6 +261,7 @@ export default defineUntypedSchema({
/**
* Boolean or a path to an HTML file with the contents of which will be inserted into any HTML page
* rendered with `ssr: false`.
+ *
* - If it is unset, it will use `~/app/spa-loading-template.html` file in one of your layers, if it exists.
* - If it is false, no SPA loading indicator will be loaded.
* - If true, Nuxt will look for `~/app/spa-loading-template.html` file in one of your layers, or a
diff --git a/packages/schema/src/config/common.ts b/packages/schema/src/config/common.ts
index 42191feace..944bd5f2c2 100644
--- a/packages/schema/src/config/common.ts
+++ b/packages/schema/src/config/common.ts
@@ -154,11 +154,23 @@ export default defineUntypedSchema({
$resolve: async (val: string | undefined, get): Promise => resolve(await get('rootDir') as string, val || '.nuxt'),
},
+ /**
+ * For multi-app projects, the unique name of the Nuxt application.
+ */
+ appId: {
+ $resolve: (val: string) => val ?? 'nuxt-app',
+ },
+
/**
* A unique identifier matching the build. This may contain the hash of the current state of the project.
*/
buildId: {
- $resolve: (val: string) => val ?? randomUUID(),
+ $resolve: async (val: string | undefined, get): Promise => {
+ if (typeof val === 'string') { return val }
+
+ const [isDev, isTest] = await Promise.all([get('dev') as Promise, get('test') as Promise])
+ return isDev ? 'dev' : isTest ? 'test' : randomUUID()
+ },
},
/**
@@ -236,7 +248,8 @@ export default defineUntypedSchema({
*
* Nuxt tries to resolve each item in the modules array using node require path
* (in `node_modules`) and then will be resolved from project `srcDir` if `~` alias is used.
- * @note Modules are executed sequentially so the order is important.
+ * @note Modules are executed sequentially so the order is important. First, the modules defined in `nuxt.config.ts` are loaded. Then, modules found in the `modules/`
+ * directory are executed, and they load in alphabetical order.
* @example
* ```js
* modules: [
@@ -527,11 +540,12 @@ export default defineUntypedSchema({
*/
runtimeConfig: {
$resolve: async (val: RuntimeConfig, get): Promise> => {
- const app = await get('app') as Record
+ const [app, buildId] = await Promise.all([get('app') as Promise>, get('buildId') as Promise])
provideFallbackValues(val)
return defu(val, {
public: {},
app: {
+ buildId,
baseURL: app.baseURL,
buildAssetsDir: app.buildAssetsDir,
cdnURL: app.cdnURL,
diff --git a/packages/schema/src/config/experimental.ts b/packages/schema/src/config/experimental.ts
index b50629cbab..f429a873e8 100644
--- a/packages/schema/src/config/experimental.ts
+++ b/packages/schema/src/config/experimental.ts
@@ -29,6 +29,7 @@ export default defineUntypedSchema({
* compileTemplate: true,
* templateUtils: true,
* relativeWatchPaths: true,
+ * resetAsyncDataToUndefined: true,
* defaults: {
* useAsyncData: {
* deep: true
@@ -45,6 +46,11 @@ export default defineUntypedSchema({
* @type {3 | 4}
*/
compatibilityVersion: 3,
+ /**
+ * This enables early access to the experimental multi-app support.
+ * @see [Nuxt Issue #21635](https://github.com/nuxt/nuxt/issues/21635)
+ */
+ multiApp: false,
/**
* This enables 'Bundler' module resolution mode for TypeScript, which is the recommended setting
* for frameworks like Nuxt and Vite.
@@ -136,8 +142,18 @@ export default defineUntypedSchema({
/**
* Tree shakes contents of client-only components from server bundle.
* @see [Nuxt PR #5750](https://github.com/nuxt/framework/pull/5750)
+ * @deprecated This option will no longer be configurable in Nuxt v4
*/
- treeshakeClientOnly: true,
+ treeshakeClientOnly: {
+ async $resolve (val, get) {
+ const isV4 = ((await get('future') as Record).compatibilityVersion === 4)
+ if (isV4 && val === false) {
+ console.warn('Enabling `experimental.treeshakeClientOnly` in v4 compatibility mode as it will no longer be configurable in Nuxt v4.')
+ return true
+ }
+ return val ?? true
+ },
+ },
/**
* Emit `app:chunkError` hook when there is an error loading vite/webpack
@@ -246,19 +262,51 @@ export default defineUntypedSchema({
/**
* Config schema support
* @see [Nuxt Issue #15592](https://github.com/nuxt/nuxt/issues/15592)
+ * @deprecated This option will no longer be configurable in Nuxt v4
*/
- configSchema: true,
+ configSchema: {
+ async $resolve (val, get) {
+ const isV4 = ((await get('future') as Record).compatibilityVersion === 4)
+ if (isV4 && val === false) {
+ console.warn('Enabling `experimental.configSchema` in v4 compatibility mode as it will no longer be configurable in Nuxt v4.')
+ return true
+ }
+ return val ?? true
+ },
+ },
/**
* Whether or not to add a compatibility layer for modules, plugins or user code relying on the old
* `@vueuse/head` API.
*
- * This can be disabled for most Nuxt sites to reduce the client-side bundle by ~0.5kb.
+ * This is disabled to reduce the client-side bundle by ~0.5kb.
+ * @deprecated This feature will be removed in Nuxt v4.
*/
- polyfillVueUseHead: false,
+ polyfillVueUseHead: {
+ async $resolve (val, get) {
+ const isV4 = ((await get('future') as Record).compatibilityVersion === 4)
+ if (isV4 && val === true) {
+ console.warn('Disabling `experimental.polyfillVueUseHead` in v4 compatibility mode as it will no longer be configurable in Nuxt v4.')
+ return false
+ }
+ return val ?? false
+ },
+ },
- /** Allow disabling Nuxt SSR responses by setting the `x-nuxt-no-ssr` header. */
- respectNoSSRHeader: false,
+ /**
+ * Allow disabling Nuxt SSR responses by setting the `x-nuxt-no-ssr` header.
+ * @deprecated This feature will be removed in Nuxt v4.
+ */
+ respectNoSSRHeader: {
+ async $resolve (val, get) {
+ const isV4 = ((await get('future') as Record).compatibilityVersion === 4)
+ if (isV4 && val === true) {
+ console.warn('Disabling `experimental.respectNoSSRHeader` in v4 compatibility mode as it will no longer be configurable in Nuxt v4.')
+ return false
+ }
+ return val ?? false
+ },
+ },
/** Resolve `~`, `~~`, `@` and `@@` aliases located within layers with respect to their layer source and root directories. */
localLayerAliases: true,
@@ -295,8 +343,10 @@ export default defineUntypedSchema({
/**
* Use new experimental head optimisations:
+ *
* - Add the capo.js head plugin in order to render tags in of the head in a more performant way.
* - Uses the hash hydration plugin to reduce initial hydration
+ *
* @see [Nuxt Discussion #22632](https://github.com/nuxt/nuxt/discussions/22632]
*/
headNext: true,
@@ -345,7 +395,11 @@ export default defineUntypedSchema({
* })
* ```
*/
- sharedPrerenderData: false,
+ sharedPrerenderData: {
+ async $resolve (val, get) {
+ return val ?? ((await get('future') as Record).compatibilityVersion === 4)
+ },
+ },
/**
* Enables CookieStore support to listen for cookie updates (if supported by the browser) and refresh `useCookie` ref values.
@@ -368,6 +422,18 @@ export default defineUntypedSchema({
* Options that apply to `useAsyncData` (and also therefore `useFetch`)
*/
useAsyncData: {
+ /** @type {'undefined' | 'null'} */
+ value: {
+ async $resolve (val, get) {
+ return val ?? ((await get('future') as Record).compatibilityVersion === 4 ? 'undefined' : 'null')
+ },
+ },
+ /** @type {'undefined' | 'null'} */
+ errorValue: {
+ async $resolve (val, get) {
+ return val ?? ((await get('future') as Record).compatibilityVersion === 4 ? 'undefined' : 'null')
+ },
+ },
deep: {
async $resolve (val, get) {
return val ?? !((await get('future') as Record).compatibilityVersion === 4)
@@ -429,5 +495,15 @@ export default defineUntypedSchema({
return val ?? ((await get('future') as Record).compatibilityVersion !== 4)
},
},
+
+ /**
+ * Whether `clear` and `clearNuxtData` should reset async data to its _default_ value or update
+ * it to `null`/`undefined`.
+ */
+ resetAsyncDataToUndefined: {
+ async $resolve (val, get) {
+ return val ?? ((await get('future') as Record).compatibilityVersion !== 4)
+ },
+ },
},
})
diff --git a/packages/schema/src/config/nitro.ts b/packages/schema/src/config/nitro.ts
index 5c72ba750f..f937bb2f20 100644
--- a/packages/schema/src/config/nitro.ts
+++ b/packages/schema/src/config/nitro.ts
@@ -46,11 +46,13 @@ export default defineUntypedSchema({
* Nitro server handlers.
*
* Each handler accepts the following options:
+ *
* - handler: The path to the file defining the handler.
* - route: The route under which the handler is available. This follows the conventions of https://github.com/unjs/radix3.
* - method: The HTTP method of requests that should be handled.
* - middleware: Specifies whether it is a middleware handler.
* - lazy: Specifies whether to use lazy loading to import the handler.
+ *
* @see https://nuxt.com/docs/guide/directory-structure/server
* @note Files from `server/api`, `server/middleware` and `server/routes` will be automatically registered by Nuxt.
* @example
diff --git a/packages/schema/src/config/typescript.ts b/packages/schema/src/config/typescript.ts
index d8e019e4ee..ccb8b54cbc 100644
--- a/packages/schema/src/config/typescript.ts
+++ b/packages/schema/src/config/typescript.ts
@@ -41,6 +41,7 @@ export default defineUntypedSchema({
'ofetch',
// Key nuxt dependencies
'@unhead/vue',
+ '@nuxt/devtools',
'vue',
'@vue/runtime-core',
'@vue/compiler-sfc',
diff --git a/packages/schema/src/config/webpack.ts b/packages/schema/src/config/webpack.ts
index 5ee21abc14..ad9787aea5 100644
--- a/packages/schema/src/config/webpack.ts
+++ b/packages/schema/src/config/webpack.ts
@@ -157,7 +157,11 @@ export default defineUntypedSchema({
* See https://github.com/esbuild-kit/esbuild-loader
* @type {Omit}
*/
- esbuild: {},
+ esbuild: {
+ jsxFactory: 'h',
+ jsxFragment: 'Fragment',
+ tsconfigRaw: '{}',
+ },
/**
* See: https://github.com/webpack-contrib/file-loader#options
diff --git a/packages/ui-templates/lib/dev.ts b/packages/ui-templates/lib/dev.ts
index a9d6ff55a1..00a307c2fc 100644
--- a/packages/ui-templates/lib/dev.ts
+++ b/packages/ui-templates/lib/dev.ts
@@ -23,7 +23,7 @@ export const DevRenderingPlugin = () => {
const messages = JSON.parse(await fsp.readFile(r(page, 'messages.json'), 'utf-8'))
return template(contents, {
- interpolate: /{{{?([\s\S]+?)}?}}/g,
+ interpolate: /\{\{\{?([\s\S]+?)\}?\}\}/g,
})({
messages: { ...genericMessages, ...messages },
})
diff --git a/packages/ui-templates/lib/render.ts b/packages/ui-templates/lib/render.ts
index 38ea09e2a6..34ba7c189c 100644
--- a/packages/ui-templates/lib/render.ts
+++ b/packages/ui-templates/lib/render.ts
@@ -58,7 +58,7 @@ export const RenderPlugin = () => {
}
// Inline our scripts
- const scriptSources = Array.from(html.matchAll(/`)),
html.match(new RegExp(``)),
]
@@ -662,7 +670,7 @@ describe('nuxt composables', () => {
describe('rich payloads', () => {
it('correctly serializes and revivifies complex types', async () => {
- const html = await $fetch('/json-payload')
+ const html = await $fetch('/json-payload')
for (const test of [
'Date: true',
'BigInt: true',
@@ -682,7 +690,7 @@ describe('rich payloads', () => {
describe('nuxt links', () => {
it('handles trailing slashes', async () => {
- const html = await $fetch('/nuxt-link/trailing-slash')
+ const html = await $fetch('/nuxt-link/trailing-slash')
const data: Record = {}
for (const selector of ['nuxt-link', 'router-link', 'link-with-trailing-slash', 'link-without-trailing-slash']) {
data[selector] = []
@@ -800,7 +808,7 @@ describe('nuxt links', () => {
})
it('useLink works', async () => {
- const html = await $fetch('/nuxt-link/use-link')
+ const html = await $fetch('/nuxt-link/use-link')
expect(html).toContain('