feat: 위키 저장소 초기 커밋
- CLAUDE.md 운영 규칙 - wiki/ 정리된 지식 페이지 (Nuxt + Claude Code) - raw/ 원본 자료 - reference/ Nuxt 4.x 공식 문서 Co-authored-by: Cursor <cursoragent@cursor.com>
This commit is contained in:
5
reference/2.directory-structure/1.app/.navigation.yml
Normal file
5
reference/2.directory-structure/1.app/.navigation.yml
Normal file
@@ -0,0 +1,5 @@
|
||||
title: app
|
||||
titleTemplate: '%s · Nuxt Directory Structure'
|
||||
head.title: "app/"
|
||||
defaultOpen: true
|
||||
icon: i-vscode-icons-folder-type-app
|
||||
16
reference/2.directory-structure/1.app/1.assets.md
Normal file
16
reference/2.directory-structure/1.app/1.assets.md
Normal file
@@ -0,0 +1,16 @@
|
||||
---
|
||||
title: "assets"
|
||||
description: "The assets/ directory is used to add all the website's assets that the build tool will process."
|
||||
head.title: "assets/"
|
||||
navigation.icon: i-vscode-icons-folder-type-asset
|
||||
---
|
||||
|
||||
The directory usually contains the following types of files:
|
||||
|
||||
- Stylesheets (CSS, SASS, etc.)
|
||||
- Fonts
|
||||
- Images that won't be served from the [`public/`](/docs/4.x/directory-structure/public) directory.
|
||||
|
||||
If you want to serve assets from the server, we recommend taking a look at the [`public/`](/docs/4.x/directory-structure/public) directory.
|
||||
|
||||
:read-more{to="/docs/4.x/getting-started/assets"}
|
||||
639
reference/2.directory-structure/1.app/1.components.md
Normal file
639
reference/2.directory-structure/1.app/1.components.md
Normal file
@@ -0,0 +1,639 @@
|
||||
---
|
||||
title: "components"
|
||||
head.title: "components/"
|
||||
description: "The components/ directory is where you put all your Vue components."
|
||||
navigation.icon: i-vscode-icons-folder-type-component
|
||||
---
|
||||
|
||||
Nuxt automatically imports any components in this directory (along with components that are registered by any modules you may be using).
|
||||
|
||||
```bash [Directory Structure]
|
||||
-| components/
|
||||
---| AppHeader.vue
|
||||
---| AppFooter.vue
|
||||
```
|
||||
|
||||
```html [app/app.vue]
|
||||
<template>
|
||||
<div>
|
||||
<AppHeader />
|
||||
<NuxtPage />
|
||||
<AppFooter />
|
||||
</div>
|
||||
</template>
|
||||
```
|
||||
|
||||
## Component Names
|
||||
|
||||
If you have a component in nested directories such as:
|
||||
|
||||
```bash [Directory Structure]
|
||||
-| components/
|
||||
---| base/
|
||||
-----| foo/
|
||||
-------| Button.vue
|
||||
```
|
||||
|
||||
... then the component's name will be based on its own path directory and filename, with duplicate segments being removed. Therefore, the component's name will be:
|
||||
|
||||
```html
|
||||
<BaseFooButton />
|
||||
```
|
||||
|
||||
::note
|
||||
For clarity, we recommend that the component's filename matches its name. So, in the example above, you could rename `Button.vue` to be `BaseFooButton.vue`.
|
||||
::
|
||||
|
||||
If you want to auto-import components based only on its name, not path, then you need to set `pathPrefix` option to `false` using extended form of the configuration object:
|
||||
|
||||
```ts twoslash [nuxt.config.ts]
|
||||
export default defineNuxtConfig({
|
||||
components: [
|
||||
{
|
||||
path: '~/components',
|
||||
pathPrefix: false, // [!code ++]
|
||||
},
|
||||
],
|
||||
})
|
||||
```
|
||||
|
||||
This registers the components using the same strategy as used in Nuxt 2. For example, `~/components/Some/MyComponent.vue` will be usable as `<MyComponent>` and not `<SomeMyComponent>`.
|
||||
|
||||
## Dynamic Components
|
||||
|
||||
If you want to use the Vue `<component :is="someComputedComponent">`{lang=vue} syntax, you need to use the `resolveComponent` helper provided by Vue or import the component directly from `#components` and pass it into `is` prop.
|
||||
|
||||
For example:
|
||||
|
||||
```vue [app/pages/index.vue]
|
||||
<script setup lang="ts">
|
||||
import { SomeComponent } from '#components'
|
||||
|
||||
const MyButton = resolveComponent('MyButton')
|
||||
</script>
|
||||
|
||||
<template>
|
||||
<component :is="clickable ? MyButton : 'div'" />
|
||||
<component :is="SomeComponent" />
|
||||
</template>
|
||||
```
|
||||
|
||||
::important
|
||||
If you are using `resolveComponent` to handle dynamic components, make sure not to insert anything but the name of the component, which must be a literal string and not be or contain a variable. The string is statically analyzed at the compilation step.
|
||||
::
|
||||
|
||||
:video-accordion{title="Watch Daniel Roe's short video about resolveComponent()" videoId="4kq8E5IUM2U"}
|
||||
|
||||
Alternatively, though not recommended, you can register all your components globally, which will create async chunks for all your components and make them available throughout your application.
|
||||
|
||||
```diff
|
||||
export default defineNuxtConfig({
|
||||
components: {
|
||||
+ global: true,
|
||||
+ dirs: ['~/components']
|
||||
},
|
||||
})
|
||||
```
|
||||
|
||||
You can also selectively register some components globally by placing them in a `~/components/global` directory, or by using a `.global.vue` suffix in the filename. As noted above, each global component is rendered in a separate chunk, so be careful not to overuse this feature.
|
||||
|
||||
::note
|
||||
The `global` option can also be set per component directory.
|
||||
::
|
||||
|
||||
## Dynamic Imports
|
||||
|
||||
To dynamically import a component (also known as lazy-loading a component) all you need to do is add the `Lazy` prefix to the component's name. This is particularly useful if the component is not always needed.
|
||||
|
||||
By using the `Lazy` prefix you can delay loading the component code until the right moment, which can be helpful for optimizing your JavaScript bundle size.
|
||||
|
||||
```vue [app/pages/index.vue]
|
||||
<script setup lang="ts">
|
||||
const show = ref(false)
|
||||
</script>
|
||||
|
||||
<template>
|
||||
<div>
|
||||
<h1>Mountains</h1>
|
||||
<LazyMountainsList v-if="show" />
|
||||
<button
|
||||
v-if="!show"
|
||||
@click="show = true"
|
||||
>
|
||||
Show List
|
||||
</button>
|
||||
</div>
|
||||
</template>
|
||||
```
|
||||
|
||||
## Delayed (or Lazy) Hydration
|
||||
|
||||
Lazy components are great for controlling the chunk sizes in your app, but they don't always enhance runtime performance, as they still load eagerly unless conditionally rendered. In real-world applications, some pages may include a lot of content and a lot of components, and most of the time not all of them need to be interactive as soon as the page is loaded. Having them all load eagerly can negatively impact performance.
|
||||
|
||||
In order to optimize your app, you may want to delay the hydration of some components until they're visible, or until the browser is done with more important tasks.
|
||||
|
||||
Nuxt supports this using lazy (or delayed) hydration, allowing you to control when components become interactive.
|
||||
|
||||
### Hydration Strategies
|
||||
|
||||
Nuxt provides a range of built-in hydration strategies. Only one strategy can be used per lazy component.
|
||||
|
||||
::note
|
||||
Any prop change on a lazily hydrated component will trigger hydration immediately. (e.g., changing a prop on a component with `hydrate-never` will cause it to hydrate)
|
||||
::
|
||||
|
||||
::warning
|
||||
Currently Nuxt's built-in lazy hydration only works in single-file components (SFCs), and requires you to define the prop in the template (rather than spreading an object of props via `v-bind`). It also does not work with direct imports from `#components`.
|
||||
::
|
||||
|
||||
#### `hydrate-on-visible`
|
||||
|
||||
Hydrates the component when it becomes visible in the viewport.
|
||||
|
||||
```vue [app/pages/index.vue]
|
||||
<template>
|
||||
<div>
|
||||
<LazyMyComponent hydrate-on-visible />
|
||||
</div>
|
||||
</template>
|
||||
```
|
||||
|
||||
::read-more{to="https://developer.mozilla.org/en-US/docs/Web/API/IntersectionObserver/IntersectionObserver" title="IntersectionObserver options"}
|
||||
Read more about the options for `hydrate-on-visible`.
|
||||
::
|
||||
|
||||
::note
|
||||
Under the hood, this uses Vue's built-in [`hydrateOnVisible` strategy](https://vuejs.org/guide/components/async#hydrate-on-visible).
|
||||
::
|
||||
|
||||
#### `hydrate-on-idle`
|
||||
|
||||
Hydrates the component when the browser is idle. This is suitable if you need the component to load as soon as possible, but not block the critical rendering path.
|
||||
|
||||
You can also pass a number which serves as a max timeout.
|
||||
|
||||
```vue [app/pages/index.vue]
|
||||
<template>
|
||||
<div>
|
||||
<LazyMyComponent hydrate-on-idle />
|
||||
</div>
|
||||
</template>
|
||||
```
|
||||
|
||||
::note
|
||||
Under the hood, this uses Vue's built-in [`hydrateOnIdle` strategy](https://vuejs.org/guide/components/async#hydrate-on-idle).
|
||||
::
|
||||
|
||||
#### `hydrate-on-interaction`
|
||||
|
||||
Hydrates the component after a specified interaction (e.g., click, mouseover).
|
||||
|
||||
```vue [app/pages/index.vue]
|
||||
<template>
|
||||
<div>
|
||||
<LazyMyComponent hydrate-on-interaction="mouseover" />
|
||||
</div>
|
||||
</template>
|
||||
```
|
||||
|
||||
If you do not pass an event or list of events, it defaults to hydrating on `pointerenter`, `click` and `focus`.
|
||||
|
||||
::note
|
||||
Under the hood, this uses Vue's built-in [`hydrateOnInteraction` strategy](https://vuejs.org/guide/components/async#hydrate-on-interaction).
|
||||
::
|
||||
|
||||
#### `hydrate-on-media-query`
|
||||
|
||||
Hydrates the component when the window matches a media query.
|
||||
|
||||
```vue [app/pages/index.vue]
|
||||
<template>
|
||||
<div>
|
||||
<LazyMyComponent hydrate-on-media-query="(max-width: 768px)" />
|
||||
</div>
|
||||
</template>
|
||||
```
|
||||
|
||||
::note
|
||||
Under the hood, this uses Vue's built-in [`hydrateOnMediaQuery` strategy](https://vuejs.org/guide/components/async#hydrate-on-media-query).
|
||||
::
|
||||
|
||||
#### `hydrate-after`
|
||||
|
||||
Hydrates the component after a specified delay (in milliseconds).
|
||||
|
||||
```vue [app/pages/index.vue]
|
||||
<template>
|
||||
<div>
|
||||
<LazyMyComponent :hydrate-after="2000" />
|
||||
</div>
|
||||
</template>
|
||||
```
|
||||
|
||||
#### `hydrate-when`
|
||||
|
||||
Hydrates the component based on a boolean condition.
|
||||
|
||||
```vue [app/pages/index.vue]
|
||||
<template>
|
||||
<div>
|
||||
<LazyMyComponent :hydrate-when="isReady" />
|
||||
</div>
|
||||
</template>
|
||||
|
||||
<script setup lang="ts">
|
||||
const isReady = ref(false)
|
||||
function myFunction () {
|
||||
// trigger custom hydration strategy...
|
||||
isReady.value = true
|
||||
}
|
||||
</script>
|
||||
```
|
||||
|
||||
#### `hydrate-never`
|
||||
|
||||
Never hydrates the component.
|
||||
|
||||
```vue [app/pages/index.vue]
|
||||
<template>
|
||||
<div>
|
||||
<LazyMyComponent hydrate-never />
|
||||
</div>
|
||||
</template>
|
||||
```
|
||||
|
||||
### Listening to Hydration Events
|
||||
|
||||
All delayed hydration components emit a `@hydrated` event when they are hydrated.
|
||||
|
||||
```vue [app/pages/index.vue]
|
||||
<template>
|
||||
<div>
|
||||
<LazyMyComponent
|
||||
hydrate-on-visible
|
||||
@hydrated="onHydrate"
|
||||
/>
|
||||
</div>
|
||||
</template>
|
||||
|
||||
<script setup lang="ts">
|
||||
function onHydrate () {
|
||||
console.log('Component has been hydrated!')
|
||||
}
|
||||
</script>
|
||||
```
|
||||
|
||||
### Caveats and Best Practices
|
||||
|
||||
Delayed hydration can offer performance benefits, but it's essential to use it correctly:
|
||||
|
||||
1. **Prioritize In-Viewport Content:** Avoid delayed hydration for critical, above-the-fold content. It's best suited for content that isn't immediately needed.
|
||||
|
||||
2. **Conditional Rendering:** When using `v-if="false"` on a lazy component, you might not need delayed hydration. You can just use a normal lazy component.
|
||||
|
||||
3. **Shared State:** Be mindful of shared state (`v-model`) across multiple components. Updating the model in one component can trigger hydration in all components bound to that model.
|
||||
|
||||
4. **Use Each Strategy's Intended Use Case:** Each strategy is optimized for a specific purpose.
|
||||
* `hydrate-when` is best for components that might not always need to be hydrated.
|
||||
* `hydrate-after` is for components that can wait a specific amount of time.
|
||||
* `hydrate-on-idle` is for components that can be hydrated when the browser is idle.
|
||||
|
||||
5. **Avoid `hydrate-never` on interactive components:** If a component requires user interaction, it should not be set to never hydrate.
|
||||
|
||||
## Direct Imports
|
||||
|
||||
You can also explicitly import components from `#components` if you want or need to bypass Nuxt's auto-importing functionality.
|
||||
|
||||
```vue [app/pages/index.vue]
|
||||
<script setup lang="ts">
|
||||
import { LazyMountainsList, NuxtLink } from '#components'
|
||||
|
||||
const show = ref(false)
|
||||
</script>
|
||||
|
||||
<template>
|
||||
<div>
|
||||
<h1>Mountains</h1>
|
||||
<LazyMountainsList v-if="show" />
|
||||
<button
|
||||
v-if="!show"
|
||||
@click="show = true"
|
||||
>
|
||||
Show List
|
||||
</button>
|
||||
<NuxtLink to="/">Home</NuxtLink>
|
||||
</div>
|
||||
</template>
|
||||
```
|
||||
|
||||
## Custom Directories
|
||||
|
||||
By default, only the `~/components` directory is scanned. If you want to add other directories, or change how the components are scanned within a subfolder of this directory, you can add additional directories to the configuration:
|
||||
|
||||
```ts twoslash [nuxt.config.ts]
|
||||
export default defineNuxtConfig({
|
||||
components: [
|
||||
// ~/calendar-module/components/event/Update.vue => <EventUpdate />
|
||||
{ path: '~/calendar-module/components' },
|
||||
|
||||
// ~/user-module/components/account/UserDeleteDialog.vue => <UserDeleteDialog />
|
||||
{ path: '~/user-module/components', pathPrefix: false },
|
||||
|
||||
// ~/components/special-components/Btn.vue => <SpecialBtn />
|
||||
{ path: '~/components/special-components', prefix: 'Special' },
|
||||
|
||||
// It's important that this comes last if you have overrides you wish to apply
|
||||
// to sub-directories of `~/components`.
|
||||
//
|
||||
// ~/components/Btn.vue => <Btn />
|
||||
// ~/components/base/Btn.vue => <BaseBtn />
|
||||
'~/components',
|
||||
],
|
||||
})
|
||||
```
|
||||
|
||||
::note
|
||||
Any nested directories need to be added first as they are scanned in order.
|
||||
::
|
||||
|
||||
## npm Packages
|
||||
|
||||
If you want to auto-import components from an npm package, you can use [`addComponent`](/docs/4.x/api/kit/components#addcomponent) in a [local module](/docs/4.x/directory-structure/modules) to register them.
|
||||
|
||||
::code-group
|
||||
|
||||
```ts twoslash [~/modules/register-component.ts]
|
||||
import { addComponent, defineNuxtModule } from '@nuxt/kit'
|
||||
|
||||
export default defineNuxtModule({
|
||||
setup () {
|
||||
// import { MyComponent as MyAutoImportedComponent } from 'my-npm-package'
|
||||
addComponent({
|
||||
name: 'MyAutoImportedComponent',
|
||||
export: 'MyComponent',
|
||||
filePath: 'my-npm-package',
|
||||
})
|
||||
},
|
||||
})
|
||||
```
|
||||
|
||||
```vue [app/app.vue]
|
||||
<template>
|
||||
<div>
|
||||
<!-- the component uses the name we specified and is auto-imported -->
|
||||
<MyAutoImportedComponent />
|
||||
</div>
|
||||
</template>
|
||||
```
|
||||
|
||||
::
|
||||
|
||||
## Component Extensions
|
||||
|
||||
By default, any file with an extension specified in the [extensions key of `nuxt.config.ts`](/docs/4.x/api/nuxt-config#extensions) is treated as a component.
|
||||
If you need to restrict the file extensions that should be registered as components, you can use the extended form of the components directory declaration and its `extensions` key:
|
||||
|
||||
```ts twoslash [nuxt.config.ts]
|
||||
export default defineNuxtConfig({
|
||||
components: [
|
||||
{
|
||||
path: '~/components',
|
||||
extensions: ['.vue'], // [!code ++]
|
||||
},
|
||||
],
|
||||
})
|
||||
```
|
||||
|
||||
## Client Components
|
||||
|
||||
If a component is meant to be rendered only client-side, you can add the `.client` suffix to your component.
|
||||
|
||||
```bash [Directory Structure]
|
||||
| components/
|
||||
--| Comments.client.vue
|
||||
```
|
||||
|
||||
```vue [app/pages/example.vue]
|
||||
<template>
|
||||
<div>
|
||||
<!-- this component will only be rendered on client side -->
|
||||
<Comments />
|
||||
</div>
|
||||
</template>
|
||||
```
|
||||
|
||||
::note
|
||||
This feature only works with Nuxt auto-imports and `#components` imports. Explicitly importing these components from their real paths does not convert them into client-only components.
|
||||
::
|
||||
|
||||
::important
|
||||
`.client` components are rendered only after being mounted. To access the rendered template using `onMounted()`, add `await nextTick()` in the callback of the `onMounted()` hook.
|
||||
::
|
||||
|
||||
::read-more{to="/docs/4.x/api/components/client-only"}
|
||||
You can also achieve a similar result with the `<ClientOnly>` component.
|
||||
::
|
||||
|
||||
## Server Components
|
||||
|
||||
Server components allow server-rendering individual components within your client-side apps. It's possible to use server components within Nuxt, even if you are generating a static site. That makes it possible to build complex sites that mix dynamic components, server-rendered HTML and even static chunks of markup.
|
||||
|
||||
Server components can either be used on their own or paired with a [client component](/docs/4.x/directory-structure/app/components#paired-with-a-client-component).
|
||||
|
||||
:video-accordion{title="Watch Learn Vue video about Nuxt Server Components" videoId="u1yyXe86xJM"}
|
||||
|
||||
::tip{icon="i-lucide-newspaper" to="https://roe.dev/blog/nuxt-server-components" target="_blank"}
|
||||
Read Daniel Roe's guide to Nuxt Server Components.
|
||||
::
|
||||
|
||||
### Standalone server components
|
||||
|
||||
Standalone server components will always be rendered on the server, also known as Islands components.
|
||||
|
||||
When their props update, this will result in a network request that will update the rendered HTML in-place.
|
||||
|
||||
Server components are currently experimental and in order to use them, you need to enable the 'component islands' feature in your nuxt.config:
|
||||
|
||||
```ts twoslash [nuxt.config.ts]
|
||||
export default defineNuxtConfig({
|
||||
experimental: {
|
||||
componentIslands: true,
|
||||
},
|
||||
})
|
||||
```
|
||||
|
||||
Now you can register server-only components with the `.server` suffix and use them anywhere in your application automatically.
|
||||
|
||||
```bash [Directory Structure]
|
||||
-| components/
|
||||
---| HighlightedMarkdown.server.vue
|
||||
```
|
||||
|
||||
```vue [app/pages/example.vue]
|
||||
<template>
|
||||
<div>
|
||||
<!--
|
||||
this will automatically be rendered on the server, meaning your markdown parsing + highlighting
|
||||
libraries are not included in your client bundle.
|
||||
-->
|
||||
<HighlightedMarkdown markdown="# Headline" />
|
||||
</div>
|
||||
</template>
|
||||
```
|
||||
|
||||
Server-only components use [`<NuxtIsland>`](/docs/4.x/api/components/nuxt-island) under the hood, meaning that `lazy` prop and `#fallback` slot are both passed down to it.
|
||||
|
||||
::warning
|
||||
Server components (and islands) must have a single root element. (HTML comments are considered elements as well.)
|
||||
::
|
||||
|
||||
::warning
|
||||
Props are passed to server components via URL query parameters, and are therefore limited by the possible length of a URL, so be careful not to pass enormous amounts of data to server components via props.
|
||||
::
|
||||
|
||||
::warning
|
||||
Be careful when nesting islands within other islands as each island adds some extra overhead.
|
||||
::
|
||||
|
||||
::warning
|
||||
Most features for server-only components and island components, such as slots and client components, are only available for single file components.
|
||||
::
|
||||
|
||||
#### Client components within server components
|
||||
|
||||
::note
|
||||
This feature needs `experimental.componentIslands.selectiveClient` within your configuration to be true.
|
||||
::
|
||||
|
||||
You can partially hydrate a component by setting a `nuxt-client` attribute on the component you wish to be loaded client-side.
|
||||
|
||||
```vue [app/components/ServerWithClient.vue]
|
||||
<template>
|
||||
<div>
|
||||
<HighlightedMarkdown markdown="# Headline" />
|
||||
<!-- Counter will be loaded and hydrated client-side -->
|
||||
<Counter
|
||||
nuxt-client
|
||||
:count="5"
|
||||
/>
|
||||
</div>
|
||||
</template>
|
||||
```
|
||||
|
||||
::note
|
||||
This only works within a server component. Slots for client components are working only with `experimental.componentIsland.selectiveClient` set to `'deep'` and since they are rendered server-side, they are not interactive once client-side.
|
||||
::
|
||||
|
||||
#### Server Component Context
|
||||
|
||||
When rendering a server-only or island component, `<NuxtIsland>` makes a fetch request which comes back with a `NuxtIslandResponse`. (This is an internal request if rendered on the server, or a request that you can see in the network tab if it's rendering on client-side navigation.)
|
||||
|
||||
This means:
|
||||
|
||||
* A new Vue app will be created server-side to create the `NuxtIslandResponse`.
|
||||
* A new 'island context' will be created while rendering the component.
|
||||
* You can't access the 'island context' from the rest of your app and you can't access the context of the rest of your app from the island component. In other words, the server component or island is _isolated_ from the rest of your app.
|
||||
* Your plugins will run again when rendering the island, unless they have `env: { islands: false }` set (which you can do in an object-syntax plugin).
|
||||
|
||||
::important
|
||||
Route middleware does not run when rendering island components. Middleware is a routing concept that applies to pages, not components, and is not designed to control component rendering.
|
||||
::
|
||||
|
||||
::important
|
||||
`useRoute()` and other `vue-router` composables do not track the current page route inside a server (island) component. The island is rendered in its own isolated Vue app keyed only on its props (and any explicit context), which is what keeps islands cacheable independently of the page they are rendered on. Inside an island, `useRoute()` will reflect the island's own request, not the page the user is on.
|
||||
|
||||
If an island needs information about the current route, pass it in explicitly — either as props from the parent component, or via the `context` prop on `<NuxtIsland>` (read inside the island from `nuxtApp.ssrContext.islandContext`).
|
||||
::
|
||||
|
||||
Within an island component, you can access its island context through `nuxtApp.ssrContext.islandContext`. Note that while island components are still marked as experimental, the format of this context may change.
|
||||
|
||||
::note
|
||||
Slots can be interactive and are wrapped within a `<div>` with `display: contents;`
|
||||
::
|
||||
|
||||
### Paired with a Client component
|
||||
|
||||
In this case, the `.server` + `.client` components are two 'halves' of a component and can be used in advanced use cases for separate implementations of a component on server and client side.
|
||||
|
||||
```bash [Directory Structure]
|
||||
-| components/
|
||||
---| Comments.client.vue
|
||||
---| Comments.server.vue
|
||||
```
|
||||
|
||||
```vue [app/pages/example.vue]
|
||||
<template>
|
||||
<div>
|
||||
<!-- this component will render Comments.server on the server then Comments.client once mounted in the browser -->
|
||||
<Comments />
|
||||
</div>
|
||||
</template>
|
||||
```
|
||||
|
||||
## Built-In Nuxt Components
|
||||
|
||||
There are a number of components that Nuxt provides, including `<ClientOnly>` and `<DevOnly>`. You can read more about them in the API documentation.
|
||||
|
||||
::read-more{to="/docs/4.x/api"}
|
||||
::
|
||||
|
||||
## Library Authors
|
||||
|
||||
Making Vue component libraries with automatic tree-shaking and component registration is super easy. ✨
|
||||
|
||||
You can use the [`addComponentsDir`](/docs/4.x/api/kit/components#addcomponentsdir) method provided from the `@nuxt/kit` to register your components directory in your Nuxt module.
|
||||
|
||||
Imagine a directory structure like this:
|
||||
|
||||
```bash [Directory Structure]
|
||||
-| node_modules/
|
||||
---| awesome-ui/
|
||||
-----| components/
|
||||
-------| Alert.vue
|
||||
-------| Button.vue
|
||||
-----| nuxt.ts
|
||||
-| pages/
|
||||
---| index.vue
|
||||
-| nuxt.config.ts
|
||||
```
|
||||
|
||||
Then in `awesome-ui/nuxt.ts` you can use the `addComponentsDir` hook:
|
||||
|
||||
```ts twoslash
|
||||
import { addComponentsDir, createResolver, defineNuxtModule } from '@nuxt/kit'
|
||||
|
||||
export default defineNuxtModule({
|
||||
setup () {
|
||||
const resolver = createResolver(import.meta.url)
|
||||
|
||||
// Add ./components dir to the list
|
||||
addComponentsDir({
|
||||
path: resolver.resolve('./components'),
|
||||
prefix: 'awesome',
|
||||
})
|
||||
},
|
||||
})
|
||||
```
|
||||
|
||||
That's it! Now in your project, you can import your UI library as a Nuxt module in your `nuxt.config` file:
|
||||
|
||||
```ts twoslash [nuxt.config.ts]
|
||||
export default defineNuxtConfig({
|
||||
modules: ['awesome-ui/nuxt'],
|
||||
})
|
||||
```
|
||||
|
||||
... and directly use the module components (prefixed with `awesome-`) in our `app/pages/index.vue`:
|
||||
|
||||
```vue
|
||||
<template>
|
||||
<div>
|
||||
My <AwesomeButton>UI button</AwesomeButton>!
|
||||
<awesome-alert>Here's an alert!</awesome-alert>
|
||||
</div>
|
||||
</template>
|
||||
```
|
||||
|
||||
It will automatically import the components only if used and also support HMR when updating your components in `node_modules/awesome-ui/components/`.
|
||||
|
||||
:link-example{to="/docs/4.x/examples/features/auto-imports"}
|
||||
121
reference/2.directory-structure/1.app/1.composables.md
Normal file
121
reference/2.directory-structure/1.app/1.composables.md
Normal file
@@ -0,0 +1,121 @@
|
||||
---
|
||||
title: 'composables'
|
||||
head.title: 'composables/'
|
||||
description: Use the composables/ directory to auto-import your Vue composables into your application.
|
||||
navigation.icon: i-vscode-icons-folder-type-src
|
||||
---
|
||||
|
||||
## Usage
|
||||
|
||||
**Method 1:** Using named export
|
||||
|
||||
```ts [app/composables/useFoo.ts]
|
||||
export const useFoo = () => {
|
||||
return useState('foo', () => 'bar')
|
||||
}
|
||||
```
|
||||
|
||||
**Method 2:** Using default export
|
||||
|
||||
```ts [app/composables/use-foo.ts or composables/useFoo.ts]
|
||||
// It will be available as useFoo() (camelCase of file name without extension)
|
||||
export default function () {
|
||||
return useState('foo', () => 'bar')
|
||||
}
|
||||
```
|
||||
|
||||
**Usage:** You can now use auto imported composable in `.js`, `.ts` and `.vue` files
|
||||
|
||||
```vue [app/app.vue]
|
||||
<script setup lang="ts">
|
||||
const foo = useFoo()
|
||||
</script>
|
||||
|
||||
<template>
|
||||
<div>
|
||||
{{ foo }}
|
||||
</div>
|
||||
</template>
|
||||
```
|
||||
|
||||
::note
|
||||
The `app/composables/` directory in Nuxt does not provide any additional reactivity capabilities to your code. Instead, any reactivity within composables is achieved using Vue's Composition API mechanisms, such as ref and reactive. Note that reactive code is also not limited to the boundaries of the `app/composables/` directory. You are free to employ reactivity features wherever they're needed in your application.
|
||||
::
|
||||
|
||||
:read-more{to="/docs/4.x/guide/concepts/auto-imports"}
|
||||
|
||||
:link-example{to="/docs/4.x/examples/features/auto-imports"}
|
||||
|
||||
## Types
|
||||
|
||||
Under the hood, Nuxt auto generates the file `.nuxt/imports.d.ts` to declare the types.
|
||||
|
||||
Be aware that you have to run [`nuxt prepare`](/docs/4.x/api/commands/prepare), [`nuxt dev`](/docs/4.x/api/commands/dev) or [`nuxt build`](/docs/4.x/api/commands/build) in order to let Nuxt generate the types.
|
||||
|
||||
::note
|
||||
If you create a composable without having the dev server running, TypeScript will throw an error, such as `Cannot find name 'useBar'.`
|
||||
::
|
||||
|
||||
## Examples
|
||||
|
||||
### Nested Composables
|
||||
|
||||
You can use a composable within another composable using auto imports:
|
||||
|
||||
```ts [app/composables/test.ts]
|
||||
export const useFoo = () => {
|
||||
const nuxtApp = useNuxtApp()
|
||||
const bar = useBar()
|
||||
}
|
||||
```
|
||||
|
||||
### Access plugin injections
|
||||
|
||||
You can access [plugin injections](/docs/4.x/directory-structure/app/plugins#providing-helpers) from composables:
|
||||
|
||||
```ts [app/composables/test.ts]
|
||||
export const useHello = () => {
|
||||
const nuxtApp = useNuxtApp()
|
||||
return nuxtApp.$hello
|
||||
}
|
||||
```
|
||||
|
||||
## How Files Are Scanned
|
||||
|
||||
Nuxt only scans files at the top level of the [`app/composables/` directory](/docs/4.x/directory-structure/app/composables), e.g.:
|
||||
|
||||
```bash [Directory Structure]
|
||||
-| composables/
|
||||
---| index.ts // scanned
|
||||
---| useFoo.ts // scanned
|
||||
---| nested/
|
||||
-----| utils.ts // not scanned
|
||||
```
|
||||
|
||||
Only `app/composables/index.ts` and `app/composables/useFoo.ts` would be searched for imports.
|
||||
|
||||
To get auto imports working for nested modules, you could either re-export them (recommended) or configure the scanner to include nested directories:
|
||||
|
||||
**Example:** Re-export the composables you need from the `app/composables/index.ts` file:
|
||||
|
||||
```ts [app/composables/index.ts]
|
||||
// Enables auto import for this export
|
||||
export { utils } from './nested/utils.ts'
|
||||
```
|
||||
|
||||
**Example:** Scan nested directories inside the `app/composables/` folder:
|
||||
|
||||
```ts twoslash [nuxt.config.ts]
|
||||
export default defineNuxtConfig({
|
||||
imports: {
|
||||
dirs: [
|
||||
// Scan top-level composables
|
||||
'~/composables',
|
||||
// ... or scan composables nested one level deep with a specific name and file extension
|
||||
'~/composables/*/index.{ts,js,mjs,mts}',
|
||||
// ... or scan all composables within given directory
|
||||
'~/composables/**',
|
||||
],
|
||||
},
|
||||
})
|
||||
```
|
||||
275
reference/2.directory-structure/1.app/1.layouts.md
Normal file
275
reference/2.directory-structure/1.app/1.layouts.md
Normal file
@@ -0,0 +1,275 @@
|
||||
---
|
||||
title: "layouts"
|
||||
head.title: "layouts/"
|
||||
description: "Nuxt provides a layouts framework to extract common UI patterns into reusable layouts."
|
||||
navigation.icon: i-vscode-icons-folder-type-view
|
||||
---
|
||||
|
||||
::tip{icon="i-lucide-rocket" }
|
||||
For best performance, components placed in this directory will be automatically loaded via asynchronous import when used.
|
||||
::
|
||||
|
||||
## Enable Layouts
|
||||
|
||||
Layouts are enabled by adding [`<NuxtLayout>`](/docs/4.x/api/components/nuxt-layout) to your [`app.vue`](/docs/4.x/directory-structure/app/app):
|
||||
|
||||
```vue [app/app.vue]
|
||||
<template>
|
||||
<NuxtLayout>
|
||||
<NuxtPage />
|
||||
</NuxtLayout>
|
||||
</template>
|
||||
```
|
||||
|
||||
To use a layout:
|
||||
- Set a `layout` property in your page with [definePageMeta](/docs/4.x/api/utils/define-page-meta).
|
||||
- Set the `name` prop of `<NuxtLayout>`.
|
||||
- Set the `appLayout` property in route rules.
|
||||
|
||||
::note
|
||||
The layout name is normalized to kebab-case, so `someLayout` becomes `some-layout`.
|
||||
::
|
||||
|
||||
::note
|
||||
If no layout is specified, `app/layouts/default.vue` will be used.
|
||||
::
|
||||
|
||||
::important
|
||||
If you only have a single layout in your application, we recommend using [`app.vue`](/docs/4.x/directory-structure/app/app) instead.
|
||||
::
|
||||
|
||||
::important
|
||||
Unlike other components, your layouts must have a single root element to allow Nuxt to apply transitions between layout changes - and this root element cannot be a `<slot />`.
|
||||
::
|
||||
|
||||
## Default Layout
|
||||
|
||||
Add a `~/layouts/default.vue`:
|
||||
|
||||
```vue [app/layouts/default.vue]
|
||||
<template>
|
||||
<div>
|
||||
<p>Some default layout content shared across all pages</p>
|
||||
<slot />
|
||||
</div>
|
||||
</template>
|
||||
```
|
||||
|
||||
In a layout file, the content of the page will be displayed in the `<slot />` component.
|
||||
|
||||
## Named Layout
|
||||
|
||||
```bash [Directory Structure]
|
||||
-| layouts/
|
||||
---| default.vue
|
||||
---| custom.vue
|
||||
```
|
||||
|
||||
Then you can use the `custom` layout in your page:
|
||||
|
||||
```vue twoslash [pages/about.vue]
|
||||
<script setup lang="ts">
|
||||
declare module 'nuxt/app' {
|
||||
interface NuxtLayouts {
|
||||
'custom': unknown
|
||||
}
|
||||
}
|
||||
// ---cut---
|
||||
definePageMeta({
|
||||
layout: 'custom',
|
||||
})
|
||||
</script>
|
||||
```
|
||||
|
||||
::read-more{to="/docs/4.x/directory-structure/app/pages#page-metadata"}
|
||||
Learn more about `definePageMeta`.
|
||||
::
|
||||
|
||||
You can directly override the default layout for all pages using the `name` property of [`<NuxtLayout>`](/docs/4.x/api/components/nuxt-layout):
|
||||
|
||||
```vue [app/app.vue]
|
||||
<script setup lang="ts">
|
||||
// You might choose this based on an API call or logged-in status
|
||||
const layout = 'custom'
|
||||
</script>
|
||||
|
||||
<template>
|
||||
<NuxtLayout :name="layout">
|
||||
<NuxtPage />
|
||||
</NuxtLayout>
|
||||
</template>
|
||||
```
|
||||
|
||||
If you have a layout in nested directories, the layout's name will be based on its own path directory and filename, with duplicate segments being removed.
|
||||
|
||||
File | Layout Name
|
||||
-- | --
|
||||
`~/layouts/desktop/default.vue` | `desktop-default`
|
||||
`~/layouts/desktop-base/base.vue` | `desktop-base`
|
||||
`~/layouts/desktop/index.vue` | `desktop`
|
||||
|
||||
For clarity, we recommend that the layout's filename matches its name:
|
||||
|
||||
File | Layout Name
|
||||
-- | --
|
||||
`~/layouts/desktop/DesktopDefault.vue` | `desktop-default`
|
||||
`~/layouts/desktop-base/DesktopBase.vue` | `desktop-base`
|
||||
`~/layouts/desktop/Desktop.vue` | `desktop`
|
||||
|
||||
:link-example{to="/docs/4.x/examples/features/layouts"}
|
||||
|
||||
## Changing the Layout Dynamically
|
||||
|
||||
You can also use the [`setPageLayout`](/docs/4.x/api/utils/set-page-layout) helper to change the layout dynamically:
|
||||
|
||||
```vue twoslash [app/pages/index.vue]
|
||||
<script setup lang="ts">
|
||||
declare module 'nuxt/app' {
|
||||
interface NuxtLayouts {
|
||||
'custom': unknown
|
||||
}
|
||||
}
|
||||
// ---cut---
|
||||
function enableCustomLayout () {
|
||||
setPageLayout('custom')
|
||||
}
|
||||
definePageMeta({
|
||||
layout: false,
|
||||
})
|
||||
</script>
|
||||
|
||||
<template>
|
||||
<div>
|
||||
<button @click="enableCustomLayout">
|
||||
Update layout
|
||||
</button>
|
||||
</div>
|
||||
</template>
|
||||
```
|
||||
|
||||
You can also set layouts for specific routes using the `appLayout` property in route rules:
|
||||
|
||||
```ts [nuxt.config.ts]
|
||||
export default defineNuxtConfig({
|
||||
routeRules: {
|
||||
// Set layout for specific route
|
||||
'/admin': { appLayout: 'admin' },
|
||||
// Set layout for multiple routes
|
||||
'/dashboard/**': { appLayout: 'dashboard' },
|
||||
// Disable layout for a route
|
||||
'/landing': { appLayout: false },
|
||||
},
|
||||
})
|
||||
```
|
||||
|
||||
::tip
|
||||
This is useful when you want to manage layouts centrally in your configuration rather than in each page file, or when you need to apply layouts to routes that don't have corresponding page components (such as catchall pages which might match many paths).
|
||||
::
|
||||
|
||||
:link-example{to="/docs/4.x/examples/features/layouts"}
|
||||
|
||||
## Passing Props to Layouts :badge[+4.4]{color="primary" class="align-middle"}
|
||||
|
||||
You can pass props to layouts in several ways.
|
||||
|
||||
### Via `definePageMeta`
|
||||
|
||||
Use the object syntax for the `layout` property to pass props directly from your page:
|
||||
|
||||
::code-group
|
||||
|
||||
```vue [app/pages/dashboard.vue]
|
||||
<script setup lang="ts">
|
||||
definePageMeta({
|
||||
layout: {
|
||||
name: 'panel',
|
||||
props: {
|
||||
sidebar: true,
|
||||
title: 'Dashboard',
|
||||
},
|
||||
},
|
||||
})
|
||||
</script>
|
||||
```
|
||||
|
||||
```vue [app/layouts/panel.vue]
|
||||
<script setup lang="ts">
|
||||
const props = defineProps<{
|
||||
sidebar?: boolean
|
||||
title?: string
|
||||
}>()
|
||||
</script>
|
||||
|
||||
<template>
|
||||
<div>
|
||||
<aside v-if="sidebar">
|
||||
Sidebar
|
||||
</aside>
|
||||
<main>
|
||||
<h1>{{ title }}</h1>
|
||||
<slot />
|
||||
</main>
|
||||
</div>
|
||||
</template>
|
||||
```
|
||||
|
||||
::
|
||||
|
||||
::tip
|
||||
Props are fully typed based on your layout's `defineProps`. You'll get autocomplete and type-checking in your editor.
|
||||
::
|
||||
|
||||
### Via `setPageLayout`
|
||||
|
||||
You can also pass props when changing the layout dynamically with [`setPageLayout`](/docs/4.x/api/utils/set-page-layout):
|
||||
|
||||
```ts
|
||||
setPageLayout('panel', { sidebar: true, title: 'Dashboard' })
|
||||
```
|
||||
|
||||
## Overriding a Layout on a Per-page Basis
|
||||
|
||||
If you are using pages, you can take full control by setting `layout: false` and then using the `<NuxtLayout>` component within the page.
|
||||
|
||||
::code-group
|
||||
|
||||
```vue [app/pages/index.vue]
|
||||
<script setup lang="ts">
|
||||
definePageMeta({
|
||||
layout: false,
|
||||
})
|
||||
</script>
|
||||
|
||||
<template>
|
||||
<div>
|
||||
<NuxtLayout name="custom">
|
||||
<template #header>
|
||||
Some header template content.
|
||||
</template>
|
||||
|
||||
The rest of the page
|
||||
</NuxtLayout>
|
||||
</div>
|
||||
</template>
|
||||
```
|
||||
|
||||
```vue [app/layouts/custom.vue]
|
||||
<template>
|
||||
<div>
|
||||
<header>
|
||||
<slot name="header">
|
||||
Default header content
|
||||
</slot>
|
||||
</header>
|
||||
<main>
|
||||
<slot />
|
||||
</main>
|
||||
</div>
|
||||
</template>
|
||||
```
|
||||
|
||||
::
|
||||
|
||||
::important
|
||||
If you use `<NuxtLayout>` within your pages, make sure it is not the root element (or [disable layout/page transitions](/docs/4.x/getting-started/transitions#disable-transitions)).
|
||||
::
|
||||
253
reference/2.directory-structure/1.app/1.middleware.md
Normal file
253
reference/2.directory-structure/1.app/1.middleware.md
Normal file
@@ -0,0 +1,253 @@
|
||||
---
|
||||
title: "middleware"
|
||||
description: "Nuxt provides middleware to run code before navigating to a particular route."
|
||||
head.title: "middleware/"
|
||||
navigation.icon: i-vscode-icons-folder-type-middleware
|
||||
---
|
||||
|
||||
Nuxt provides a customizable **route middleware** framework you can use throughout your application, ideal for extracting code that you want to run before navigating to a particular route.
|
||||
|
||||
There are three kinds of route middleware:
|
||||
|
||||
1. Anonymous (or inline) route middleware are defined directly within the page.
|
||||
2. Named route middleware, placed in the `app/middleware/` and automatically loaded via asynchronous import when used on a page.
|
||||
3. Global route middleware, placed in the `app/middleware/` with a `.global` suffix and is run on every route change.
|
||||
|
||||
The first two kinds of route middleware can be defined in [`definePageMeta`](/docs/4.x/api/utils/define-page-meta).
|
||||
|
||||
::note
|
||||
Name of middleware are normalized to kebab-case: `myMiddleware` becomes `my-middleware`.
|
||||
::
|
||||
|
||||
::note
|
||||
Route middleware run within the Vue part of your Nuxt app. Despite the similar name, they are completely different from [server middleware](/docs/4.x/directory-structure/server#server-middleware), which are run in the Nitro server part of your app.
|
||||
::
|
||||
|
||||
:video-accordion{title="Watch a video from Vue School on all 3 kinds of middleware" videoId="761471577" platform="vimeo"}
|
||||
|
||||
## Usage
|
||||
|
||||
Route middleware are navigation guards that receive the current route and the next route as arguments.
|
||||
|
||||
```ts twoslash [middleware/my-middleware.ts]
|
||||
export default defineNuxtRouteMiddleware((to, from) => {
|
||||
if (to.params.id === '1') {
|
||||
return abortNavigation()
|
||||
}
|
||||
// In a real app you would probably not redirect every route to `/`
|
||||
// however it is important to check `to.path` before redirecting or you
|
||||
// might get an infinite redirect loop
|
||||
if (to.path !== '/') {
|
||||
return navigateTo('/')
|
||||
}
|
||||
})
|
||||
```
|
||||
|
||||
Nuxt provides two globally available helpers that can be returned directly from the middleware.
|
||||
|
||||
1. [`navigateTo`](/docs/4.x/api/utils/navigate-to) - Redirects to the given route
|
||||
2. [`abortNavigation`](/docs/4.x/api/utils/abort-navigation) - Aborts the navigation, with an optional error message.
|
||||
|
||||
Unlike [navigation guards](https://router.vuejs.org/guide/advanced/navigation-guards#Global-Before-Guards) from `vue-router`, a third `next()` argument is not passed, and **redirect or route cancellation is handled by returning a value from the middleware**.
|
||||
|
||||
Possible return values are:
|
||||
|
||||
* nothing (a simple `return` or no return at all) - does not block navigation and will move to the next middleware function, if any, or complete the route navigation
|
||||
* `return navigateTo('/')` - redirects to the given path and will set the redirect code to [`302` Found](https://developer.mozilla.org/en-US/docs/Web/HTTP/Reference/Status/302) if the redirect happens on the server side
|
||||
* `return navigateTo('/', { redirectCode: 301 })` - redirects to the given path and will set the redirect code to [`301` Moved Permanently](https://developer.mozilla.org/en-US/docs/Web/HTTP/Reference/Status/301) if the redirect happens on the server side
|
||||
* `return abortNavigation()` - stops the current navigation
|
||||
* `return abortNavigation(error)` - rejects the current navigation with an error
|
||||
|
||||
:read-more{to="/docs/4.x/api/utils/navigate-to"}
|
||||
:read-more{to="/docs/4.x/api/utils/abort-navigation"}
|
||||
|
||||
::important
|
||||
We recommend using the helper functions above for performing redirects or stopping navigation. Other possible return values described in [the vue-router docs](https://router.vuejs.org/guide/advanced/navigation-guards#Global-Before-Guards) may work but there may be breaking changes in future.
|
||||
::
|
||||
|
||||
## Middleware Order
|
||||
|
||||
Middleware runs in the following order:
|
||||
|
||||
1. Global Middleware
|
||||
2. Page defined middleware order (if there are multiple middleware declared with the array syntax)
|
||||
|
||||
For example, assuming you have the following middleware and component:
|
||||
|
||||
```bash [app/middleware/ directory]
|
||||
-| middleware/
|
||||
---| analytics.global.ts
|
||||
---| setup.global.ts
|
||||
---| auth.ts
|
||||
```
|
||||
|
||||
```vue twoslash [pages/profile.vue]
|
||||
<script setup lang="ts">
|
||||
definePageMeta({
|
||||
middleware: [
|
||||
function (to, from) {
|
||||
// Custom inline middleware
|
||||
},
|
||||
'auth',
|
||||
],
|
||||
})
|
||||
</script>
|
||||
```
|
||||
|
||||
You can expect the middleware to be run in the following order:
|
||||
|
||||
1. `analytics.global.ts`
|
||||
2. `setup.global.ts`
|
||||
3. Custom inline middleware
|
||||
4. `auth.ts`
|
||||
|
||||
### Ordering Global Middleware
|
||||
|
||||
By default, global middleware is executed alphabetically based on the filename.
|
||||
|
||||
However, there may be times you want to define a specific order. For example, in the last scenario, `setup.global.ts` may need to run before `analytics.global.ts`. In that case, we recommend prefixing global middleware with 'alphabetical' numbering.
|
||||
|
||||
```bash [Directory structure]
|
||||
-| middleware/
|
||||
---| 01.setup.global.ts
|
||||
---| 02.analytics.global.ts
|
||||
---| auth.ts
|
||||
```
|
||||
|
||||
::note
|
||||
In case you're new to 'alphabetical' numbering, remember that filenames are sorted as strings, not as numeric values. For example, `10.new.global.ts` would come before `2.new.global.ts`. This is why the example prefixes single digit numbers with `0`.
|
||||
::
|
||||
|
||||
## When Middleware Runs
|
||||
|
||||
If your site is server-rendered or generated, middleware for the initial page will be executed both when the page is rendered and then again on the client. This might be needed if your middleware needs a browser environment, such as if you have a generated site, aggressively cache responses, or want to read a value from local storage.
|
||||
|
||||
However, if you want to avoid this behaviour you can do so:
|
||||
|
||||
```ts twoslash [middleware/example.ts]
|
||||
export default defineNuxtRouteMiddleware((to) => {
|
||||
// skip middleware on server
|
||||
if (import.meta.server) {
|
||||
return
|
||||
}
|
||||
// skip middleware on client side entirely
|
||||
if (import.meta.client) {
|
||||
return
|
||||
}
|
||||
// or only skip middleware on initial client load
|
||||
const nuxtApp = useNuxtApp()
|
||||
if (import.meta.client && nuxtApp.isHydrating && nuxtApp.payload.serverRendered) {
|
||||
return
|
||||
}
|
||||
})
|
||||
```
|
||||
|
||||
This is true even if you throw an error in your middleware on the server, and an error page is rendered. The middleware will still run again in the browser.
|
||||
|
||||
::note
|
||||
Rendering an error page is an entirely separate page load, meaning any registered middleware will run again. You can use [`useError`](/docs/4.x/getting-started/error-handling#useerror) in middleware to check if an error is being handled.
|
||||
::
|
||||
|
||||
## Accessing Route in Middleware
|
||||
|
||||
Always use the `to` and `from` parameters in your middleware to access the next and previous routes. Avoid using the [`useRoute()`](/docs/4.x/api/composables/use-route) composable in this context altogether.
|
||||
There is **no concept of a "current route" in middleware**, as middleware can abort a navigation or redirect to a different route. The `useRoute()` composable will always be inaccurate in this context.
|
||||
|
||||
::warning
|
||||
Sometimes, you might call a composable that uses `useRoute()` internally, which can trigger this warning even if there is no direct call in your middleware.
|
||||
This leads to the **same issue as above**, so you should structure your functions to accept the route as an argument instead when they are used in middleware.
|
||||
::
|
||||
|
||||
::code-group
|
||||
```ts twoslash [middleware/access-route.ts]
|
||||
// @errors: 2304
|
||||
export default defineNuxtRouteMiddleware((to) => {
|
||||
// passing the route to the function to avoid calling `useRoute()` in middleware
|
||||
doSomethingWithRoute(to)
|
||||
|
||||
// ❌ this will output a warning and is NOT recommended
|
||||
callsRouteInternally()
|
||||
})
|
||||
```
|
||||
|
||||
```ts twoslash [utils/handle-route.ts]
|
||||
// providing the route as an argument so that it can be used in middleware correctly
|
||||
export function doSomethingWithRoute (route = useRoute()) {
|
||||
// ...
|
||||
}
|
||||
```
|
||||
```ts twoslash [utils/dont-do-this.ts]
|
||||
// ❌ this function is not suitable for use in middleware
|
||||
export function callsRouteInternally () {
|
||||
const route = useRoute()
|
||||
// ...
|
||||
}
|
||||
```
|
||||
|
||||
::
|
||||
|
||||
## Adding Middleware Dynamically
|
||||
|
||||
It is possible to add global or named route middleware manually using the [`addRouteMiddleware()`](/docs/4.x/api/utils/add-route-middleware) helper function, such as from within a plugin.
|
||||
|
||||
```ts twoslash
|
||||
export default defineNuxtPlugin(() => {
|
||||
addRouteMiddleware('global-test', () => {
|
||||
console.log('this global middleware was added in a plugin and will be run on every route change')
|
||||
}, { global: true })
|
||||
|
||||
addRouteMiddleware('named-test', () => {
|
||||
console.log('this named middleware was added in a plugin and would override any existing middleware of the same name')
|
||||
})
|
||||
})
|
||||
```
|
||||
|
||||
## Example
|
||||
|
||||
```bash [Directory Structure]
|
||||
-| middleware/
|
||||
---| auth.ts
|
||||
```
|
||||
|
||||
In your page file, you can reference this route middleware:
|
||||
|
||||
```vue twoslash
|
||||
<script setup lang="ts">
|
||||
definePageMeta({
|
||||
middleware: ['auth'],
|
||||
// or middleware: 'auth'
|
||||
})
|
||||
</script>
|
||||
```
|
||||
|
||||
Now, before navigation to that page can complete, the `auth` route middleware will be run.
|
||||
|
||||
:link-example{to="/docs/4.x/examples/routing/middleware"}
|
||||
|
||||
## Setting Middleware at Build Time
|
||||
|
||||
Instead of using `definePageMeta` on each page, you can add named route middleware within the `pages:extend` hook.
|
||||
|
||||
```ts twoslash [nuxt.config.ts]
|
||||
import type { NuxtPage } from 'nuxt/schema'
|
||||
|
||||
export default defineNuxtConfig({
|
||||
hooks: {
|
||||
'pages:extend' (pages) {
|
||||
function setMiddleware (pages: NuxtPage[]) {
|
||||
for (const page of pages) {
|
||||
if (/* some condition */ Math.random() > 0.5) {
|
||||
page.meta ||= {}
|
||||
// Note that this will override any middleware set in `definePageMeta` in the page
|
||||
page.meta.middleware = ['named']
|
||||
}
|
||||
if (page.children) {
|
||||
setMiddleware(page.children)
|
||||
}
|
||||
}
|
||||
}
|
||||
setMiddleware(pages)
|
||||
},
|
||||
},
|
||||
})
|
||||
```
|
||||
467
reference/2.directory-structure/1.app/1.pages.md
Normal file
467
reference/2.directory-structure/1.app/1.pages.md
Normal file
@@ -0,0 +1,467 @@
|
||||
---
|
||||
title: "pages"
|
||||
description: "Nuxt provides file-based routing to create routes within your web application."
|
||||
head.title: "pages/"
|
||||
navigation.icon: i-vscode-icons-folder-type-view
|
||||
---
|
||||
|
||||
::note
|
||||
To reduce your application's bundle size, this directory is **optional**, meaning that [`vue-router`](https://router.vuejs.org) won't be included if you only use [`app.vue`](/docs/4.x/directory-structure/app/app). To force the pages system, set `pages: true` in `nuxt.config` or have a [`router.options.ts`](/docs/4.x/guide/recipes/custom-routing#using-routeroptions).
|
||||
::
|
||||
|
||||
## Usage
|
||||
|
||||
Pages are Vue components and can have any [valid extension](/docs/4.x/api/nuxt-config#extensions) that Nuxt supports (by default `.vue`, `.js`, `.jsx`, `.mjs`, `.ts` or `.tsx`).
|
||||
|
||||
Nuxt will automatically create a route for every page in your `~/pages/` directory.
|
||||
|
||||
::code-group
|
||||
|
||||
```vue [app/pages/index.vue]
|
||||
<template>
|
||||
<h1>Index page</h1>
|
||||
</template>
|
||||
```
|
||||
|
||||
```ts twoslash [pages/index.ts]
|
||||
// https://vuejs.org/guide/extras/render-function.html
|
||||
export default defineComponent({
|
||||
render () {
|
||||
return h('h1', 'Index page')
|
||||
},
|
||||
})
|
||||
```
|
||||
|
||||
```tsx twoslash [pages/index.tsx]
|
||||
// https://nuxt.com/docs/4.x/examples/advanced/jsx
|
||||
// https://vuejs.org/guide/extras/render-function.html#jsx-tsx
|
||||
export default defineComponent({
|
||||
render () {
|
||||
return <h1>Index page</h1>
|
||||
},
|
||||
})
|
||||
```
|
||||
|
||||
::
|
||||
|
||||
The `app/pages/index.vue` file will be mapped to the `/` route of your application.
|
||||
|
||||
If you are using [`app.vue`](/docs/4.x/directory-structure/app/app), make sure to use the [`<NuxtPage/>`](/docs/4.x/api/components/nuxt-page) component to display the current page:
|
||||
|
||||
```vue [app/app.vue]
|
||||
<template>
|
||||
<div>
|
||||
<!-- Markup shared across all pages, ex: NavBar -->
|
||||
<NuxtPage />
|
||||
</div>
|
||||
</template>
|
||||
```
|
||||
|
||||
Pages **must have a single root element** to allow [route transitions](/docs/4.x/getting-started/transitions) between pages. HTML comments are considered elements as well.
|
||||
|
||||
This means that when the route is server-rendered, or statically generated, you will be able to see its contents correctly, but when you navigate towards that route during client-side navigation the transition between routes will fail and you'll see that the route will not be rendered.
|
||||
|
||||
Here are some examples to illustrate what a page with a single root element looks like:
|
||||
|
||||
::code-group
|
||||
|
||||
```vue [app/pages/working.vue]
|
||||
<template>
|
||||
<div>
|
||||
<!-- This page correctly has only one single root element -->
|
||||
Page content
|
||||
</div>
|
||||
</template>
|
||||
```
|
||||
|
||||
```vue [app/pages/bad-1.vue]
|
||||
<template>
|
||||
<!-- This page will not render when route changes during client side navigation, because of this comment -->
|
||||
<div>Page content</div>
|
||||
</template>
|
||||
```
|
||||
|
||||
```vue [app/pages/bad-2.vue]
|
||||
<template>
|
||||
<div>This page</div>
|
||||
<div>Has more than one root element</div>
|
||||
<div>And will not render when route changes during client side navigation</div>
|
||||
</template>
|
||||
```
|
||||
|
||||
::
|
||||
|
||||
## Dynamic Routes
|
||||
|
||||
If you place anything within square brackets, it will be turned into a [dynamic route](https://router.vuejs.org/guide/essentials/dynamic-matching) parameter. You can mix and match multiple parameters and even non-dynamic text within a file name or directory.
|
||||
|
||||
If you want a parameter to be _optional_, you must enclose it in double square brackets - for example, `~/pages/[[slug]]/index.vue` or `~/pages/[[slug]].vue` will match both `/` and `/test`.
|
||||
|
||||
```bash [Directory Structure]
|
||||
-| pages/
|
||||
---| index.vue
|
||||
---| users-[group]/
|
||||
-----| [id].vue
|
||||
```
|
||||
|
||||
Given the example above, you can access group/id within your component via the `$route` object:
|
||||
|
||||
```vue [app/pages/users-[group\\]/[id\\].vue]
|
||||
<template>
|
||||
<p>{{ $route.params.group }} - {{ $route.params.id }}</p>
|
||||
</template>
|
||||
```
|
||||
|
||||
Navigating to `/users-admins/123` would render:
|
||||
|
||||
```html
|
||||
<p>admins - 123</p>
|
||||
```
|
||||
|
||||
If you want to access the route using Composition API, there is a global [`useRoute`](/docs/4.x/api/composables/use-route) function that will allow you to access the route just like `this.$route` in the Options API.
|
||||
|
||||
```vue twoslash
|
||||
<script setup lang="ts">
|
||||
const route = useRoute()
|
||||
|
||||
if (route.params.group === 'admins' && !route.params.id) {
|
||||
console.log('Warning! Make sure user is authenticated!')
|
||||
}
|
||||
</script>
|
||||
```
|
||||
|
||||
::note
|
||||
Named parent routes will take priority over nested dynamic routes. For the `/foo/hello` route, `~/pages/foo.vue` will take priority over `~/pages/foo/[slug].vue`. :br Use `~/pages/foo/index.vue` and `~/pages/foo/[slug].vue` to match `/foo` and `/foo/hello` with different pages,.
|
||||
::
|
||||
|
||||
:video-accordion{title="Watch a video from Vue School on dynamic routes" videoId="754465699" platform="vimeo"}
|
||||
|
||||
## Catch-all Route
|
||||
|
||||
If you need a catch-all route, you create it by using a file named like `[...slug].vue`. This will match _all_ routes under that path.
|
||||
|
||||
```vue [app/pages/[...slug\\].vue]
|
||||
<template>
|
||||
<p>{{ $route.params.slug }}</p>
|
||||
</template>
|
||||
```
|
||||
|
||||
Navigating to `/hello/world` would render:
|
||||
|
||||
```html
|
||||
<p>["hello", "world"]</p>
|
||||
```
|
||||
|
||||
## Nested Routes
|
||||
|
||||
It is possible to display [nested routes](https://router.vuejs.org/guide/essentials/nested-routes) with `<NuxtPage>`.
|
||||
|
||||
Example:
|
||||
|
||||
```bash [Directory Structure]
|
||||
-| pages/
|
||||
---| parent/
|
||||
-----| child.vue
|
||||
---| parent.vue
|
||||
```
|
||||
|
||||
This file tree will generate these routes:
|
||||
|
||||
```js
|
||||
[
|
||||
{
|
||||
path: '/parent',
|
||||
component: '~/pages/parent.vue',
|
||||
name: 'parent',
|
||||
children: [
|
||||
{
|
||||
path: 'child',
|
||||
component: '~/pages/parent/child.vue',
|
||||
name: 'parent-child',
|
||||
},
|
||||
],
|
||||
},
|
||||
]
|
||||
```
|
||||
|
||||
To display the `child.vue` component, you have to insert the `<NuxtPage>` component inside `app/pages/parent.vue`:
|
||||
|
||||
```vue {}[pages/parent.vue]
|
||||
<template>
|
||||
<div>
|
||||
<h1>I am the parent view</h1>
|
||||
<NuxtPage :foobar="123" />
|
||||
</div>
|
||||
</template>
|
||||
```
|
||||
|
||||
```vue {}[pages/parent/child.vue]
|
||||
<script setup lang="ts">
|
||||
const props = defineProps({
|
||||
foobar: String,
|
||||
})
|
||||
|
||||
console.log(props.foobar)
|
||||
</script>
|
||||
```
|
||||
|
||||
### Child Route Keys
|
||||
|
||||
If you want more control over when the `<NuxtPage>` component is re-rendered (for example, for transitions), you can either pass a string or function via the `pageKey` prop, or you can define a `key` value via `definePageMeta`:
|
||||
|
||||
```vue {}[pages/parent.vue]
|
||||
<template>
|
||||
<div>
|
||||
<h1>I am the parent view</h1>
|
||||
<NuxtPage :page-key="route => route.fullPath" />
|
||||
</div>
|
||||
</template>
|
||||
```
|
||||
|
||||
Or alternatively:
|
||||
|
||||
```vue twoslash {}[pages/parent/child.vue]
|
||||
<script setup lang="ts">
|
||||
definePageMeta({
|
||||
key: route => route.fullPath,
|
||||
})
|
||||
</script>
|
||||
```
|
||||
|
||||
:link-example{to="/docs/4.x/examples/routing/pages"}
|
||||
|
||||
## Route Groups
|
||||
|
||||
In some cases, you may want to group a set of routes together in a way which doesn't affect file-based routing. For this purpose, you can put files in a folder which is wrapped in parentheses - `(` and `)`.
|
||||
|
||||
For example:
|
||||
|
||||
```bash [Directory structure]
|
||||
-| pages/
|
||||
---| index.vue
|
||||
---| (marketing)/
|
||||
-----| about.vue
|
||||
-----| contact.vue
|
||||
```
|
||||
|
||||
This will produce `/`, `/about` and `/contact` pages in your app. The `marketing` group is ignored for purposes of your URL structure.
|
||||
|
||||
### Accessing Route Groups
|
||||
|
||||
Route groups are automatically available in the route metadata as `route.meta.groups`.
|
||||
This allows you to access the group information in your components for conditional logic, styling, or other purposes.
|
||||
|
||||
```vue {}[pages/(marketing)/about.vue]
|
||||
<script setup lang="ts">
|
||||
const route = useRoute()
|
||||
|
||||
console.log(route.meta.groups) // Output: ['marketing']
|
||||
</script>
|
||||
|
||||
<template>
|
||||
<div>
|
||||
<p v-if="route.meta.groups?.includes('marketing')">
|
||||
This is a marketing page
|
||||
</p>
|
||||
</div>
|
||||
</template>
|
||||
```
|
||||
|
||||
## Page Metadata
|
||||
|
||||
You might want to define metadata for each route in your app. You can do this using the `definePageMeta` macro, which will work both in `<script>` and in `<script setup>`:
|
||||
|
||||
```vue twoslash
|
||||
<script setup lang="ts">
|
||||
definePageMeta({
|
||||
title: 'My home page',
|
||||
})
|
||||
</script>
|
||||
```
|
||||
|
||||
This data can then be accessed throughout the rest of your app from the `route.meta` object.
|
||||
|
||||
```vue twoslash
|
||||
<script setup lang="ts">
|
||||
const route = useRoute()
|
||||
|
||||
console.log(route.meta.title) // My home page
|
||||
</script>
|
||||
```
|
||||
|
||||
If you are using nested routes, the page metadata from all these routes will be merged into a single object. For more on route meta, see the [vue-router docs](https://router.vuejs.org/guide/advanced/meta).
|
||||
|
||||
Much like `defineEmits` or `defineProps` (see [Vue docs](https://vuejs.org/api/sfc-script-setup#defineprops-defineemits)), `definePageMeta` is a **compiler macro**. It will be compiled away so you cannot reference it within your component. Instead, the metadata passed to it will be hoisted out of the component.
|
||||
Therefore, the page meta object cannot reference the component. However, it can reference imported bindings, as well as locally defined **pure functions**.
|
||||
|
||||
::warning
|
||||
Make sure not to reference any reactive data or functions that cause side effects. This can lead to unexpected behavior.
|
||||
::
|
||||
|
||||
```vue
|
||||
<script setup lang="ts">
|
||||
import { someData } from '~/utils/example'
|
||||
|
||||
function validateIdParam (route) {
|
||||
return route.params.id && !Number.isNaN(Number(route.params.id))
|
||||
}
|
||||
|
||||
const title = ref('')
|
||||
|
||||
definePageMeta({
|
||||
validate: validateIdParam,
|
||||
someData,
|
||||
title, // do not do this, the ref will be hoisted out of the component
|
||||
})
|
||||
</script>
|
||||
```
|
||||
|
||||
### Special Metadata
|
||||
|
||||
Of course, you are welcome to define metadata for your own use throughout your app. But some metadata defined with `definePageMeta` has a particular purpose:
|
||||
|
||||
#### `alias`
|
||||
|
||||
You can define page aliases. They allow you to access the same page from different paths. It can be either a string or an array of strings as defined [in the vue-router documentation](https://router.vuejs.org/guide/essentials/redirect-and-alias#Alias).
|
||||
|
||||
#### `keepalive`
|
||||
|
||||
Nuxt will automatically wrap your page in [the Vue `<KeepAlive>` component](https://vuejs.org/guide/built-ins/keep-alive#keepalive) if you set `keepalive: true` in your `definePageMeta`. This might be useful to do, for example, in a parent route that has dynamic child routes, if you want to preserve page state across route changes.
|
||||
|
||||
When your goal is to preserve state for parent routes use this syntax: `<NuxtPage keepalive />`. You can also set props to be passed to `<KeepAlive>` (see [a full list](https://vuejs.org/api/built-in-components#keepalive)).
|
||||
|
||||
You can set a default value for this property [in your `nuxt.config`](/docs/4.x/api/nuxt-config#keepalive).
|
||||
|
||||
#### `key`
|
||||
|
||||
[See above](/docs/4.x/directory-structure/app/pages#child-route-keys).
|
||||
|
||||
#### `layout`
|
||||
|
||||
You can define the layout used to render the route. This can be either false (to disable any layout), a string or a ref/computed, if you want to make it reactive in some way. [More about layouts](/docs/4.x/directory-structure/app/layouts).
|
||||
|
||||
#### `layoutTransition` and `pageTransition`
|
||||
|
||||
You can define transition properties for the `<transition>` component that wraps your pages and layouts, or pass `false` to disable the `<transition>` wrapper for that route. You can see [a list of options that can be passed](https://vuejs.org/api/built-in-components#transition) or read [more about how transitions work](https://vuejs.org/guide/built-ins/transition#transition).
|
||||
|
||||
You can set default values for these properties [in your `nuxt.config`](/docs/4.x/api/nuxt-config#layouttransition).
|
||||
|
||||
#### `middleware`
|
||||
|
||||
You can define middleware to apply before loading this page. It will be merged with all the other middleware used in any matching parent/child routes. It can be a string, a function (an anonymous/inlined middleware function following [the global before guard pattern](https://router.vuejs.org/guide/advanced/navigation-guards#Global-Before-Guards)), or an array of strings/functions. [More about named middleware](/docs/4.x/directory-structure/app/middleware).
|
||||
|
||||
#### `name`
|
||||
|
||||
You may define a name for this page's route.
|
||||
|
||||
#### `path`
|
||||
|
||||
You may define a path matcher, if you have a more complex pattern than can be expressed with the file name. See [the `vue-router` docs](https://router.vuejs.org/guide/essentials/route-matching-syntax#Custom-regex-in-params) for more information.
|
||||
|
||||
#### `props`
|
||||
|
||||
Allows accessing the route `params` as props passed to the page component. See [the `vue-router` docs](https://router.vuejs.org/guide/essentials/passing-props) for more information.
|
||||
|
||||
### Typing Custom Metadata
|
||||
|
||||
If you add custom metadata for your pages, you may wish to do so in a type-safe way. It is possible to augment the type of the object accepted by `definePageMeta`:
|
||||
|
||||
```ts [index.d.ts]
|
||||
declare module '#app' {
|
||||
interface PageMeta {
|
||||
pageType?: string
|
||||
}
|
||||
}
|
||||
|
||||
// It is always important to ensure you import/export something when augmenting a type
|
||||
export {}
|
||||
```
|
||||
|
||||
## Navigation
|
||||
|
||||
To navigate between pages of your app, you should use the [`<NuxtLink>`](/docs/4.x/api/components/nuxt-link) component.
|
||||
|
||||
This component is included with Nuxt and therefore you don't have to import it as you do with other components.
|
||||
|
||||
A simple link to the `index.vue` page in your `app/pages` folder:
|
||||
|
||||
```vue
|
||||
<template>
|
||||
<NuxtLink to="/">Home page</NuxtLink>
|
||||
</template>
|
||||
```
|
||||
|
||||
::read-more{to="/docs/4.x/api/components/nuxt-link"}
|
||||
Learn more about `<NuxtLink>` usage.
|
||||
::
|
||||
|
||||
## Programmatic Navigation
|
||||
|
||||
Nuxt allows programmatic navigation through the `navigateTo()` utility method. Using this utility method, you will be able to programmatically navigate the user in your app. This is great for taking input from the user and navigating them dynamically throughout your application. In this example, we have a simple method called `navigate()` that gets called when the user submits a search form.
|
||||
|
||||
::note
|
||||
Make sure to always `await` on `navigateTo` or chain its result by returning from functions.
|
||||
::
|
||||
|
||||
```vue twoslash
|
||||
<script setup lang="ts">
|
||||
const name = ref('')
|
||||
const type = ref(1)
|
||||
|
||||
function navigate () {
|
||||
return navigateTo({
|
||||
path: '/search',
|
||||
query: {
|
||||
name: name.value,
|
||||
type: type.value,
|
||||
},
|
||||
})
|
||||
}
|
||||
</script>
|
||||
```
|
||||
|
||||
## Client-Only Pages
|
||||
|
||||
You can define a page as [client only](/docs/4.x/directory-structure/app/components#client-components) by giving it a `.client.vue` suffix. None of the content of this page will be rendered on the server.
|
||||
|
||||
## Server-Only Pages
|
||||
|
||||
You can define a page as [server only](/docs/4.x/directory-structure/app/components#server-components) by giving it a `.server.vue` suffix. While you will be able to navigate to the page using client-side navigation, controlled by `vue-router`, it will be rendered with a server component automatically, meaning the code required to render the page will not be in your client-side bundle.
|
||||
|
||||
::warning
|
||||
Server-only pages must have a single root element. (HTML comments are considered elements as well.)
|
||||
::
|
||||
|
||||
## Custom Routing
|
||||
|
||||
As your app gets bigger and more complex, your routing might require more flexibility. For this reason, Nuxt directly exposes the router, routes and router options for customization in different ways.
|
||||
|
||||
:read-more{to="/docs/4.x/guide/recipes/custom-routing"}
|
||||
|
||||
## Multiple Pages Directories
|
||||
|
||||
By default, all your pages should be in one `app/pages` directory at the root of your project.
|
||||
|
||||
However, you can use [Nuxt Layers](/docs/4.x/getting-started/layers) to create groupings of your app's pages:
|
||||
|
||||
```bash [Directory Structure]
|
||||
-| some-app/
|
||||
---| nuxt.config.ts
|
||||
---| pages/
|
||||
-----| app-page.vue
|
||||
-| nuxt.config.ts
|
||||
```
|
||||
|
||||
```ts twoslash [some-app/nuxt.config.ts]
|
||||
// some-app/nuxt.config.ts
|
||||
export default defineNuxtConfig({
|
||||
})
|
||||
```
|
||||
|
||||
```ts twoslash [nuxt.config.ts]
|
||||
export default defineNuxtConfig({
|
||||
extends: ['./some-app'],
|
||||
})
|
||||
```
|
||||
|
||||
:read-more{to="/docs/4.x/guide/going-further/layers"}
|
||||
298
reference/2.directory-structure/1.app/1.plugins.md
Normal file
298
reference/2.directory-structure/1.app/1.plugins.md
Normal file
@@ -0,0 +1,298 @@
|
||||
---
|
||||
title: "plugins"
|
||||
description: "Nuxt has a plugins system to use Vue plugins and more at the creation of your Vue application."
|
||||
head.title: "plugins/"
|
||||
navigation.icon: i-vscode-icons-folder-type-plugin
|
||||
---
|
||||
|
||||
Nuxt automatically reads the files in the `app/plugins/` directory and loads them at the creation of the Vue application.
|
||||
|
||||
::note
|
||||
All plugins inside are auto-registered, you don't need to add them to your `nuxt.config` separately.
|
||||
::
|
||||
|
||||
::note
|
||||
You can use `.server` or `.client` suffix in the file name to load a plugin only on the server or client side.
|
||||
::
|
||||
|
||||
## Registered Plugins
|
||||
|
||||
Only files at the top level of the directory (or index files within any subdirectories) will be auto-registered as plugins.
|
||||
|
||||
```bash [Directory structure]
|
||||
-| plugins/
|
||||
---| foo.ts // scanned
|
||||
---| bar/
|
||||
-----| baz.ts // not scanned
|
||||
-----| foz.vue // not scanned
|
||||
-----| index.ts // currently scanned but deprecated
|
||||
```
|
||||
|
||||
Only `foo.ts` and `bar/index.ts` would be registered.
|
||||
|
||||
To add plugins in subdirectories, you can use the [`app/plugins`](/docs/4.x/api/nuxt-config#plugins-1) option in `nuxt.config.ts`:
|
||||
|
||||
```ts twoslash [nuxt.config.ts]
|
||||
export default defineNuxtConfig({
|
||||
plugins: [
|
||||
'~/plugins/bar/baz',
|
||||
'~/plugins/bar/foz',
|
||||
],
|
||||
})
|
||||
```
|
||||
|
||||
## Creating Plugins
|
||||
|
||||
The only argument passed to a plugin is [`nuxtApp`](/docs/4.x/api/composables/use-nuxt-app).
|
||||
|
||||
```ts twoslash [plugins/hello.ts]
|
||||
export default defineNuxtPlugin((nuxtApp) => {
|
||||
// Doing something with nuxtApp
|
||||
})
|
||||
```
|
||||
|
||||
### Object Syntax Plugins
|
||||
|
||||
It is also possible to define a plugin using an object syntax, for more advanced use cases. For example:
|
||||
|
||||
```ts twoslash [plugins/hello.ts]
|
||||
export default defineNuxtPlugin({
|
||||
name: 'my-plugin',
|
||||
enforce: 'pre', // or 'post'
|
||||
async setup (nuxtApp) {
|
||||
// this is the equivalent of a normal functional plugin
|
||||
},
|
||||
hooks: {
|
||||
// You can directly register Nuxt app runtime hooks here
|
||||
'app:created' () {
|
||||
const nuxtApp = useNuxtApp()
|
||||
// do something in the hook
|
||||
},
|
||||
},
|
||||
env: {
|
||||
// Set this value to `false` if you don't want the plugin to run when rendering server-only or island components.
|
||||
islands: true,
|
||||
},
|
||||
})
|
||||
```
|
||||
|
||||
:video-accordion{title="Watch a video from Alexander Lichter about the Object Syntax for Nuxt plugins" videoId="2aXZyXB1QGQ"}
|
||||
|
||||
::note
|
||||
If you are using the object-syntax, the properties are statically analyzed 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.
|
||||
Nuxt does statically pre-load any hook listeners when using object-syntax, allowing you to define hooks without needing to worry about order of plugin registration.
|
||||
::
|
||||
|
||||
## Registration Order
|
||||
|
||||
You can control the order in which plugins are registered by prefixing with 'alphabetical' numbering to the file names.
|
||||
|
||||
```bash [Directory structure]
|
||||
plugins/
|
||||
| - 01.myPlugin.ts
|
||||
| - 02.myOtherPlugin.ts
|
||||
```
|
||||
|
||||
In this example, `02.myOtherPlugin.ts` will be able to access anything that was injected by `01.myPlugin.ts`.
|
||||
|
||||
This is useful in situations where you have a plugin that depends on another plugin.
|
||||
|
||||
::note
|
||||
In case you're new to 'alphabetical' numbering, remember that filenames are sorted as strings, not as numeric values. For example, `10.myPlugin.ts` would come before `2.myOtherPlugin.ts`. This is why the example prefixes single digit numbers with `0`.
|
||||
::
|
||||
|
||||
## Loading Strategy
|
||||
|
||||
### Parallel Plugins
|
||||
|
||||
By default, Nuxt loads plugins sequentially. You can define a plugin as `parallel` so Nuxt won't wait until the end of the plugin's execution before loading the next plugin.
|
||||
|
||||
```ts twoslash [plugins/my-plugin.ts]
|
||||
export default defineNuxtPlugin({
|
||||
name: 'my-plugin',
|
||||
parallel: true,
|
||||
async setup (nuxtApp) {
|
||||
// the next plugin will be executed immediately
|
||||
},
|
||||
})
|
||||
```
|
||||
|
||||
### Plugins With Dependencies
|
||||
|
||||
If a plugin needs to wait for another plugin before it runs, you can add the plugin's name to the `dependsOn` array.
|
||||
|
||||
```ts twoslash [plugins/depending-on-my-plugin.ts]
|
||||
export default defineNuxtPlugin({
|
||||
name: 'depends-on-my-plugin',
|
||||
dependsOn: ['my-plugin'],
|
||||
async setup (nuxtApp) {
|
||||
// this plugin will wait for the end of `my-plugin`'s execution before it runs
|
||||
},
|
||||
})
|
||||
```
|
||||
|
||||
## Using Composables
|
||||
|
||||
You can use [composables](/docs/4.x/directory-structure/app/composables) as well as [utils](/docs/4.x/directory-structure/app/utils) within Nuxt plugins:
|
||||
|
||||
```ts [app/plugins/hello.ts]
|
||||
export default defineNuxtPlugin((nuxtApp) => {
|
||||
const foo = useFoo()
|
||||
})
|
||||
```
|
||||
|
||||
However, keep in mind there are some limitations and differences:
|
||||
|
||||
::important
|
||||
**If a composable depends on another plugin registered later, it might not work.** :br
|
||||
|
||||
Plugins are called in order sequentially and before everything else. You might use a composable that depends on another plugin which has not been called yet.
|
||||
::
|
||||
|
||||
::important
|
||||
**If a composable depends on the Vue.js lifecycle, it won't work.** :br
|
||||
|
||||
Normally, Vue.js composables are bound to the current component instance while plugins are only bound to [`nuxtApp`](/docs/4.x/api/composables/use-nuxt-app) instance.
|
||||
::
|
||||
|
||||
## Providing Helpers
|
||||
|
||||
If you would like to provide a helper on the [`NuxtApp`](/docs/4.x/api/composables/use-nuxt-app) instance, return it from the plugin under a `provide` key.
|
||||
|
||||
::code-group
|
||||
```ts twoslash [plugins/hello.ts]
|
||||
export default defineNuxtPlugin(() => {
|
||||
return {
|
||||
provide: {
|
||||
hello: (msg: string) => `Hello ${msg}!`,
|
||||
},
|
||||
}
|
||||
})
|
||||
```
|
||||
```ts twoslash [plugins/hello-object-syntax.ts]
|
||||
export default defineNuxtPlugin({
|
||||
name: 'hello',
|
||||
setup () {
|
||||
return {
|
||||
provide: {
|
||||
hello: (msg: string) => `Hello ${msg}!`,
|
||||
},
|
||||
}
|
||||
},
|
||||
})
|
||||
```
|
||||
::
|
||||
|
||||
You can then use the helper in your components:
|
||||
|
||||
```vue [app/components/Hello.vue]
|
||||
<script setup lang="ts">
|
||||
// alternatively, you can also use it here
|
||||
const { $hello } = useNuxtApp()
|
||||
</script>
|
||||
|
||||
<template>
|
||||
<div>
|
||||
{{ $hello('world') }}
|
||||
</div>
|
||||
</template>
|
||||
```
|
||||
|
||||
::important
|
||||
Note that we highly recommend using [`composables`](/docs/4.x/directory-structure/app/composables) instead of providing helpers to avoid polluting the global namespace and keep your main bundle entry small.
|
||||
::
|
||||
|
||||
::warning
|
||||
**If your plugin provides a `ref` or `computed`, it will not be unwrapped in a component `<template>`.** :br
|
||||
This is due to how Vue works with refs that aren't top-level to the template. You can read more about it [in the Vue documentation](https://vuejs.org/guide/essentials/reactivity-fundamentals#caveat-when-unwrapping-in-templates).
|
||||
::
|
||||
|
||||
## Typing Plugins
|
||||
|
||||
If you return your helpers from the plugin, they will be typed automatically; you'll find them typed for the return of `useNuxtApp()` and within your templates.
|
||||
|
||||
::note
|
||||
If you need to use a provided helper _within_ another plugin, you can call [`useNuxtApp()`](/docs/4.x/api/composables/use-nuxt-app) to get the typed version. But in general, this should be avoided unless you are certain of the plugins' order.
|
||||
::
|
||||
|
||||
For advanced use-cases, you can declare the type of injected properties like this:
|
||||
|
||||
```ts [index.d.ts]
|
||||
declare module '#app' {
|
||||
interface NuxtApp {
|
||||
$hello (msg: string): string
|
||||
}
|
||||
}
|
||||
|
||||
declare module 'vue' {
|
||||
interface ComponentCustomProperties {
|
||||
$hello (msg: string): string
|
||||
}
|
||||
}
|
||||
|
||||
export {}
|
||||
```
|
||||
|
||||
## Vue Plugins
|
||||
|
||||
If you want to use Vue plugins, like [vue-gtag](https://github.com/MatteoGabriele/vue-gtag) to add Google Analytics tags, you can use a Nuxt plugin to do so.
|
||||
|
||||
First, install the Vue plugin dependency:
|
||||
|
||||
::code-group{sync="pm"}
|
||||
```bash [npm]
|
||||
npm install --save-dev vue-gtag-next
|
||||
```
|
||||
```bash [yarn]
|
||||
yarn add --dev vue-gtag-next
|
||||
```
|
||||
```bash [pnpm]
|
||||
pnpm add -D vue-gtag-next
|
||||
```
|
||||
```bash [bun]
|
||||
bun add -D vue-gtag-next
|
||||
```
|
||||
```bash [deno]
|
||||
deno add -D npm:vue-gtag-next
|
||||
```
|
||||
::
|
||||
|
||||
Then create a plugin file:
|
||||
|
||||
```ts [app/plugins/vue-gtag.client.ts]
|
||||
import VueGtag, { trackRouter } from 'vue-gtag-next'
|
||||
|
||||
export default defineNuxtPlugin((nuxtApp) => {
|
||||
nuxtApp.vueApp.use(VueGtag, {
|
||||
property: {
|
||||
id: 'GA_MEASUREMENT_ID',
|
||||
},
|
||||
})
|
||||
trackRouter(useRouter())
|
||||
})
|
||||
```
|
||||
|
||||
## Vue Directives
|
||||
|
||||
Similarly, you can register a custom Vue directive in a plugin.
|
||||
|
||||
```ts twoslash [plugins/my-directive.ts]
|
||||
export default defineNuxtPlugin((nuxtApp) => {
|
||||
nuxtApp.vueApp.directive('focus', {
|
||||
mounted (el) {
|
||||
el.focus()
|
||||
},
|
||||
getSSRProps (binding, vnode) {
|
||||
// you can provide SSR-specific props here
|
||||
return {}
|
||||
},
|
||||
})
|
||||
})
|
||||
```
|
||||
|
||||
::warning
|
||||
If you register a Vue directive, you _must_ register it on both client and server side unless you are only using it when rendering one side. If the directive only makes sense from a client side, you can always move it to `~/plugins/my-directive.client.ts` and provide a 'stub' directive for the server in `~/plugins/my-directive.server.ts`.
|
||||
::
|
||||
|
||||
:read-more{icon="i-simple-icons-vuedotjs" title="Custom Directives on Vue Docs" to="https://vuejs.org/guide/reusability/custom-directives.html" target="_blank"}
|
||||
49
reference/2.directory-structure/1.app/1.utils.md
Normal file
49
reference/2.directory-structure/1.app/1.utils.md
Normal file
@@ -0,0 +1,49 @@
|
||||
---
|
||||
title: 'utils'
|
||||
head.title: 'utils/'
|
||||
description: Use the utils/ directory to auto-import your utility functions throughout your application.
|
||||
navigation.icon: i-vscode-icons-folder-type-tools
|
||||
---
|
||||
|
||||
The main purpose of the [`app/utils/` directory](/docs/4.x/directory-structure/app/utils) is to allow a semantic distinction between your Vue composables and other auto-imported utility functions.
|
||||
|
||||
## Usage
|
||||
|
||||
**Method 1:** Using named export
|
||||
|
||||
```ts twoslash [utils/index.ts]
|
||||
export const { format: formatNumber } = Intl.NumberFormat('en-GB', {
|
||||
notation: 'compact',
|
||||
maximumFractionDigits: 1,
|
||||
})
|
||||
```
|
||||
|
||||
**Method 2:** Using default export
|
||||
|
||||
```ts twoslash [utils/random-entry.ts or utils/randomEntry.ts]
|
||||
// It will be available as randomEntry() (camelCase of file name without extension)
|
||||
export default function (arr: Array<any>) {
|
||||
return arr[Math.floor(Math.random() * arr.length)]
|
||||
}
|
||||
```
|
||||
|
||||
You can now use auto imported utility functions in `.js`, `.ts` and `.vue` files
|
||||
|
||||
```vue [app/app.vue]
|
||||
<template>
|
||||
<p>{{ formatNumber(1234) }}</p>
|
||||
</template>
|
||||
```
|
||||
|
||||
:read-more{to="/docs/4.x/guide/concepts/auto-imports"}
|
||||
|
||||
:link-example{to="/docs/4.x/examples/features/auto-imports"}
|
||||
|
||||
::tip
|
||||
The way `app/utils/` auto-imports work and are scanned is identical to the [`app/composables/`](/docs/4.x/directory-structure/app/composables) directory.
|
||||
::
|
||||
|
||||
::important
|
||||
These utils are only available within the Vue part of your app. :br
|
||||
Only `server/utils` are auto-imported in the [`server/`](/docs/4.x/directory-structure/server#server-utilities) directory.
|
||||
::
|
||||
177
reference/2.directory-structure/1.app/3.app-config.md
Normal file
177
reference/2.directory-structure/1.app/3.app-config.md
Normal file
@@ -0,0 +1,177 @@
|
||||
---
|
||||
title: app.config.ts
|
||||
head.title: 'app.config.ts'
|
||||
description: Expose reactive configuration within your application with the App Config file.
|
||||
navigation.icon: i-vscode-icons-file-type-light-config
|
||||
---
|
||||
|
||||
Nuxt provides an `app/app.config.ts` config file to expose reactive configuration within your application with the ability to update it at runtime within lifecycle or using a nuxt plugin and editing it with HMR (hot-module-replacement).
|
||||
|
||||
You can easily provide runtime app configuration using `app.config.ts` file. It can have either of `.ts`, `.js`, or `.mjs` extensions.
|
||||
|
||||
```ts twoslash [app/app.config.ts]
|
||||
export default defineAppConfig({
|
||||
foo: 'bar',
|
||||
})
|
||||
```
|
||||
|
||||
::caution
|
||||
Do not put any secret values inside `app.config` file. It is exposed to the user client bundle.
|
||||
::
|
||||
|
||||
::note
|
||||
When configuring a custom [`srcDir`](/docs/4.x/api/nuxt-config#srcdir), make sure to place the `app.config` file at the root of the new `srcDir` path.
|
||||
::
|
||||
|
||||
## Usage
|
||||
|
||||
To expose config and environment variables to the rest of your app, you will need to define configuration in `app.config` file.
|
||||
|
||||
```ts twoslash [app/app.config.ts]
|
||||
export default defineAppConfig({
|
||||
theme: {
|
||||
primaryColor: '#ababab',
|
||||
},
|
||||
})
|
||||
```
|
||||
|
||||
We can now universally access `theme` both when server-rendering the page and in the browser using [`useAppConfig`](/docs/4.x/api/composables/use-app-config) composable.
|
||||
|
||||
```vue [app/pages/index.vue]
|
||||
<script setup lang="ts">
|
||||
const appConfig = useAppConfig()
|
||||
|
||||
console.log(appConfig.theme)
|
||||
</script>
|
||||
```
|
||||
|
||||
The [`updateAppConfig`](/docs/4.x/api/utils/update-app-config) utility can be used to update the `app.config` at runtime.
|
||||
|
||||
```vue [app/pages/index.vue]
|
||||
<script setup>
|
||||
const appConfig = useAppConfig() // { foo: 'bar' }
|
||||
|
||||
const newAppConfig = { foo: 'baz' }
|
||||
|
||||
updateAppConfig(newAppConfig)
|
||||
|
||||
console.log(appConfig) // { foo: 'baz' }
|
||||
</script>
|
||||
```
|
||||
|
||||
::read-more{to="/docs/4.x/api/utils/update-app-config"}
|
||||
Read more about the `updateAppConfig` utility.
|
||||
::
|
||||
|
||||
## Typing App Config
|
||||
|
||||
Nuxt tries to automatically generate a TypeScript interface from provided app config so you won't have to type it yourself.
|
||||
|
||||
However, there are some cases where you might want to type it yourself. There are two possible things you might want to type.
|
||||
|
||||
### App Config Input
|
||||
|
||||
`AppConfigInput` might be used by module authors who are declaring what valid _input_ options are when setting app config. This will not affect the type of `useAppConfig()`.
|
||||
|
||||
```ts [index.d.ts]
|
||||
declare module 'nuxt/schema' {
|
||||
interface AppConfigInput {
|
||||
/** Theme configuration */
|
||||
theme?: {
|
||||
/** Primary app color */
|
||||
primaryColor?: string
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// It is always important to ensure you import/export something when augmenting a type
|
||||
export {}
|
||||
```
|
||||
|
||||
### App Config Output
|
||||
|
||||
If you want to type the result of calling [`useAppConfig()`](/docs/4.x/api/composables/use-app-config), then you will want to extend `AppConfig`.
|
||||
|
||||
::warning
|
||||
Be careful when typing `AppConfig` as you will overwrite the types Nuxt infers from your actually defined app config.
|
||||
::
|
||||
|
||||
```ts [index.d.ts]
|
||||
declare module 'nuxt/schema' {
|
||||
interface AppConfig {
|
||||
// This will entirely replace the existing inferred `theme` property
|
||||
theme: {
|
||||
// You might want to type this value to add more specific types than Nuxt can infer,
|
||||
// such as string literal types
|
||||
primaryColor?: 'red' | 'blue'
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// It is always important to ensure you import/export something when augmenting a type
|
||||
export {}
|
||||
```
|
||||
|
||||
## Merging Strategy
|
||||
|
||||
Nuxt uses a custom merging strategy for the `AppConfig` within [the layers](/docs/4.x/getting-started/layers) of your application.
|
||||
|
||||
This strategy is implemented using a [Function Merger](https://github.com/unjs/defu#function-merger), which allows defining a custom merging strategy for every key in `app.config` that has an array as value.
|
||||
|
||||
::note
|
||||
The function merger can only be used in the extended layers and not the main `app.config` in project.
|
||||
::
|
||||
|
||||
Here's an example of how you can use:
|
||||
|
||||
::code-group
|
||||
|
||||
```ts twoslash [layer/app/app.config.ts]
|
||||
export default defineAppConfig({
|
||||
// Default array value
|
||||
array: ['hello'],
|
||||
})
|
||||
```
|
||||
|
||||
```ts twoslash [app/app.config.ts]
|
||||
export default defineAppConfig({
|
||||
// Overwrite default array value by using a merger function
|
||||
array: () => ['bonjour'],
|
||||
})
|
||||
```
|
||||
|
||||
::
|
||||
|
||||
## Known Limitations
|
||||
|
||||
As of Nuxt v3.3, the `app.config.ts` file is shared with Nitro, which results in the following limitations:
|
||||
|
||||
1. You cannot import Vue components directly in `app.config.ts`.
|
||||
2. Some auto-imports are not available in the Nitro context.
|
||||
|
||||
These limitations occur because Nitro processes the app config without full Vue component support.
|
||||
|
||||
While it's possible to use Vite plugins in the Nitro config as a workaround, this approach is not recommended:
|
||||
|
||||
```ts [nuxt.config.ts]
|
||||
export default defineNuxtConfig({
|
||||
nitro: {
|
||||
vite: {
|
||||
plugins: [vue()],
|
||||
},
|
||||
},
|
||||
})
|
||||
```
|
||||
|
||||
::warning
|
||||
Using this workaround may lead to unexpected behavior and bugs. The Vue plugin is one of many that are not available in the Nitro context.
|
||||
::
|
||||
|
||||
Related issues:
|
||||
- [Issue #19858](https://github.com/nuxt/nuxt/issues/19858)
|
||||
- [Issue #19854](https://github.com/nuxt/nuxt/issues/19854)
|
||||
|
||||
::note
|
||||
Nitro v3 will resolve these limitations by removing support for the app config.
|
||||
You can track the progress in [this pull request](https://github.com/nitrojs/nitro/pull/2521).
|
||||
::
|
||||
72
reference/2.directory-structure/1.app/3.app.md
Normal file
72
reference/2.directory-structure/1.app/3.app.md
Normal file
@@ -0,0 +1,72 @@
|
||||
---
|
||||
title: "app.vue"
|
||||
description: "The app.vue file is the main component of your Nuxt application."
|
||||
head.title: "app.vue"
|
||||
navigation.icon: i-vscode-icons-file-type-vue
|
||||
---
|
||||
|
||||
::tip
|
||||
If you have a `app/pages/` directory, the `app.vue` file is optional. Nuxt will automatically include a default `app.vue`, but you can still add your own to customize the structure and content as needed.
|
||||
::
|
||||
|
||||
## Usage
|
||||
|
||||
### Minimal Usage
|
||||
|
||||
With Nuxt, the [`app/pages/`](/docs/4.x/directory-structure/app/pages) directory is optional. If it is not present, Nuxt will not include the [vue-router](https://router.vuejs.org) dependency. This is useful when building a landing page or an application that does not require routing.
|
||||
|
||||
```vue [app/app.vue]
|
||||
<template>
|
||||
<h1>Hello World!</h1>
|
||||
</template>
|
||||
```
|
||||
|
||||
:link-example{to="/docs/4.x/examples/hello-world"}
|
||||
|
||||
### Usage with Pages
|
||||
|
||||
When you have a [`app/pages/`](/docs/4.x/directory-structure/app/pages) directory, you need to use the [`<NuxtPage>`](/docs/4.x/api/components/nuxt-page) component to display the current page:
|
||||
|
||||
```vue [app/app.vue]
|
||||
<template>
|
||||
<NuxtPage />
|
||||
</template>
|
||||
```
|
||||
|
||||
You can also define the common structure of your application directly in `app.vue`. This is useful when you want to include global elements such as a header or footer:
|
||||
|
||||
```vue [app/app.vue]
|
||||
<template>
|
||||
<header>
|
||||
Header content
|
||||
</header>
|
||||
<NuxtPage />
|
||||
<footer>
|
||||
Footer content
|
||||
</footer>
|
||||
</template>
|
||||
```
|
||||
|
||||
::note
|
||||
Remember that `app.vue` acts as the main component of your Nuxt application. Anything you add to it (JS and CSS) will be global and included in every page.
|
||||
::
|
||||
|
||||
::read-more{to="/docs/4.x/directory-structure/app/pages"}
|
||||
Learn more about how to structure your pages using the `app/pages/` directory.
|
||||
::
|
||||
|
||||
### Usage with Layouts
|
||||
|
||||
When your application requires different layouts for different pages, you can use the `app/layouts/` directory with the [`<NuxtLayout>`](/docs/4.x/api/components/nuxt-layout) component. This allows you to define multiple layouts and apply them per page.
|
||||
|
||||
```vue [app/app.vue]
|
||||
<template>
|
||||
<NuxtLayout>
|
||||
<NuxtPage />
|
||||
</NuxtLayout>
|
||||
</template>
|
||||
```
|
||||
|
||||
::read-more{to="/docs/4.x/directory-structure/app/layouts"}
|
||||
Learn more about how to structure your layouts using the `app/layouts/` directory.
|
||||
::
|
||||
53
reference/2.directory-structure/1.app/3.error.md
Normal file
53
reference/2.directory-structure/1.app/3.error.md
Normal file
@@ -0,0 +1,53 @@
|
||||
---
|
||||
title: "error.vue"
|
||||
description: "The error.vue file is the error page in your Nuxt application."
|
||||
head.title: "error.vue"
|
||||
navigation.icon: i-vscode-icons-file-type-vue
|
||||
---
|
||||
|
||||
During the lifespan of your application, some errors may appear unexpectedly at runtime. In such case, we can use the `error.vue` file to override the default error files and display the error nicely.
|
||||
|
||||
```vue [error.vue]
|
||||
<script setup lang="ts">
|
||||
import type { NuxtError } from '#app'
|
||||
|
||||
const props = defineProps<{ error: NuxtError }>()
|
||||
</script>
|
||||
|
||||
<template>
|
||||
<div>
|
||||
<h1>{{ error.status }}</h1>
|
||||
<NuxtLink to="/">Go back home</NuxtLink>
|
||||
</div>
|
||||
</template>
|
||||
```
|
||||
|
||||
::note
|
||||
Although it is called an 'error page' it's not a route and shouldn't be placed in your `~/pages` directory. For the same reason, you shouldn't use `definePageMeta` within this page. That being said, you can still use layouts in the error file, by utilizing the [`NuxtLayout`](/docs/4.x/api/components/nuxt-layout) component and specifying the name of the layout.
|
||||
::
|
||||
|
||||
The error page has a single prop - `error` which contains an error for you to handle.
|
||||
|
||||
The `error` object provides the following fields:
|
||||
```ts
|
||||
interface NuxtError {
|
||||
status: number
|
||||
fatal: boolean
|
||||
unhandled: boolean
|
||||
statusText?: string
|
||||
data?: unknown
|
||||
cause?: unknown
|
||||
}
|
||||
```
|
||||
|
||||
If you have an error with custom fields they will be lost; you should assign them to `data` instead:
|
||||
|
||||
```ts
|
||||
throw createError({
|
||||
status: 404,
|
||||
statusText: 'Page Not Found',
|
||||
data: {
|
||||
myCustomField: true,
|
||||
},
|
||||
})
|
||||
```
|
||||
Reference in New Issue
Block a user