@nuxt/docs 4.0.1 → 4.0.2
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/02.installation.md +4 -4
- package/2.guide/3.going-further/11.nightly-release-channel.md +4 -4
- package/2.guide/5.best-practices/hydration.md +188 -0
- package/3.api/1.components/4.nuxt-link.md +1 -1
- package/3.api/2.composables/use-route.md +1 -1
- package/3.api/3.utils/define-nuxt-route-middleware.md +1 -1
- package/3.api/3.utils/on-before-route-leave.md +1 -1
- package/3.api/3.utils/on-before-route-update.md +1 -1
- package/3.api/5.kit/1.modules.md +49 -0
- package/5.community/4.contribution.md +18 -0
- package/package.json +1 -1
|
@@ -39,19 +39,19 @@ Open a terminal (if you're using [Visual Studio Code](https://code.visualstudio.
|
|
|
39
39
|
::code-group{sync="pm"}
|
|
40
40
|
|
|
41
41
|
```bash [npm]
|
|
42
|
-
npm create nuxt <project-name>
|
|
42
|
+
npm create nuxt@latest <project-name>
|
|
43
43
|
```
|
|
44
44
|
|
|
45
45
|
```bash [yarn]
|
|
46
|
-
yarn create nuxt <project-name>
|
|
46
|
+
yarn create nuxt@latest <project-name>
|
|
47
47
|
```
|
|
48
48
|
|
|
49
49
|
```bash [pnpm]
|
|
50
|
-
pnpm create nuxt <project-name>
|
|
50
|
+
pnpm create nuxt@latest <project-name>
|
|
51
51
|
```
|
|
52
52
|
|
|
53
53
|
```bash [bun]
|
|
54
|
-
bun create nuxt <project-name>
|
|
54
|
+
bun create nuxt@latest <project-name>
|
|
55
55
|
```
|
|
56
56
|
|
|
57
57
|
```bash [deno]
|
|
@@ -26,8 +26,8 @@ Update `nuxt` dependency inside `package.json`:
|
|
|
26
26
|
```diff [package.json]
|
|
27
27
|
{
|
|
28
28
|
"devDependencies": {
|
|
29
|
-
-- "nuxt": "^
|
|
30
|
-
++ "nuxt": "npm:nuxt-nightly@
|
|
29
|
+
-- "nuxt": "^4.0.0"
|
|
30
|
+
++ "nuxt": "npm:nuxt-nightly@latest"
|
|
31
31
|
}
|
|
32
32
|
}
|
|
33
33
|
```
|
|
@@ -41,8 +41,8 @@ Update `nuxt` dependency inside `package.json`:
|
|
|
41
41
|
```diff [package.json]
|
|
42
42
|
{
|
|
43
43
|
"devDependencies": {
|
|
44
|
-
-- "nuxt": "npm:nuxt-nightly@
|
|
45
|
-
++ "nuxt": "^
|
|
44
|
+
-- "nuxt": "npm:nuxt-nightly@latest"
|
|
45
|
+
++ "nuxt": "^4.0.0"
|
|
46
46
|
}
|
|
47
47
|
}
|
|
48
48
|
```
|
|
@@ -0,0 +1,188 @@
|
|
|
1
|
+
---
|
|
2
|
+
navigation.title: 'Nuxt and hydration'
|
|
3
|
+
title: Nuxt and hydration
|
|
4
|
+
description: Why fixing hydration issues is important
|
|
5
|
+
---
|
|
6
|
+
|
|
7
|
+
When developing, you may face hydration issues. Don't ignore those warnings.
|
|
8
|
+
|
|
9
|
+
# Why is it important to fix them?
|
|
10
|
+
|
|
11
|
+
Hydration mismatches are not just warnings - they are indicators of serious problems that can break your application:
|
|
12
|
+
|
|
13
|
+
## Performance Impact
|
|
14
|
+
|
|
15
|
+
- **Increased time to interactive**: Hydration errors force Vue to re-render the entire component tree, which will increase the time for your Nuxt app to become interactive
|
|
16
|
+
- **Poor user experience**: Users may see content flashing or unexpected layout shifts
|
|
17
|
+
|
|
18
|
+
## Functionality Issues
|
|
19
|
+
|
|
20
|
+
- **Broken interactivity**: Event listeners may not attach properly, leaving buttons and forms non-functional
|
|
21
|
+
- **State inconsistencies**: Application state can become out of sync between what the user sees and what the application thinks is rendered
|
|
22
|
+
- **SEO problems**: Search engines may index different content than what users actually see
|
|
23
|
+
|
|
24
|
+
# How to detect them
|
|
25
|
+
|
|
26
|
+
## Development Console Warnings
|
|
27
|
+
|
|
28
|
+
Vue will log hydration mismatch warnings in the browser console during development:
|
|
29
|
+
|
|
30
|
+

|
|
31
|
+
|
|
32
|
+
# Common reasons
|
|
33
|
+
|
|
34
|
+
## Browser-only APIs in Server Context
|
|
35
|
+
|
|
36
|
+
**Problem**: Using browser-specific APIs during server-side rendering.
|
|
37
|
+
|
|
38
|
+
```html
|
|
39
|
+
<template>
|
|
40
|
+
<div>User preference: {{ userTheme }}</div>
|
|
41
|
+
</template>
|
|
42
|
+
|
|
43
|
+
<script setup>
|
|
44
|
+
// This will cause hydration mismatch!
|
|
45
|
+
// localStorage doesn't exist on the server!
|
|
46
|
+
const userTheme = localStorage.getItem('theme') || 'light'
|
|
47
|
+
</script>
|
|
48
|
+
```
|
|
49
|
+
|
|
50
|
+
**Solution**: You can use [`useCookie`](/docs/api/composables/use-cookie):
|
|
51
|
+
|
|
52
|
+
```html
|
|
53
|
+
<template>
|
|
54
|
+
<div>User preference: {{ userTheme }}</div>
|
|
55
|
+
</template>
|
|
56
|
+
|
|
57
|
+
<script setup>
|
|
58
|
+
// This works on both server and client
|
|
59
|
+
const userTheme = useCookie('theme', { default: () => 'light' })
|
|
60
|
+
</script>
|
|
61
|
+
```
|
|
62
|
+
|
|
63
|
+
## Inconsistent Data
|
|
64
|
+
|
|
65
|
+
**Problem**: Different data between server and client.
|
|
66
|
+
|
|
67
|
+
```html
|
|
68
|
+
<template>
|
|
69
|
+
<div>{{ Math.random() }}</div>
|
|
70
|
+
</template>
|
|
71
|
+
```
|
|
72
|
+
|
|
73
|
+
**Solution**: Use SSR-friendly state:
|
|
74
|
+
|
|
75
|
+
```html
|
|
76
|
+
<template>
|
|
77
|
+
<div>{{ state }}</div>
|
|
78
|
+
</template>
|
|
79
|
+
|
|
80
|
+
<script setup>
|
|
81
|
+
const state = useState('random', () => Math.random())
|
|
82
|
+
</script>
|
|
83
|
+
```
|
|
84
|
+
|
|
85
|
+
## Conditional Rendering Based on Client State
|
|
86
|
+
|
|
87
|
+
**Problem**: Using client-only conditions during SSR.
|
|
88
|
+
|
|
89
|
+
```html
|
|
90
|
+
<template>
|
|
91
|
+
<div v-if="window?.innerWidth > 768">
|
|
92
|
+
Desktop content
|
|
93
|
+
</div>
|
|
94
|
+
</template>
|
|
95
|
+
```
|
|
96
|
+
|
|
97
|
+
**Solution**: Use media queries or handle it client-side:
|
|
98
|
+
|
|
99
|
+
```html
|
|
100
|
+
<template>
|
|
101
|
+
<div class="responsive-content">
|
|
102
|
+
<div class="hidden md:block">Desktop content</div>
|
|
103
|
+
<div class="md:hidden">Mobile content</div>
|
|
104
|
+
</div>
|
|
105
|
+
</template>
|
|
106
|
+
```
|
|
107
|
+
|
|
108
|
+
## Third-party Libraries with Side Effects
|
|
109
|
+
|
|
110
|
+
**Problem**: Libraries that modify the DOM or have browser dependencies (this happens a LOT with tag managers).
|
|
111
|
+
|
|
112
|
+
```html
|
|
113
|
+
<script setup>
|
|
114
|
+
if (import.meta.client) {
|
|
115
|
+
const { default: SomeBrowserLibrary } = await import('browser-only-lib')
|
|
116
|
+
SomeBrowserLibrary.init()
|
|
117
|
+
}
|
|
118
|
+
</script>
|
|
119
|
+
```
|
|
120
|
+
|
|
121
|
+
**Solution**: Initialise libraries after hydration has completed:
|
|
122
|
+
|
|
123
|
+
```html
|
|
124
|
+
<script setup>
|
|
125
|
+
onMounted(async () => {
|
|
126
|
+
const { default: SomeBrowserLibrary } = await import('browser-only-lib')
|
|
127
|
+
SomeBrowserLibrary.init()
|
|
128
|
+
})
|
|
129
|
+
</script>
|
|
130
|
+
```
|
|
131
|
+
|
|
132
|
+
## Dynamic Content Based on Time
|
|
133
|
+
|
|
134
|
+
**Problem**: Content that changes based on current time.
|
|
135
|
+
|
|
136
|
+
```html
|
|
137
|
+
<template>
|
|
138
|
+
<div>{{ greeting }}</div>
|
|
139
|
+
</template>
|
|
140
|
+
|
|
141
|
+
<script setup>
|
|
142
|
+
const hour = new Date().getHours()
|
|
143
|
+
const greeting = hour < 12 ? 'Good morning' : 'Good afternoon'
|
|
144
|
+
</script>
|
|
145
|
+
```
|
|
146
|
+
|
|
147
|
+
**Solution**: Use [`NuxtTime`](/docs/api/components/nuxt-time) component or handle it client-side:
|
|
148
|
+
|
|
149
|
+
```html
|
|
150
|
+
<template>
|
|
151
|
+
<div>
|
|
152
|
+
<NuxtTime :date="new Date()" format="HH:mm" />
|
|
153
|
+
</div>
|
|
154
|
+
</template>
|
|
155
|
+
```
|
|
156
|
+
|
|
157
|
+
```html
|
|
158
|
+
<template>
|
|
159
|
+
<div>
|
|
160
|
+
<ClientOnly>
|
|
161
|
+
{{ greeting }}
|
|
162
|
+
<template #fallback>
|
|
163
|
+
Hello!
|
|
164
|
+
</template>
|
|
165
|
+
</ClientOnly>
|
|
166
|
+
</div>
|
|
167
|
+
</template>
|
|
168
|
+
|
|
169
|
+
<script setup>
|
|
170
|
+
const greeting = ref('Hello!')
|
|
171
|
+
|
|
172
|
+
onMounted(() => {
|
|
173
|
+
const hour = new Date().getHours()
|
|
174
|
+
greeting.value = hour < 12 ? 'Good morning' : 'Good afternoon'
|
|
175
|
+
})
|
|
176
|
+
</script>
|
|
177
|
+
```
|
|
178
|
+
|
|
179
|
+
## In summary
|
|
180
|
+
|
|
181
|
+
1. **Use SSR-friendly composables**: [`useFetch`](/docs/api/composables/use-fetch), [`useAsyncData`](/docs/api/composables/use-async-data), [`useState`](/docs/api/composables/use-state)
|
|
182
|
+
2. **Wrap client-only code**: Use [`ClientOnly`](/docs/api/components/client-only) component for browser-specific content
|
|
183
|
+
3. **Consistent data sources**: Ensure server and client uses the same data
|
|
184
|
+
4. **Avoid side effects in setup**: Move browser-dependent code to `onMounted`
|
|
185
|
+
|
|
186
|
+
::tip
|
|
187
|
+
You can read the [Vue documentation on SSR hydration mismatch](https://vuejs.org/guide/scaling-up/ssr.html#hydration-mismatch) for a better understanding of hydration.
|
|
188
|
+
::
|
|
@@ -226,7 +226,7 @@ export default defineNuxtConfig({
|
|
|
226
226
|
|
|
227
227
|
When not using `external`, `<NuxtLink>` supports all Vue Router's [`RouterLink` props](https://router.vuejs.org/api/interfaces/RouterLinkProps.html)
|
|
228
228
|
|
|
229
|
-
- `to`: Any URL or a [route location object](https://router.vuejs.org/api
|
|
229
|
+
- `to`: Any URL or a [route location object](https://router.vuejs.org/api/type-aliases/RouteLocation.html) from Vue Router
|
|
230
230
|
- `custom`: Whether `<NuxtLink>` should wrap its content in an `<a>` element. It allows taking full control of how a link is rendered and how navigation works when it is clicked. Works the same as [Vue Router's `custom` prop](https://router.vuejs.org/api/interfaces/RouterLinkProps.html#Properties-custom)
|
|
231
231
|
- `exactActiveClass`: A class to apply on exact active links. Works the same as [Vue Router's `exactActiveClass` prop](https://router.vuejs.org/api/interfaces/RouterLinkProps.html#Properties-exactActiveClass) on internal links. Defaults to Vue Router's default (`"router-link-exact-active"`)
|
|
232
232
|
- `activeClass`: A class to apply on active links. Works the same as [Vue Router's `activeClass` prop](https://router.vuejs.org/api/interfaces/RouterLinkProps.html#Properties-activeClass) on internal links. Defaults to Vue Router's default (`"router-link-active"`)
|
|
@@ -49,4 +49,4 @@ Apart from dynamic parameters and query parameters, `useRoute()` also provides t
|
|
|
49
49
|
Browsers don't send [URL fragments](https://url.spec.whatwg.org/#concept-url-fragment) (for example `#foo`) when making requests. So using `route.fullPath` in your template can trigger hydration issues because this will include the fragment on client but not the server.
|
|
50
50
|
::
|
|
51
51
|
|
|
52
|
-
:read-more{icon="i-simple-icons-vuedotjs" to="https://router.vuejs.org/api
|
|
52
|
+
:read-more{icon="i-simple-icons-vuedotjs" to="https://router.vuejs.org/api/type-aliases/RouteLocationNormalizedLoaded.html"}
|
|
@@ -28,7 +28,7 @@ interface RouteMiddleware {
|
|
|
28
28
|
|
|
29
29
|
A function that takes two Vue Router's route location objects as parameters: the next route `to` as the first, and the current route `from` as the second.
|
|
30
30
|
|
|
31
|
-
Learn more about available properties of `RouteLocationNormalized` in the **[Vue Router docs](https://router.vuejs.org/api
|
|
31
|
+
Learn more about available properties of `RouteLocationNormalized` in the **[Vue Router docs](https://router.vuejs.org/api/type-aliases/RouteLocationNormalized.html)**.
|
|
32
32
|
|
|
33
33
|
## Examples
|
|
34
34
|
|
|
@@ -8,4 +8,4 @@ links:
|
|
|
8
8
|
size: xs
|
|
9
9
|
---
|
|
10
10
|
|
|
11
|
-
:read-more{icon="i-simple-icons-vuedotjs" to="https://router.vuejs.org/api
|
|
11
|
+
:read-more{icon="i-simple-icons-vuedotjs" to="https://router.vuejs.org/api/functions/onBeforeRouteLeave.html" title="Vue Router Docs" target="_blank"}
|
|
@@ -8,4 +8,4 @@ links:
|
|
|
8
8
|
size: xs
|
|
9
9
|
---
|
|
10
10
|
|
|
11
|
-
:read-more{icon="i-simple-icons-vuedotjs" to="https://router.vuejs.org/api
|
|
11
|
+
:read-more{icon="i-simple-icons-vuedotjs" to="https://router.vuejs.org/api/functions/onBeforeRouteUpdate.html" title="Vue Router Docs" target="_blank"}
|
package/3.api/5.kit/1.modules.md
CHANGED
|
@@ -44,6 +44,12 @@ import type { ModuleDefinition, ModuleOptions, NuxtModule } from '@nuxt/schema'
|
|
|
44
44
|
export function defineNuxtModule<TOptions extends ModuleOptions> (
|
|
45
45
|
definition?: ModuleDefinition<TOptions, Partial<TOptions>, false> | NuxtModule<TOptions, Partial<TOptions>, false>,
|
|
46
46
|
): NuxtModule<TOptions, TOptions, false>
|
|
47
|
+
|
|
48
|
+
export function defineNuxtModule<TOptions extends ModuleOptions> (): {
|
|
49
|
+
with: <TOptionsDefaults extends Partial<TOptions>> (
|
|
50
|
+
definition: ModuleDefinition<TOptions, TOptionsDefaults, true> | NuxtModule<TOptions, TOptionsDefaults, true>
|
|
51
|
+
) => NuxtModule<TOptions, TOptionsDefaults, true>
|
|
52
|
+
}
|
|
47
53
|
```
|
|
48
54
|
|
|
49
55
|
### Parameters
|
|
@@ -122,6 +128,49 @@ If the user tries to use your module with an incompatible Nuxt version, they wil
|
|
|
122
128
|
- [nuxt] Nuxt version ^3.1.0 is required but currently using 3.0.0
|
|
123
129
|
```
|
|
124
130
|
|
|
131
|
+
#### Type Safety for Resolved Options with `.with()`
|
|
132
|
+
|
|
133
|
+
When you need type safety for your resolved/merged module options, you can use the `.with()` method. This enables TypeScript to properly infer the relationship between your module's defaults and the final resolved options that your setup function receives.
|
|
134
|
+
|
|
135
|
+
```ts
|
|
136
|
+
import { defineNuxtModule } from '@nuxt/kit'
|
|
137
|
+
|
|
138
|
+
// Define your module options interface
|
|
139
|
+
interface ModuleOptions {
|
|
140
|
+
apiKey: string
|
|
141
|
+
baseURL: string
|
|
142
|
+
timeout?: number
|
|
143
|
+
retries?: number
|
|
144
|
+
}
|
|
145
|
+
|
|
146
|
+
export default defineNuxtModule<ModuleOptions>().with({
|
|
147
|
+
meta: {
|
|
148
|
+
name: '@nuxtjs/my-api',
|
|
149
|
+
configKey: 'myApi'
|
|
150
|
+
},
|
|
151
|
+
defaults: {
|
|
152
|
+
baseURL: 'https://api.example.com',
|
|
153
|
+
timeout: 5000,
|
|
154
|
+
retries: 3
|
|
155
|
+
},
|
|
156
|
+
setup(resolvedOptions, nuxt) {
|
|
157
|
+
// resolvedOptions is properly typed as:
|
|
158
|
+
// {
|
|
159
|
+
// apiKey: string // Required, no default provided
|
|
160
|
+
// baseURL: string // Required, has default value
|
|
161
|
+
// timeout: number // Optional, has default value
|
|
162
|
+
// retries: number // Optional, has default value
|
|
163
|
+
// }
|
|
164
|
+
|
|
165
|
+
console.log(resolvedOptions.baseURL) // ✅ TypeScript knows this is always defined
|
|
166
|
+
console.log(resolvedOptions.timeout) // ✅ TypeScript knows this is always defined
|
|
167
|
+
console.log(resolvedOptions.retries) // ✅ TypeScript knows this is always defined
|
|
168
|
+
}
|
|
169
|
+
})
|
|
170
|
+
```
|
|
171
|
+
|
|
172
|
+
Without using `.with()`, the `resolvedOptions` parameter would be typed as the raw `ModuleOptions` interface, where `timeout` and `retries` could be `undefined` even when defaults are provided. The `.with()` method enables TypeScript to understand that default values make those properties non-optional in the resolved options.
|
|
173
|
+
|
|
125
174
|
## `installModule`
|
|
126
175
|
|
|
127
176
|
Install specified Nuxt module programmatically. This is helpful when your module depends on other modules. You can pass the module options as an object to `inlineOptions` and they will be passed to the module's `setup` function.
|
|
@@ -80,6 +80,24 @@ If we mark a PR as 'pending', that means we likely have another task to do in re
|
|
|
80
80
|
|
|
81
81
|
We'll do our best to follow [our PR decision making flowchart](https://mermaid.live/view#pako:eNp9VE1v2kAQ_SsjXzBSEqlALlaUisSh0ACK2l4qcVm8Y9hi7672Iwly-O-ZtYPt5FAOCHbee_PmzdpVlCmOURLlhXrJ9sw4-JNuJNBnWs1UQafIQVjrERyWumAOv58-AJeXt29_0b7BXbWwwL0uRPa1vlZvcB_fF8oiMMmB2QM4BXkt3UoON7Lh3LWaDz2SVkK6QGt7DHvw0CKt5sxCKaQoWQEGtVHcZ04oGdw04LTVngW_LHOeFcURGGz97mw6PSv-iJdsi0UCA4nI7SfNwc3W3JZit3eQ1SZFDlKB15yswQ2MgbOjbYeatY3n8bcr-IWlekYYaJRcyB04I9gOB1CEfkF5dAVTzmFAtnqn4-bUYAiMMmHZgWhNPRhgus5mW2BATxq0NkIZ4Y4NbNjzE2ZchBzcHmGLe_ZMSKCcyRXyLrVFa_5n_PBK2xKy3kk9eOjULUdltk6C8kI-7NFDr8f4EVGDoqlp-wa4sJm3ltIMIuZ_mTQXJyTSkQZtunPqsKxShV9GKdkBYe1fHXjpbcjlvONlO9Kqx_M7YHmOmav_luxfE5zKwVs09hM5DLSupgYDlr5flDkwo7ykixKG-xDsUly1LZ-uY32dgDc7lG7YqwbNp0msJwmIUivjWFtfd-xRrEcJ7Omydz37qFplHOtxEp4GskI2qB5dRCWakglOz3oV8JuITJa4iRL6yZk5bKKNPBGOead-H2UWJc54vIiaW53SPgwrz4fIhVNm1bw76lfI6R2_MW21) when responding and reviewing to pull requests.
|
|
82
82
|
|
|
83
|
+
### AI-Assisted Contributions
|
|
84
|
+
|
|
85
|
+
We welcome the thoughtful use of AI tools when contributing to Nuxt, yet ask all contributors to follow [two core principles](https://roe.dev/blog/using-ai-in-open-source).
|
|
86
|
+
|
|
87
|
+
#### Never let an LLM speak for you
|
|
88
|
+
|
|
89
|
+
* All comments, issues, and pull request descriptions should be written in your own voice
|
|
90
|
+
* We value clear, human communication over perfect grammar or spelling
|
|
91
|
+
* Avoid copy-pasting AI-generated summaries that don't reflect your own understanding
|
|
92
|
+
|
|
93
|
+
#### Never let an LLM think for you
|
|
94
|
+
|
|
95
|
+
* Feel free to use AI tools to generate code or explore ideas
|
|
96
|
+
* Only submit contributions you fully understand and can explain
|
|
97
|
+
* Contributions should reflect your own reasoning and problem-solving
|
|
98
|
+
|
|
99
|
+
Our aim is ensuring quality and maintaining the joy of collaborating and communicating with real people. If you have ideas for improving our policy on AI in the Nuxt community, we'd love to hear them! ❤️
|
|
100
|
+
|
|
83
101
|
### Create a Module
|
|
84
102
|
|
|
85
103
|
If you've built something with Nuxt that's cool, why not [extract it into a module](/docs/guide/going-further/modules), so it can be shared with others? We have [many excellent modules already](/modules), but there's always room for more.
|