@nuxt/docs-nightly 4.5.2-29756973.d1e6ba2e → 4.5.2-29757143.58d8fcc3
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/1.getting-started/01.introduction.md +2 -2
- package/1.getting-started/17.testing.md +28 -0
- package/2.directory-structure/1.app/1.components.md +3 -101
- package/3.guide/1.concepts/1.rendering.md +1 -1
- package/3.guide/1.concepts/6.server-components.md +193 -0
- package/3.guide/2.best-practices/accessibility.md +155 -0
- package/3.guide/2.best-practices/performance.md +4 -0
- package/3.guide/3.ai/1.mcp.md +81 -10
- package/3.guide/5.recipes/5.mostly-static-sites.md +109 -0
- package/3.guide/6.going-further/1.experimental-features.md +2 -2
- package/3.guide/6.going-further/1.features.md +34 -4
- package/4.api/1.components/12.nuxt-route-announcer.md +2 -0
- package/4.api/1.components/4.nuxt-link.md +2 -0
- package/4.api/1.components/8.nuxt-island.md +5 -1
- package/4.api/2.composables/use-route-announcer.md +2 -0
- package/package.json +1 -1
|
@@ -48,9 +48,9 @@ disable SSR globally with the `ssr: false` option or leverage hybrid rendering b
|
|
|
48
48
|
|
|
49
49
|
### Server engine
|
|
50
50
|
|
|
51
|
-
The Nuxt server engine [Nitro](https://nitro.build/) unlocks new full-stack capabilities.
|
|
51
|
+
The Nuxt server engine, [Nitro](https://nitro.build/), unlocks new full-stack capabilities.
|
|
52
52
|
|
|
53
|
-
In development, it uses Rollup and Node.js workers for your server code and context isolation. It also generates your server API by reading files
|
|
53
|
+
In development, it uses Rollup and Node.js workers for your server code and context isolation. It also generates your server API by reading files from `server/api/` and server middleware from `server/middleware/`.
|
|
54
54
|
|
|
55
55
|
In production, Nitro builds your app and server into one universal `.output` directory. This output is light: minified and removed from any Node.js modules (except polyfills). You can deploy this output on any system supporting JavaScript, from Node.js, Serverless, Workers, Edge-side rendering or purely static.
|
|
56
56
|
|
|
@@ -734,6 +734,13 @@ Please use the options below for the `setup` method.
|
|
|
734
734
|
- Type: `'vitest' | 'jest' | 'cucumber'`
|
|
735
735
|
- Default: `'vitest'`
|
|
736
736
|
|
|
737
|
+
- `logLevel`: Overrides the consola log level for the server subprocess. (use NUXT_TEST_LOG_LEVEL environment variable to override)
|
|
738
|
+
- Type: `number`
|
|
739
|
+
- Default: `1`
|
|
740
|
+
- `captureServerLogs`: Whether to capture server process output instead of inheriting stdio. When `true` (default), server stdout/stderr is suppressed from the console and accessible via `getServerLogs()`. Set to `false` to restore the old inherit-stdio behaviour (useful when debugging a test locally).
|
|
741
|
+
- Type: `boolean`
|
|
742
|
+
- Default: `true`
|
|
743
|
+
|
|
737
744
|
##### Target `host` end-to-end example
|
|
738
745
|
|
|
739
746
|
A common use-case for end-to-end testing is running the tests against a deployed application running in the same environment typically used for Production.
|
|
@@ -793,6 +800,27 @@ const pageUrl = url('/page')
|
|
|
793
800
|
// 'http://localhost:6840/page'
|
|
794
801
|
```
|
|
795
802
|
|
|
803
|
+
#### `getServerLogs()`
|
|
804
|
+
|
|
805
|
+
Returns the lines captured from the server subprocess's stdout/stderr since the last `startServer()` call (or `clearServerLogs()`). Only populated when `captureServerLogs` is `true` (the default).
|
|
806
|
+
|
|
807
|
+
```ts twoslash
|
|
808
|
+
import { expect, it, vi } from 'vitest'
|
|
809
|
+
import { $fetch, clearServerLogs, getServerLogs } from '@nuxt/test-utils/e2e'
|
|
810
|
+
|
|
811
|
+
it('captures console.log output from a server route', async () => {
|
|
812
|
+
clearServerLogs()
|
|
813
|
+
await $fetch('/api/log-test')
|
|
814
|
+
await vi.waitFor(() => {
|
|
815
|
+
expect(getServerLogs().some(line => line.includes('[test] server-log-marker'))).toBe(true)
|
|
816
|
+
})
|
|
817
|
+
})
|
|
818
|
+
```
|
|
819
|
+
|
|
820
|
+
#### `clearServerLogs()`
|
|
821
|
+
|
|
822
|
+
Clears the captured server log lines. Useful between requests when you want to assert only on the logs produced by a specific operation.
|
|
823
|
+
|
|
796
824
|
### Testing in a Browser
|
|
797
825
|
|
|
798
826
|
We provide built-in support using Playwright within `@nuxt/test-utils`, either programmatically or via the Playwright test runner.
|
|
@@ -471,17 +471,7 @@ Standalone server components will always be rendered on the server, also known a
|
|
|
471
471
|
|
|
472
472
|
When their props update, this will result in a network request that will update the rendered HTML in-place.
|
|
473
473
|
|
|
474
|
-
|
|
475
|
-
|
|
476
|
-
```ts twoslash [nuxt.config.ts]
|
|
477
|
-
export default defineNuxtConfig({
|
|
478
|
-
experimental: {
|
|
479
|
-
componentIslands: true,
|
|
480
|
-
},
|
|
481
|
-
})
|
|
482
|
-
```
|
|
483
|
-
|
|
484
|
-
Now you can register server-only components with the `.server` suffix and use them anywhere in your application automatically.
|
|
474
|
+
Register server-only components with the `.server` suffix and use them anywhere in your application automatically.
|
|
485
475
|
|
|
486
476
|
```bash [Directory Structure]
|
|
487
477
|
-| components/
|
|
@@ -506,96 +496,8 @@ Server-only components use [`<NuxtIsland>`](/docs/4.x/api/components/nuxt-island
|
|
|
506
496
|
Server components (and islands) must have a single root element. (HTML comments are considered elements as well.)
|
|
507
497
|
::
|
|
508
498
|
|
|
509
|
-
::
|
|
510
|
-
|
|
511
|
-
::
|
|
512
|
-
|
|
513
|
-
::note
|
|
514
|
-
Server component props come from the request (URL query or body), so treat them as untrusted input. Nuxt rejects the props most likely to leak through unintentionally: a top-level `as` that the island does not declare (an undeclared prop falls through as an attribute onto the island's root), and, with `vue.runtimeCompiler` enabled, a `template` anywhere in the props. Beyond that, avoid feeding props you have not validated into dynamic component resolution (`<component :is>`{lang=vue}, `h()`, `resolveDynamicComponent()`, or a polymorphic `as` / `asChild` prop), since a string can resolve to any registered component or HTML element.
|
|
515
|
-
|
|
516
|
-
Props a component does not declare fall through as attributes onto its single root element, so an island whose root is a polymorphic component (e.g. from `reka-ui` / `@nuxt/ui`) can receive attributes you did not bind. Set `defineOptions({ inheritAttrs: false })`{lang=ts} on such islands, or declare the props you accept.
|
|
517
|
-
|
|
518
|
-
To switch components based on caller input, map a discriminator through an allowlist of imported components rather than passing the raw prop:
|
|
519
|
-
|
|
520
|
-
```vue
|
|
521
|
-
<script setup lang="ts">
|
|
522
|
-
import type { Component } from 'vue'
|
|
523
|
-
import CardA from './CardA.vue'
|
|
524
|
-
import CardB from './CardB.vue'
|
|
525
|
-
|
|
526
|
-
const props = defineProps<{ variant: string }>()
|
|
527
|
-
const allowed: Record<string, Component> = { a: CardA, b: CardB }
|
|
528
|
-
const component = allowed[props.variant] ?? CardA
|
|
529
|
-
</script>
|
|
530
|
-
|
|
531
|
-
<template>
|
|
532
|
-
<component :is="component" />
|
|
533
|
-
</template>
|
|
534
|
-
```
|
|
535
|
-
::
|
|
536
|
-
|
|
537
|
-
::warning
|
|
538
|
-
Be careful when nesting islands within other islands as each island adds some extra overhead.
|
|
539
|
-
::
|
|
540
|
-
|
|
541
|
-
::warning
|
|
542
|
-
Most features for server-only components and island components, such as slots and client components, are only available for single file components.
|
|
543
|
-
::
|
|
544
|
-
|
|
545
|
-
#### Client components within server components
|
|
546
|
-
|
|
547
|
-
::note
|
|
548
|
-
This feature needs `experimental.componentIslands.selectiveClient` within your configuration to be true.
|
|
549
|
-
::
|
|
550
|
-
|
|
551
|
-
You can partially hydrate a component by setting a `nuxt-client` attribute on the component you wish to be loaded client-side.
|
|
552
|
-
|
|
553
|
-
```vue [app/components/ServerWithClient.vue]
|
|
554
|
-
<template>
|
|
555
|
-
<div>
|
|
556
|
-
<HighlightedMarkdown markdown="# Headline" />
|
|
557
|
-
<!-- Counter will be loaded and hydrated client-side -->
|
|
558
|
-
<Counter
|
|
559
|
-
nuxt-client
|
|
560
|
-
:count="5"
|
|
561
|
-
/>
|
|
562
|
-
</div>
|
|
563
|
-
</template>
|
|
564
|
-
```
|
|
565
|
-
|
|
566
|
-
::note
|
|
567
|
-
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.
|
|
568
|
-
::
|
|
569
|
-
|
|
570
|
-
::warning
|
|
571
|
-
Use `nuxt-client` only on local `.vue` SFCs. Built-ins like [`<NuxtLink>`](/docs/4.x/api/components/nuxt-link) skip the islands transform. After client navigation you may see `Failed to locate Teleport target`, or the link disappears with no error. Wrap the built-in in your own `.vue` file and put `nuxt-client` on that wrapper, or drop `nuxt-client` from the built-in. See [#29251](https://github.com/nuxt/nuxt/issues/29251) and [#26002](https://github.com/nuxt/nuxt/issues/26002).
|
|
572
|
-
::
|
|
573
|
-
|
|
574
|
-
#### Server Component Context
|
|
575
|
-
|
|
576
|
-
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.)
|
|
577
|
-
|
|
578
|
-
This means:
|
|
579
|
-
|
|
580
|
-
* A new Vue app will be created server-side to create the `NuxtIslandResponse`.
|
|
581
|
-
* A new 'island context' will be created while rendering the component.
|
|
582
|
-
* 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.
|
|
583
|
-
* 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).
|
|
584
|
-
|
|
585
|
-
::important
|
|
586
|
-
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.
|
|
587
|
-
::
|
|
588
|
-
|
|
589
|
-
::important
|
|
590
|
-
`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.
|
|
591
|
-
|
|
592
|
-
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`).
|
|
593
|
-
::
|
|
594
|
-
|
|
595
|
-
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.
|
|
596
|
-
|
|
597
|
-
::note
|
|
598
|
-
Slots can be interactive and are wrapped within a `<div>` with `display: contents;`
|
|
499
|
+
::read-more{to="/docs/4.x/guide/concepts/server-components"}
|
|
500
|
+
Read more about how islands are rendered, the isolated island context, selective hydration with `nuxt-client`, slots, caching and current limitations in the dedicated server components guide.
|
|
599
501
|
::
|
|
600
502
|
|
|
601
503
|
### Paired with a Client component
|
|
@@ -197,7 +197,7 @@ The different properties you can use are the following:
|
|
|
197
197
|
- `swr: number | boolean`{lang=ts} - Add cache headers to the server response and cache it on the server or reverse proxy for a configurable TTL (time to live). The `node-server` preset of Nitro is able to cache the full response. When the TTL expired, the cached response will be sent while the page will be regenerated in the background. If true is used, a `stale-while-revalidate` header is added without a MaxAge.
|
|
198
198
|
- `isr: number | boolean`{lang=ts} - The behavior is the same as `swr` except that we are able to add the response to the CDN cache on platforms that support this (currently Netlify or Vercel). If `true` is used, the content persists until the next deploy inside the CDN.
|
|
199
199
|
- `prerender: boolean`{lang=ts} - Prerenders routes at build time and includes them in your build as static assets
|
|
200
|
-
- `noScripts: boolean`{lang=ts} - Disables rendering of Nuxt scripts and JS resource hints for sections of your site.
|
|
200
|
+
- `noScripts: boolean`{lang=ts} - Disables rendering of Nuxt scripts and JS resource hints for sections of your site. Read more about [`noScripts`](/docs/4.x/guide/going-further/features#noscripts).
|
|
201
201
|
- `appMiddleware: string | string[] | Record<string, boolean>`{lang=ts} - Allows you to define middleware that should or should not run for page paths within the Vue app part of your application (that is, not your Nitro routes)
|
|
202
202
|
|
|
203
203
|
::note
|
|
@@ -0,0 +1,193 @@
|
|
|
1
|
+
---
|
|
2
|
+
title: 'Server Components'
|
|
3
|
+
description: Render individual components on the server only, keeping their JavaScript out of your client bundle.
|
|
4
|
+
---
|
|
5
|
+
|
|
6
|
+
Nuxt renders your app on the server by default, but then it ships the JavaScript for every component to the browser and hydrates the whole page. For content-heavy components (markdown rendering, syntax highlighting, CMS output) that never change on the client, this is wasted work: the user downloads, parses and executes code whose only job is to reproduce HTML that is already on the page.
|
|
7
|
+
|
|
8
|
+
Server components (also called island components) invert this. A server component is rendered on the server, its HTML is embedded in the page, and none of its JavaScript is sent to the client. Its dependencies (a markdown parser, a highlighting library) stay on the server too.
|
|
9
|
+
|
|
10
|
+
::tip{icon="i-lucide-newspaper" to="https://roe.dev/blog/nuxt-server-components" target="_blank"}
|
|
11
|
+
Read Daniel Roe's guide to Nuxt Server Components.
|
|
12
|
+
::
|
|
13
|
+
|
|
14
|
+
:video-accordion{title="Watch Learn Vue video about Nuxt Server Components" videoId="u1yyXe86xJM"}
|
|
15
|
+
|
|
16
|
+
## Enabling Server Components
|
|
17
|
+
|
|
18
|
+
Component islands are controlled by [`experimental.componentIslands`](/docs/4.x/guide/going-further/experimental-features#componentislands). The default value is `'auto'`, which enables the feature automatically as soon as your app contains a server component or island, so in most cases you do not need any configuration.
|
|
19
|
+
|
|
20
|
+
Set the option explicitly if you want remote islands or selective client hydration:
|
|
21
|
+
|
|
22
|
+
```ts twoslash [nuxt.config.ts]
|
|
23
|
+
export default defineNuxtConfig({
|
|
24
|
+
experimental: {
|
|
25
|
+
componentIslands: {
|
|
26
|
+
selectiveClient: true, // or 'deep', to enable `nuxt-client`
|
|
27
|
+
remoteIsland: false, // allow rendering islands from a remote source
|
|
28
|
+
},
|
|
29
|
+
},
|
|
30
|
+
})
|
|
31
|
+
```
|
|
32
|
+
|
|
33
|
+
::read-more{icon="i-simple-icons-github" to="https://github.com/nuxt/nuxt/issues/19772" target="_blank"}
|
|
34
|
+
Server components are still marked experimental. You can follow the roadmap on GitHub.
|
|
35
|
+
::
|
|
36
|
+
|
|
37
|
+
## `.server.vue` Components
|
|
38
|
+
|
|
39
|
+
Add the `.server` suffix to a component to make it a standalone server component:
|
|
40
|
+
|
|
41
|
+
```bash [Directory Structure]
|
|
42
|
+
-| app/
|
|
43
|
+
---| components/
|
|
44
|
+
-----| HighlightedMarkdown.server.vue
|
|
45
|
+
```
|
|
46
|
+
|
|
47
|
+
Use it like any other component:
|
|
48
|
+
|
|
49
|
+
```vue [app/pages/example.vue]
|
|
50
|
+
<template>
|
|
51
|
+
<div>
|
|
52
|
+
<!--
|
|
53
|
+
rendered on the server; the markdown parsing and highlighting
|
|
54
|
+
libraries are not included in your client bundle
|
|
55
|
+
-->
|
|
56
|
+
<HighlightedMarkdown markdown="# Headline" />
|
|
57
|
+
</div>
|
|
58
|
+
</template>
|
|
59
|
+
```
|
|
60
|
+
|
|
61
|
+
Components inside `~/components/islands/` are also registered as islands and can be rendered with [`<NuxtIsland>`](/docs/4.x/api/components/nuxt-island) directly, for example `<NuxtIsland name="MyIsland" />` for `~/components/islands/MyIsland.vue`.
|
|
62
|
+
|
|
63
|
+
You can also pair a `.server.vue` component with a `.client.vue` component of the same name for [separate server and client implementations](/docs/4.x/directory-structure/app/components#paired-with-a-client-component). In that case the component is not an island: the client half hydrates normally.
|
|
64
|
+
|
|
65
|
+
::warning
|
|
66
|
+
Server components (and islands) must have a single root element. (HTML comments are considered elements as well.)
|
|
67
|
+
::
|
|
68
|
+
|
|
69
|
+
## How Islands Are Rendered
|
|
70
|
+
|
|
71
|
+
Server components use [`<NuxtIsland>`](/docs/4.x/api/components/nuxt-island) under the hood. Rendering an island issues a request to a dedicated island endpoint, which:
|
|
72
|
+
|
|
73
|
+
- creates a **new, isolated Vue app** on the server to render just that component
|
|
74
|
+
- creates an 'island context' that you can access via `nuxtApp.ssrContext.islandContext` inside the island
|
|
75
|
+
- runs your plugins again, unless they set `env: { islands: false }` (object-syntax plugins)
|
|
76
|
+
|
|
77
|
+
Because the island is isolated from the rest of your app:
|
|
78
|
+
|
|
79
|
+
- you cannot share state (provide/inject, Pinia, `useState`) between the page and the island; pass data via props instead
|
|
80
|
+
- [`useRoute()`](/docs/4.x/api/composables/use-route) inside an island reflects the island's own request, not the page the user is on. If an island needs route information, pass it in explicitly, either as props or via the `context` prop on `<NuxtIsland>` (read inside the island from `nuxtApp.ssrContext.islandContext`)
|
|
81
|
+
- route middleware does not run when rendering islands
|
|
82
|
+
|
|
83
|
+
Props are serialized and sent as **GET query parameters**. This makes island responses cacheable, but it also means:
|
|
84
|
+
|
|
85
|
+
- props must be JSON-serializable
|
|
86
|
+
- props are limited by URL length, so avoid passing large amounts of data
|
|
87
|
+
- props may be visible in server access logs, CDN caches and `Referer` headers
|
|
88
|
+
|
|
89
|
+
::note
|
|
90
|
+
Because props come from the request (URL query or body), treat them as untrusted input. Nuxt rejects the props most likely to leak through unintentionally: a top-level `as` that the island does not declare (an undeclared prop falls through as an attribute onto the island's root), and, with `vue.runtimeCompiler` enabled, a `template` anywhere in the props. Beyond that, avoid feeding props you have not validated into dynamic component resolution (`<component :is>`{lang=vue}, `h()`, `resolveDynamicComponent()`, or a polymorphic `as` / `asChild` prop), since a string can resolve to any registered component or HTML element.
|
|
91
|
+
|
|
92
|
+
Props a component does not declare fall through as attributes onto its single root element, so an island whose root is a polymorphic component (e.g. from `reka-ui` / `@nuxt/ui`) can receive attributes you did not bind. Set `defineOptions({ inheritAttrs: false })`{lang=ts} on such islands, or declare the props you accept.
|
|
93
|
+
|
|
94
|
+
To switch components based on caller input, map a discriminator through an allowlist of imported components rather than passing the raw prop:
|
|
95
|
+
|
|
96
|
+
```vue
|
|
97
|
+
<script setup lang="ts">
|
|
98
|
+
import type { Component } from 'vue'
|
|
99
|
+
import CardA from './CardA.vue'
|
|
100
|
+
import CardB from './CardB.vue'
|
|
101
|
+
|
|
102
|
+
const props = defineProps<{ variant: string }>()
|
|
103
|
+
const allowed: Record<string, Component> = { a: CardA, b: CardB }
|
|
104
|
+
const component = allowed[props.variant] ?? CardA
|
|
105
|
+
</script>
|
|
106
|
+
|
|
107
|
+
<template>
|
|
108
|
+
<component :is="component" />
|
|
109
|
+
</template>
|
|
110
|
+
```
|
|
111
|
+
::
|
|
112
|
+
|
|
113
|
+
Changing an island's props triggers a network request that re-renders the component on the server and updates its HTML in place.
|
|
114
|
+
|
|
115
|
+
::read-more{to="/docs/4.x/api/components/nuxt-island"}
|
|
116
|
+
Read the full `<NuxtIsland>` API documentation, including props, slots, events and known limitations.
|
|
117
|
+
::
|
|
118
|
+
|
|
119
|
+
## Selective Hydration with `nuxt-client`
|
|
120
|
+
|
|
121
|
+
An island is static by default, but you can hydrate individual components inside it by adding the `nuxt-client` attribute. This requires `experimental.componentIslands.selectiveClient` to be enabled.
|
|
122
|
+
|
|
123
|
+
```vue [app/components/ServerWithClient.server.vue]
|
|
124
|
+
<template>
|
|
125
|
+
<div>
|
|
126
|
+
<HighlightedMarkdown markdown="# Headline" />
|
|
127
|
+
<!-- Counter will be loaded and hydrated client-side -->
|
|
128
|
+
<Counter
|
|
129
|
+
nuxt-client
|
|
130
|
+
:count="5"
|
|
131
|
+
/>
|
|
132
|
+
</div>
|
|
133
|
+
</template>
|
|
134
|
+
```
|
|
135
|
+
|
|
136
|
+
The component marked with `nuxt-client` is server-rendered as part of the island, then hydrated by the main client app. Only its chunk is shipped to the client; the rest of the island remains static.
|
|
137
|
+
|
|
138
|
+
Setting `selectiveClient: 'deep'` additionally allows passing slots to `nuxt-client` components. Those slots are rendered on the server and are **not interactive** on the client.
|
|
139
|
+
|
|
140
|
+
::warning
|
|
141
|
+
Use `nuxt-client` only on local `.vue` SFCs. Built-ins like [`<NuxtLink>`](/docs/4.x/api/components/nuxt-link) skip the islands transform. After client navigation you may see `Failed to locate Teleport target`, or the link disappears with no error. Wrap the built-in in your own `.vue` file and put `nuxt-client` on that wrapper. See [#29251](https://github.com/nuxt/nuxt/issues/29251) and [#26002](https://github.com/nuxt/nuxt/issues/26002).
|
|
142
|
+
::
|
|
143
|
+
|
|
144
|
+
## Slots
|
|
145
|
+
|
|
146
|
+
Slots can be passed to an island component if declared in the island. Slot content is provided by the parent, so it belongs to the main client app and **is** interactive (it is wrapped in a `<div>` with `display: contents;`).
|
|
147
|
+
|
|
148
|
+
`<NuxtIsland>` reserves the `#fallback` slot to specify content rendered before the island loads (when `lazy` is set) or when fetching the island fails.
|
|
149
|
+
|
|
150
|
+
## The Client Navigation Round Trip
|
|
151
|
+
|
|
152
|
+
On the initial server-rendered page load, islands are rendered inline and there is no extra request. On **client-side navigation**, however, each island on the destination page must be fetched from the server (you can see these requests in the network tab). This has real costs:
|
|
153
|
+
|
|
154
|
+
- islands block on a network round trip during navigation, unless you pass the `lazy` prop (with a `#fallback` slot) to render them non-blockingly
|
|
155
|
+
- an app with many islands per page makes many requests per navigation
|
|
156
|
+
|
|
157
|
+
Islands work best on pages that are reached by full page loads (content and marketing pages) or when their number per page is small. If a component needs to update frequently on the client, an island is probably the wrong tool.
|
|
158
|
+
|
|
159
|
+
## Prerendering and Caching
|
|
160
|
+
|
|
161
|
+
Islands play well with static and cached rendering:
|
|
162
|
+
|
|
163
|
+
- during prerendering (`nuxt generate` or `prerender` route rules), island responses are cached, so identical islands (same name, props and context) are rendered once and reused
|
|
164
|
+
- because props travel as GET query parameters, island responses can also be cached by your server or CDN at the island endpoint level
|
|
165
|
+
- two instances of the same island with the same props share a single server render and payload entry
|
|
166
|
+
|
|
167
|
+
Note that island responses being keyed only on name, props and context is exactly what keeps them cacheable independently of the page they appear on; this is also why they cannot see the current route (see above).
|
|
168
|
+
|
|
169
|
+
If you are building a mostly-static site, islands combine well with `prerender` and `noScripts` route rules.
|
|
170
|
+
|
|
171
|
+
::read-more{to="/docs/4.x/guide/recipes/mostly-static-sites"}
|
|
172
|
+
See the mostly-static site recipe for combining prerendering, `noScripts`, islands and lazy hydration.
|
|
173
|
+
::
|
|
174
|
+
|
|
175
|
+
One interaction to be aware of: island slots and `nuxt-client` components rely on a small inline script to relocate teleported content into place before hydration. On routes rendered with [`noScripts`](/docs/4.x/guide/going-further/features#noscripts), that script is omitted, so fully interactive `nuxt-client` components will not hydrate there. Plain static islands are unaffected.
|
|
176
|
+
|
|
177
|
+
## Current Limitations
|
|
178
|
+
|
|
179
|
+
Server components are experimental, and some rough edges are tracked in open issues:
|
|
180
|
+
|
|
181
|
+
- Most features for server-only and island components, such as slots and `nuxt-client` components, are only available for single file components.
|
|
182
|
+
- Global styles of your application are sent with each island response, and island assets can be loaded twice in some setups ([#29591](https://github.com/nuxt/nuxt/issues/29591)).
|
|
183
|
+
- Using islands can significantly increase the number of chunks generated at build time ([#34855](https://github.com/nuxt/nuxt/issues/34855)).
|
|
184
|
+
- `:slotted()` styles are ignored in server component slots ([#31510](https://github.com/nuxt/nuxt/issues/31510)).
|
|
185
|
+
- Template refs cannot reference elements inside a server component from the parent ([#31512](https://github.com/nuxt/nuxt/issues/31512)).
|
|
186
|
+
- `inject`/`provide` does not cross the island boundary, so injecting from the page into a standalone server component does not work ([#22751](https://github.com/nuxt/nuxt/issues/22751)).
|
|
187
|
+
- Server components rendered via the auto-generated wrapper do not expose load and error events; use `<NuxtIsland>` directly if you need its `error` event and `refresh()` method ([#25744](https://github.com/nuxt/nuxt/issues/25744)).
|
|
188
|
+
- [`useId`](https://vuejs.org/api/composition-api-helpers#useid) has known limitations inside islands; see the [`<NuxtIsland>` documentation](/docs/4.x/api/components/nuxt-island#known-limitations).
|
|
189
|
+
- Each nested island adds extra overhead, so be careful when nesting islands within other islands.
|
|
190
|
+
|
|
191
|
+
::read-more{to="/docs/4.x/directory-structure/app/components#server-components"}
|
|
192
|
+
Read more about server component file conventions in the components directory documentation.
|
|
193
|
+
::
|
|
@@ -0,0 +1,155 @@
|
|
|
1
|
+
---
|
|
2
|
+
navigation.title: 'Accessibility'
|
|
3
|
+
title: Nuxt accessibility
|
|
4
|
+
description: Best practices for accessibility in Nuxt apps.
|
|
5
|
+
---
|
|
6
|
+
|
|
7
|
+
Most of what makes an app accessible is not specific to Nuxt: color contrast, form semantics, and ARIA work the same here as in any Vue or plain HTML application, and the [resources](#useful-resources) at the end of this guide cover them well.
|
|
8
|
+
|
|
9
|
+
What Nuxt does change is navigation. Once your app has hydrated, it routes on the client, so the document is never reloaded and the browser no longer announces a new page or resets focus for you. Nuxt ships with features that fill some of that gap, and a few conventions cover the rest. This guide outlines best practices for handling that.
|
|
10
|
+
|
|
11
|
+
::tip
|
|
12
|
+
[`@nuxt/a11y`](https://github.com/nuxt/a11y) surfaces accessibility problems in your components while you develop, alongside the practices below. It is in alpha, so expect its API to change.
|
|
13
|
+
::
|
|
14
|
+
|
|
15
|
+
## Route Announcements
|
|
16
|
+
|
|
17
|
+
Screen readers announce a full page load by themselves, but they have no way of knowing that a client-side navigation happened. [`<NuxtRouteAnnouncer>`](/docs/4.x/api/components/nuxt-route-announcer) solves this by rendering a hidden live region and writing the new page title into it after every navigation:
|
|
18
|
+
|
|
19
|
+
```vue [app.vue]
|
|
20
|
+
<template>
|
|
21
|
+
<NuxtRouteAnnouncer />
|
|
22
|
+
<NuxtPage />
|
|
23
|
+
</template>
|
|
24
|
+
```
|
|
25
|
+
|
|
26
|
+
The announcer reads the title that Unhead rendered, so it is only as useful as your titles are. If two routes share the same `<title>`, users hear nothing on the way between them.
|
|
27
|
+
|
|
28
|
+
When you need to announce something else, or to change how urgently it is announced, use [`useRouteAnnouncer`](/docs/4.x/api/composables/use-route-announcer):
|
|
29
|
+
|
|
30
|
+
```vue [app/pages/search.vue]
|
|
31
|
+
<script setup lang="ts">
|
|
32
|
+
const { set } = useRouteAnnouncer()
|
|
33
|
+
const { data: results } = await useFetch('/api/search')
|
|
34
|
+
|
|
35
|
+
watch(results, (results) => {
|
|
36
|
+
set(`${results?.length ?? 0} results found`)
|
|
37
|
+
})
|
|
38
|
+
</script>
|
|
39
|
+
```
|
|
40
|
+
|
|
41
|
+
:read-more{title="NuxtRouteAnnouncer" to="/docs/4.x/api/components/nuxt-route-announcer"}
|
|
42
|
+
|
|
43
|
+
For in-page updates that are not navigations, such as form validation or toasts, use [`<NuxtAnnouncer>`](/docs/4.x/api/components/nuxt-announcer) with [`useAnnouncer`](/docs/4.x/api/composables/use-announcer) instead.
|
|
44
|
+
|
|
45
|
+
## Page Titles
|
|
46
|
+
|
|
47
|
+
Because the route announcer follows the document title, giving every route a distinct title is the single most valuable thing you can do. Set a global template in `app.vue` and let each page fill in its own part:
|
|
48
|
+
|
|
49
|
+
```vue [app.vue]
|
|
50
|
+
<script setup lang="ts">
|
|
51
|
+
useHead({
|
|
52
|
+
titleTemplate: title => title ? `${title} - Nuxt` : 'Nuxt',
|
|
53
|
+
})
|
|
54
|
+
</script>
|
|
55
|
+
```
|
|
56
|
+
|
|
57
|
+
```vue [app/pages/about.vue]
|
|
58
|
+
<script setup lang="ts">
|
|
59
|
+
useHead({
|
|
60
|
+
title: 'About us',
|
|
61
|
+
})
|
|
62
|
+
</script>
|
|
63
|
+
```
|
|
64
|
+
|
|
65
|
+
If your titles come from route metadata rather than from the page itself, you can read [`definePageMeta`](/docs/4.x/directory-structure/app/pages#page-metadata) values from [`useRoute`](/docs/4.x/api/composables/use-route) in a layout.
|
|
66
|
+
|
|
67
|
+
:read-more{title="SEO and Meta" to="/docs/4.x/getting-started/seo-meta#dynamic-title"}
|
|
68
|
+
|
|
69
|
+
## Links
|
|
70
|
+
|
|
71
|
+
Use [`<NuxtLink>`](/docs/4.x/api/components/nuxt-link) for in-app navigation. It renders a real `<a href="...">`, which means it is focusable, appears in the tab order, and works with middle-click and "open in new tab", all of which you would have to reimplement on a `<div>` with a `@click` handler calling `navigateTo`.
|
|
72
|
+
|
|
73
|
+
```vue
|
|
74
|
+
<template>
|
|
75
|
+
<NuxtLink to="/about">About page</NuxtLink>
|
|
76
|
+
</template>
|
|
77
|
+
```
|
|
78
|
+
|
|
79
|
+
In a menu or a set of breadcrumbs, the link matching the current route already exposes `aria-current="page"`, so assistive technology can tell which item you are on. Where a different token describes the relationship better, such as a step in a multi-page form, set [`ariaCurrentValue`](/docs/4.x/api/components/nuxt-link#routerlink):
|
|
80
|
+
|
|
81
|
+
```vue
|
|
82
|
+
<template>
|
|
83
|
+
<NuxtLink
|
|
84
|
+
to="/checkout/payment"
|
|
85
|
+
aria-current-value="step"
|
|
86
|
+
>Payment</NuxtLink>
|
|
87
|
+
</template>
|
|
88
|
+
```
|
|
89
|
+
|
|
90
|
+
Links to files in your `public/` directory, or to another app on the same origin, are not routes that Vue Router knows about. Mark them as [`external`](/docs/4.x/api/components/nuxt-link#handling-static-file-and-cross-app-links) so the browser performs a real navigation instead of failing to match a route.
|
|
91
|
+
|
|
92
|
+
:read-more{title="NuxtLink" to="/docs/4.x/api/components/nuxt-link"}
|
|
93
|
+
|
|
94
|
+
## Focus Management
|
|
95
|
+
|
|
96
|
+
After a client-side navigation, focus stays where it was, which is usually the link the user just activated. Vue Router does not move it and neither does Nuxt, so a keyboard user can end up tabbing through the whole header again to reach the content that just changed.
|
|
97
|
+
|
|
98
|
+
A skip link as the first tab stop of your app is the conventional fix, and it helps on the initial page load too:
|
|
99
|
+
|
|
100
|
+
```vue [app.vue]
|
|
101
|
+
<template>
|
|
102
|
+
<a
|
|
103
|
+
class="skip-link"
|
|
104
|
+
href="#main"
|
|
105
|
+
>Skip to main content</a>
|
|
106
|
+
<AppHeader />
|
|
107
|
+
<main
|
|
108
|
+
id="main"
|
|
109
|
+
tabindex="-1"
|
|
110
|
+
>
|
|
111
|
+
<NuxtPage />
|
|
112
|
+
</main>
|
|
113
|
+
</template>
|
|
114
|
+
|
|
115
|
+
<style>
|
|
116
|
+
.skip-link {
|
|
117
|
+
position: absolute;
|
|
118
|
+
left: -9999px;
|
|
119
|
+
}
|
|
120
|
+
.skip-link:focus {
|
|
121
|
+
position: static;
|
|
122
|
+
}
|
|
123
|
+
</style>
|
|
124
|
+
```
|
|
125
|
+
|
|
126
|
+
`<main>` is not focusable on its own, so it needs `tabindex="-1"` to accept focus from the skip link or from a script. Use `-1` rather than a positive value, which would move the element in the tab order and surprise everyone else.
|
|
127
|
+
|
|
128
|
+
If it suits your app, you can go further and move focus to the main region after every navigation from a plugin:
|
|
129
|
+
|
|
130
|
+
```ts [app/plugins/focus-main.client.ts]
|
|
131
|
+
export default defineNuxtPlugin(() => {
|
|
132
|
+
useRouter().afterEach((to, from) => {
|
|
133
|
+
if (to.path === from.path) {
|
|
134
|
+
return
|
|
135
|
+
}
|
|
136
|
+
nextTick(() => document.getElementById('main')?.focus())
|
|
137
|
+
})
|
|
138
|
+
})
|
|
139
|
+
```
|
|
140
|
+
|
|
141
|
+
::tip
|
|
142
|
+
Navigate around your app with the keyboard alone. Tabbing from the skip link into `<main>` after a couple of navigations will surface most focus problems quickly.
|
|
143
|
+
::
|
|
144
|
+
|
|
145
|
+
## Scroll Behavior
|
|
146
|
+
|
|
147
|
+
Nuxt scrolls to the top on a new route, restores the previous position when the user goes back, and scrolls to hash targets. If you need something different, such as smooth scrolling or a different offset, configure [`scrollBehaviorType`](/docs/4.x/guide/recipes/custom-routing#scroll-behavior-for-hash-links) or write your own `scrollBehavior` in [`router.options.ts`](/docs/4.x/guide/recipes/custom-routing#router-options). Bear in mind that smooth scrolling should respect the user's `prefers-reduced-motion` setting.
|
|
148
|
+
|
|
149
|
+
:read-more{title="Custom routing" to="/docs/4.x/guide/recipes/custom-routing"}
|
|
150
|
+
|
|
151
|
+
## Useful Resources
|
|
152
|
+
|
|
153
|
+
- [Web Accessibility Initiative (WAI)](https://www.w3.org/WAI/)
|
|
154
|
+
- [MDN: Accessibility](https://developer.mozilla.org/en-US/docs/Web/Accessibility)
|
|
155
|
+
- [Understanding WCAG 2.1](https://www.w3.org/WAI/WCAG21/Understanding/)
|
|
@@ -111,6 +111,10 @@ To optimize your app, you may want to delay the hydration of some components unt
|
|
|
111
111
|
|
|
112
112
|
:read-more{title="Lazy hydration" to="/docs/4.x/directory-structure/app/components#delayed-or-lazy-hydration"}
|
|
113
113
|
|
|
114
|
+
For content and marketing sites that need little or no client-side interactivity, you can go further and combine prerendering, the `noScripts` route rule, [server components](/docs/4.x/guide/concepts/server-components) and lazy hydration to ship near-zero JavaScript.
|
|
115
|
+
|
|
116
|
+
:read-more{title="Mostly-static sites" to="/docs/4.x/guide/recipes/mostly-static-sites"}
|
|
117
|
+
|
|
114
118
|
### Fetching data
|
|
115
119
|
|
|
116
120
|
To avoid fetching same data twice (once on the server and once on client) Nuxt provides [`useFetch`](/docs/4.x/api/composables/use-fetch) and [`useAsyncData`](/docs/4.x/api/composables/use-async-data). They ensure that if an API call is made on the server, the data is forwarded to the client in the payload instead of being fetched again.
|
package/3.guide/3.ai/1.mcp.md
CHANGED
|
@@ -26,7 +26,7 @@ The Nuxt MCP server provides the following tools organized by category:
|
|
|
26
26
|
|
|
27
27
|
### Documentation
|
|
28
28
|
|
|
29
|
-
- **`list_documentation_pages`**: Lists all available Nuxt documentation pages with their categories and basic information. Supports version filtering (3.x, 4.x, or all)
|
|
29
|
+
- **`list_documentation_pages`**: Lists all available Nuxt documentation pages with their categories and basic information. Supports version filtering (3.x, 4.x, 5.x, or all)
|
|
30
30
|
- **`get_documentation_page`**: Retrieves documentation page content and details by path
|
|
31
31
|
- **`get_getting_started_guide`**: Gets the getting started guide for a specific Nuxt version
|
|
32
32
|
|
|
@@ -40,6 +40,15 @@ The Nuxt MCP server provides the following tools organized by category:
|
|
|
40
40
|
- **`list_deploy_providers`**: Lists all deployment providers and hosting platforms for Nuxt applications
|
|
41
41
|
- **`get_deploy_provider`**: Retrieves deployment provider details and instructions by path
|
|
42
42
|
|
|
43
|
+
### Modules
|
|
44
|
+
|
|
45
|
+
- **`list_modules`**: Lists all available Nuxt modules with optional filtering and sorting by downloads, stars, or date
|
|
46
|
+
- **`get_module`**: Retrieves complete details about a specific module including README, compatibility, maintainers, and stats
|
|
47
|
+
|
|
48
|
+
### Changelog
|
|
49
|
+
|
|
50
|
+
- **`get_changelog`**: Retrieves the latest releases from Nuxt core and official modules
|
|
51
|
+
|
|
43
52
|
## Prompts
|
|
44
53
|
|
|
45
54
|
The Nuxt MCP server provides guided prompts for common workflows:
|
|
@@ -85,7 +94,7 @@ The Nuxt connector will appear in the composer's "Developer mode" tool later dur
|
|
|
85
94
|
Add the server using the CLI command:
|
|
86
95
|
|
|
87
96
|
```bash
|
|
88
|
-
claude mcp add --transport http nuxt
|
|
97
|
+
claude mcp add --transport http nuxt https://nuxt.com/mcp
|
|
89
98
|
```
|
|
90
99
|
|
|
91
100
|
### Claude Desktop
|
|
@@ -141,13 +150,30 @@ Or manually create/update `.cursor/mcp.json` in your project root:
|
|
|
141
150
|
}
|
|
142
151
|
```
|
|
143
152
|
|
|
153
|
+
### Gemini CLI
|
|
154
|
+
|
|
155
|
+
1. Locate your Gemini CLI configuration file (usually `~/.gemini/settings.json` or as specified in your environment).
|
|
156
|
+
2. Add the following configuration to your `mcpServers` object:
|
|
157
|
+
|
|
158
|
+
```json [~/.gemini/settings.json]
|
|
159
|
+
{
|
|
160
|
+
"mcpServers": {
|
|
161
|
+
"nuxt": {
|
|
162
|
+
"httpUrl": "https://nuxt.com/mcp"
|
|
163
|
+
}
|
|
164
|
+
}
|
|
165
|
+
}
|
|
166
|
+
```
|
|
167
|
+
|
|
168
|
+
1. Restart your terminal session or reload the CLI. The Nuxt MCP server tools will now be available for use.
|
|
169
|
+
|
|
144
170
|
### Le Chat Mistral
|
|
145
171
|
|
|
146
172
|
1. Navigate to "Intelligence" > "Connectors"
|
|
147
173
|
2. Click on "Add Connector" button, then select "Custom MCP Connector"
|
|
148
174
|
3. Create your Custom MCP Connector:
|
|
149
|
-
- Connector Name
|
|
150
|
-
-
|
|
175
|
+
- Connector Name: `Nuxt`
|
|
176
|
+
- Server URL: `https://nuxt.com/mcp`
|
|
151
177
|
|
|
152
178
|
### Visual Studio Code
|
|
153
179
|
|
|
@@ -207,6 +233,55 @@ To verify the MCP server is configured correctly:
|
|
|
207
233
|
|
|
208
234
|
For more information on using MCP with GitHub Copilot coding agent, see [Extend coding agent with MCP](https://docs.github.com/en/copilot/how-tos/use-copilot-agents/coding-agent/extend-coding-agent-with-mcp).
|
|
209
235
|
|
|
236
|
+
### GitHub Copilot CLI
|
|
237
|
+
|
|
238
|
+
1. Start GitHub Copilot CLI in interactive mode.
|
|
239
|
+
2. Run `/mcp add`.
|
|
240
|
+
3. Fill in the configuration form with:
|
|
241
|
+
- **Server Name**: `nuxt`
|
|
242
|
+
- **Server Type**: `HTTP`
|
|
243
|
+
- **URL**: `https://nuxt.com/mcp`
|
|
244
|
+
- **HTTP Headers**: leave empty
|
|
245
|
+
- **Tools**: `*`
|
|
246
|
+
4. Press <kbd>Ctrl</kbd>+<kbd>S</kbd> to save. The MCP server is available immediately without restarting the CLI.
|
|
247
|
+
|
|
248
|
+
Or add the server directly to `~/.copilot/mcp-config.json`:
|
|
249
|
+
|
|
250
|
+
```json [~/.copilot/mcp-config.json]
|
|
251
|
+
{
|
|
252
|
+
"mcpServers": {
|
|
253
|
+
"nuxt": {
|
|
254
|
+
"type": "http",
|
|
255
|
+
"url": "https://nuxt.com/mcp",
|
|
256
|
+
"tools": ["*"]
|
|
257
|
+
}
|
|
258
|
+
}
|
|
259
|
+
}
|
|
260
|
+
```
|
|
261
|
+
|
|
262
|
+
Use `/mcp show` to confirm the server is configured and available.
|
|
263
|
+
|
|
264
|
+
For more details, see [Adding MCP servers for GitHub Copilot CLI](https://docs.github.com/en/copilot/how-tos/copilot-cli/customize-copilot/add-mcp-servers).
|
|
265
|
+
|
|
266
|
+
### Google Antigravity
|
|
267
|
+
|
|
268
|
+
1. Open the MCP store via the "..." dropdown at the top of the editor's agent panel.
|
|
269
|
+
2. Click on "Manage MCP Servers"
|
|
270
|
+
3. Click on "View raw config"
|
|
271
|
+
4. Modify the `mcp_config.json` with your custom MCP server configuration:
|
|
272
|
+
|
|
273
|
+
```json [mcp_config.json]
|
|
274
|
+
{
|
|
275
|
+
"mcpServers": {
|
|
276
|
+
"nuxt": {
|
|
277
|
+
"serverUrl": "https://nuxt.com/mcp"
|
|
278
|
+
}
|
|
279
|
+
}
|
|
280
|
+
}
|
|
281
|
+
```
|
|
282
|
+
|
|
283
|
+
1. Return to the "Manage MCP Servers" tab and click "Refresh". The Nuxt MCP server should now appear.
|
|
284
|
+
|
|
210
285
|
### Windsurf
|
|
211
286
|
|
|
212
287
|
1. Open Windsurf and navigate to "Settings" > "Windsurf Settings" > "Cascade"
|
|
@@ -217,8 +292,7 @@ For more information on using MCP with GitHub Copilot coding agent, see [Extend
|
|
|
217
292
|
{
|
|
218
293
|
"mcpServers": {
|
|
219
294
|
"nuxt": {
|
|
220
|
-
"
|
|
221
|
-
"url": "https://nuxt.com/mcp"
|
|
295
|
+
"serverUrl": "https://nuxt.com/mcp"
|
|
222
296
|
}
|
|
223
297
|
}
|
|
224
298
|
}
|
|
@@ -234,10 +308,7 @@ For more information on using MCP with GitHub Copilot coding agent, see [Extend
|
|
|
234
308
|
{
|
|
235
309
|
"context_servers": {
|
|
236
310
|
"nuxt": {
|
|
237
|
-
"
|
|
238
|
-
"command": "npx",
|
|
239
|
-
"args": ["mcp-remote", "https://nuxt.com/mcp"],
|
|
240
|
-
"env": {}
|
|
311
|
+
"url": "https://nuxt.com/mcp"
|
|
241
312
|
}
|
|
242
313
|
}
|
|
243
314
|
}
|
|
@@ -0,0 +1,109 @@
|
|
|
1
|
+
---
|
|
2
|
+
title: 'Mostly-Static Sites'
|
|
3
|
+
description: 'Ship near-zero JavaScript for content and marketing sites while keeping islands of interactivity.'
|
|
4
|
+
---
|
|
5
|
+
|
|
6
|
+
By default, Nuxt server-renders a page and then hydrates all of it. For a page that is 95% static text and images, that hydration JavaScript is mostly overhead: it delays interactivity and competes with your content for bandwidth and CPU, which can hurt metrics like mobile PageSpeed scores.
|
|
7
|
+
|
|
8
|
+
Nuxt has the pieces to avoid paying that cost where you don't need it. This recipe combines them:
|
|
9
|
+
|
|
10
|
+
1. **Prerender** your content routes at build time.
|
|
11
|
+
2. **Drop the scripts** on those routes with the `noScripts` route rule.
|
|
12
|
+
3. Use **server components** for widgets that need server rendering but no interactivity.
|
|
13
|
+
4. Use **lazy hydration** on routes that keep their scripts, so interactivity is paid for only when needed.
|
|
14
|
+
|
|
15
|
+
## Prerender and Drop Scripts
|
|
16
|
+
|
|
17
|
+
For routes that have no client-side interactivity at all, combine `prerender` and `noScripts` in [`routeRules`](/docs/4.x/guide/concepts/rendering#hybrid-rendering):
|
|
18
|
+
|
|
19
|
+
```ts twoslash [nuxt.config.ts]
|
|
20
|
+
export default defineNuxtConfig({
|
|
21
|
+
routeRules: {
|
|
22
|
+
'/blog/**': { prerender: true, noScripts: true },
|
|
23
|
+
'/about': { prerender: true, noScripts: true },
|
|
24
|
+
},
|
|
25
|
+
})
|
|
26
|
+
```
|
|
27
|
+
|
|
28
|
+
These routes are rendered to plain HTML at build time, and the rendered pages omit the Nuxt entry scripts, import map, payload script and JS resource hints. CSS is still included. The result is a page with effectively zero JavaScript cost.
|
|
29
|
+
|
|
30
|
+
::read-more{to="/docs/4.x/guide/going-further/features#noscripts"}
|
|
31
|
+
Read more about exactly what `noScripts` omits, and the app-wide `features.noScripts` option.
|
|
32
|
+
::
|
|
33
|
+
|
|
34
|
+
::important
|
|
35
|
+
A `noScripts` page does not hydrate. Nothing on it is interactive: no `@click` handlers, no `<NuxtLink>` prefetching (links still work as plain `<a>` tags), no client-side navigation away from the page. Only use it on routes where that is what you want.
|
|
36
|
+
::
|
|
37
|
+
|
|
38
|
+
## Islands for Server-Rendered Widgets
|
|
39
|
+
|
|
40
|
+
Some parts of a static page still benefit from being components with real logic behind them: rendered markdown, syntax-highlighted code, CMS content. Use [server components](/docs/4.x/guide/concepts/server-components) for these. They render on the server, and their dependencies never reach the client bundle:
|
|
41
|
+
|
|
42
|
+
```bash [Directory Structure]
|
|
43
|
+
-| app/
|
|
44
|
+
---| components/
|
|
45
|
+
-----| HighlightedMarkdown.server.vue
|
|
46
|
+
```
|
|
47
|
+
|
|
48
|
+
```vue [app/pages/blog/[slug\\].vue]
|
|
49
|
+
<template>
|
|
50
|
+
<article>
|
|
51
|
+
<h1>{{ post.title }}</h1>
|
|
52
|
+
<HighlightedMarkdown :markdown="post.body" />
|
|
53
|
+
</article>
|
|
54
|
+
</template>
|
|
55
|
+
```
|
|
56
|
+
|
|
57
|
+
Static islands like this work fine on `noScripts` routes, since their HTML is embedded at render time.
|
|
58
|
+
|
|
59
|
+
::warning
|
|
60
|
+
Fully interactive widgets inside islands (components marked with [`nuxt-client`](/docs/4.x/guide/concepts/server-components#selective-hydration-with-nuxt-client), or interactive island slots) do **not** work on a `noScripts` route. Island teleports are relocated into place by a small inline script before hydration; with scripts disabled, that relocation cannot run and nothing hydrates. If a page needs even one interactive widget, keep scripts on that route and use lazy hydration instead.
|
|
61
|
+
::
|
|
62
|
+
|
|
63
|
+
## Lazy Hydration for the Rest
|
|
64
|
+
|
|
65
|
+
For routes that need some interactivity, keep scripts enabled but control *when* components hydrate using [lazy hydration](/docs/4.x/directory-structure/app/components#delayed-or-lazy-hydration):
|
|
66
|
+
|
|
67
|
+
```vue [app/pages/index.vue]
|
|
68
|
+
<template>
|
|
69
|
+
<div>
|
|
70
|
+
<HeroSection />
|
|
71
|
+
<!-- static content that never needs to become interactive -->
|
|
72
|
+
<LazyTestimonials hydrate-never />
|
|
73
|
+
<!-- hydrate only when scrolled into view -->
|
|
74
|
+
<LazyNewsletterSignup hydrate-on-visible />
|
|
75
|
+
<!-- hydrate when the browser is idle -->
|
|
76
|
+
<LazyCookieBanner hydrate-on-idle />
|
|
77
|
+
</div>
|
|
78
|
+
</template>
|
|
79
|
+
```
|
|
80
|
+
|
|
81
|
+
`hydrate-never` is particularly useful on content sites: the component's HTML is server-rendered, but it is never hydrated, so its JavaScript is never executed (though prop changes will still trigger hydration).
|
|
82
|
+
|
|
83
|
+
## Putting It Together
|
|
84
|
+
|
|
85
|
+
A typical mostly-static site ends up with:
|
|
86
|
+
|
|
87
|
+
```ts twoslash [nuxt.config.ts]
|
|
88
|
+
export default defineNuxtConfig({
|
|
89
|
+
routeRules: {
|
|
90
|
+
// pure content: prerendered, zero JS
|
|
91
|
+
'/': { prerender: true, noScripts: true },
|
|
92
|
+
'/blog/**': { prerender: true, noScripts: true },
|
|
93
|
+
// pages with an interactive widget: prerendered, scripts kept
|
|
94
|
+
'/contact': { prerender: true },
|
|
95
|
+
},
|
|
96
|
+
})
|
|
97
|
+
```
|
|
98
|
+
|
|
99
|
+
- content routes ship plain HTML and CSS
|
|
100
|
+
- islands (`.server.vue` components) handle server-rendered widgets everywhere, keeping heavy dependencies out of the client bundle
|
|
101
|
+
- the few interactive routes keep scripts, with `hydrate-on-visible` / `hydrate-on-idle` / `hydrate-never` limiting how much work the browser does up front
|
|
102
|
+
|
|
103
|
+
::read-more{to="/docs/4.x/getting-started/prerendering"}
|
|
104
|
+
Read more about prerendering, including selective prerendering and payload extraction.
|
|
105
|
+
::
|
|
106
|
+
|
|
107
|
+
::read-more{to="/docs/4.x/guide/best-practices/performance"}
|
|
108
|
+
Read more general performance best practices for Nuxt apps.
|
|
109
|
+
::
|
|
@@ -364,10 +364,10 @@ export default defineNuxtConfig({
|
|
|
364
364
|
})
|
|
365
365
|
```
|
|
366
366
|
|
|
367
|
-
:read-more{to="/docs/4.x/
|
|
367
|
+
:read-more{to="/docs/4.x/guide/concepts/server-components"}
|
|
368
368
|
|
|
369
369
|
::note
|
|
370
|
-
Skip `nuxt-client` on non-SFC components such as [`<NuxtLink>`](/docs/4.x/api/components/nuxt-link). See [
|
|
370
|
+
Skip `nuxt-client` on non-SFC components such as [`<NuxtLink>`](/docs/4.x/api/components/nuxt-link). See [selective hydration with `nuxt-client`](/docs/4.x/guide/concepts/server-components#selective-hydration-with-nuxt-client).
|
|
371
371
|
::
|
|
372
372
|
|
|
373
373
|
::read-more{icon="i-simple-icons-github" to="https://github.com/nuxt/nuxt/issues/19772" target="_blank"}
|
|
@@ -41,20 +41,50 @@ export default defineNuxtConfig({
|
|
|
41
41
|
|
|
42
42
|
### noScripts
|
|
43
43
|
|
|
44
|
-
Turn off rendering of Nuxt scripts and JavaScript resource hints
|
|
44
|
+
Turn off rendering of Nuxt scripts and JavaScript resource hints, so pages ship as pure HTML and CSS.
|
|
45
45
|
|
|
46
|
-
|
|
46
|
+
Possible values:
|
|
47
47
|
|
|
48
|
-
|
|
48
|
+
- `false` (default): scripts are rendered normally.
|
|
49
|
+
- `'production'` (or `true`, which is normalized to `'production'`): scripts are omitted in production builds only, so hot module replacement and other development features keep working in `nuxt dev`.
|
|
50
|
+
- `'all'`: scripts are omitted in both development and production.
|
|
49
51
|
|
|
50
52
|
```ts [nuxt.config.ts]
|
|
51
53
|
export default defineNuxtConfig({
|
|
52
54
|
features: {
|
|
53
|
-
noScripts:
|
|
55
|
+
noScripts: 'production', // or 'all' | false
|
|
54
56
|
},
|
|
55
57
|
})
|
|
56
58
|
```
|
|
57
59
|
|
|
60
|
+
You can also disable scripts per-route with the `noScripts` [route rule](/docs/4.x/guide/concepts/rendering#hybrid-rendering), which applies in all modes:
|
|
61
|
+
|
|
62
|
+
```ts [nuxt.config.ts]
|
|
63
|
+
export default defineNuxtConfig({
|
|
64
|
+
routeRules: {
|
|
65
|
+
'/blog/**': { noScripts: true },
|
|
66
|
+
},
|
|
67
|
+
})
|
|
68
|
+
```
|
|
69
|
+
|
|
70
|
+
When `noScripts` applies to a rendered page, the following are omitted from the HTML:
|
|
71
|
+
|
|
72
|
+
- the Nuxt entry `<script>` tags
|
|
73
|
+
- the import map for the entry chunk
|
|
74
|
+
- the inlined payload `<script>` (and the payload preload link, when payload extraction is enabled)
|
|
75
|
+
- JavaScript resource hints (`preload` and `prefetch` links for JS chunks)
|
|
76
|
+
|
|
77
|
+
CSS is unaffected: stylesheet links and inlined styles are still rendered, so the page looks identical. The page simply never hydrates, which means nothing on it is interactive.
|
|
78
|
+
|
|
79
|
+
A few interactions to be aware of:
|
|
80
|
+
|
|
81
|
+
- Routes with the `noScripts` route rule are always rendered with the buffered (non-streaming) renderer.
|
|
82
|
+
- [Server components](/docs/4.x/guide/concepts/server-components) that only render static HTML work fine, but components hydrated with `nuxt-client` and interactive island slots rely on an inline script to relocate teleported content, so they will not become interactive on a `noScripts` route. When `features.noScripts` is set app-wide and component islands are active, Nuxt also falls back to buffered rendering for this reason.
|
|
83
|
+
|
|
84
|
+
::read-more{to="/docs/4.x/guide/recipes/mostly-static-sites"}
|
|
85
|
+
See the mostly-static site recipe for combining `noScripts` with prerendering, islands and lazy hydration.
|
|
86
|
+
::
|
|
87
|
+
|
|
58
88
|
## `future`
|
|
59
89
|
|
|
60
90
|
There is also a `future` namespace for early opting-in to new features that will become default in a future (possibly major) version of the framework.
|
|
@@ -13,6 +13,8 @@ links:
|
|
|
13
13
|
This component is available in Nuxt v3.12+.
|
|
14
14
|
::
|
|
15
15
|
|
|
16
|
+
:read-more{title="Nuxt accessibility" to="/docs/4.x/guide/best-practices/accessibility#route-announcements"}
|
|
17
|
+
|
|
16
18
|
## Usage
|
|
17
19
|
|
|
18
20
|
Add `<NuxtRouteAnnouncer/>` in your [`app.vue`](/docs/4.x/directory-structure/app/app) or [`app/layouts/`](/docs/4.x/directory-structure/app/layouts) to enhance accessibility by informing assistive technologies about page title changes. This ensures that navigational changes are announced to users relying on screen readers.
|
|
@@ -12,6 +12,8 @@ links:
|
|
|
12
12
|
`<NuxtLink>` is a drop-in replacement for both Vue Router's `<RouterLink>` component and HTML's `<a>` tag. It intelligently determines whether the link is _internal_ or _external_ and renders it accordingly with available optimizations (prefetching, default attributes, etc.)
|
|
13
13
|
::
|
|
14
14
|
|
|
15
|
+
:read-more{title="Nuxt accessibility" to="/docs/4.x/guide/best-practices/accessibility#links"}
|
|
16
|
+
|
|
15
17
|
## Internal Routing
|
|
16
18
|
|
|
17
19
|
In this example, we use `<NuxtLink>` component to link to another page of the application.
|
|
@@ -20,6 +20,10 @@ Global styles of your application are sent with the response.
|
|
|
20
20
|
Server only components use `<NuxtIsland>` under the hood
|
|
21
21
|
::
|
|
22
22
|
|
|
23
|
+
::read-more{to="/docs/4.x/guide/concepts/server-components"}
|
|
24
|
+
Read the dedicated guide to server components and islands.
|
|
25
|
+
::
|
|
26
|
+
|
|
23
27
|
## Props
|
|
24
28
|
|
|
25
29
|
- `name` : Name of the component to render.
|
|
@@ -75,7 +79,7 @@ export default defineNuxtPlugin((nuxtApp) => {
|
|
|
75
79
|
|
|
76
80
|
- **Identical islands still share the same ids.** Two instances of the same island rendered with the same name, props and context share a single server render (and payload entry), so their HTML — including any `useId`-generated ids — is identical, and the island context id used as prefix is the same. This results in duplicated `id` attributes in the DOM, which can break `aria-*` references and `<label for>` associations between the two instances. There is currently no workaround for this case.
|
|
77
81
|
|
|
78
|
-
- **`useId` does not work in interactive components inside islands.** A component loaded with the [`nuxt-client` attribute](/docs/4.x/
|
|
82
|
+
- **`useId` does not work in interactive components inside islands.** A component loaded with the [`nuxt-client` attribute](/docs/4.x/guide/concepts/server-components#selective-hydration-with-nuxt-client) is server-rendered inside the island's app but hydrated by the main client app, so `useId` returns different values on the server and on the client, causing a hydration mismatch.
|
|
79
83
|
|
|
80
84
|
## Slots
|
|
81
85
|
|
|
@@ -18,6 +18,8 @@ This composable is available in Nuxt v3.12+.
|
|
|
18
18
|
A composable which observes the page title changes and updates the announcer message accordingly. Used by [`<NuxtRouteAnnouncer>`](/docs/4.x/api/components/nuxt-route-announcer) and controllable.
|
|
19
19
|
It hooks into Unhead's `dom:rendered` hook to read the page's title and set it as the announcer message.
|
|
20
20
|
|
|
21
|
+
:read-more{title="Nuxt accessibility" to="/docs/4.x/guide/best-practices/accessibility#route-announcements"}
|
|
22
|
+
|
|
21
23
|
## Parameters
|
|
22
24
|
|
|
23
25
|
- `politeness`: Sets the urgency for screen reader announcements: `off` (disable the announcement), `polite` (waits for silence), or `assertive` (interrupts immediately). (default `polite`).
|