0
# Hydration Strategies
1
2
Vue provides advanced hydration strategies for selective server-side rendering and client-side hydration, enabling performance optimizations for different content types.
3
4
## Capabilities
5
6
### Idle Hydration
7
8
Hydrate components when the browser is idle, reducing impact on initial page load.
9
10
```typescript { .api }
11
/**
12
* Hydrates component when browser is idle
13
* @param timeout - Maximum time to wait before forcing hydration (ms)
14
* @returns Hydration strategy function
15
*/
16
function hydrateOnIdle(timeout?: number): HydrationStrategy;
17
```
18
19
**Usage Examples:**
20
21
```typescript
22
import { defineAsyncComponent, hydrateOnIdle } from "@vue/runtime-core";
23
24
// Hydrate when browser is idle
25
const IdleComponent = defineAsyncComponent({
26
loader: () => import('./HeavyComponent.vue'),
27
hydrate: hydrateOnIdle(1000) // Force hydration after 1 second if not idle
28
});
29
30
// Multiple components with idle hydration
31
const Dashboard = defineComponent({
32
components: {
33
Analytics: defineAsyncComponent({
34
loader: () => import('./Analytics.vue'),
35
hydrate: hydrateOnIdle()
36
}),
37
Charts: defineAsyncComponent({
38
loader: () => import('./Charts.vue'),
39
hydrate: hydrateOnIdle(2000)
40
})
41
}
42
});
43
```
44
45
### Visibility-based Hydration
46
47
Hydrate components when they become visible in the viewport.
48
49
```typescript { .api }
50
/**
51
* Hydrates component when it becomes visible
52
* @param options - Intersection observer options
53
* @returns Hydration strategy function
54
*/
55
function hydrateOnVisible(options?: IntersectionObserverInit): HydrationStrategy;
56
```
57
58
**Usage Examples:**
59
60
```typescript
61
import { defineAsyncComponent, hydrateOnVisible } from "@vue/runtime-core";
62
63
// Hydrate when component becomes visible
64
const LazyImage = defineAsyncComponent({
65
loader: () => import('./LazyImage.vue'),
66
hydrate: hydrateOnVisible()
67
});
68
69
// Hydrate with custom intersection options
70
const BelowFoldContent = defineAsyncComponent({
71
loader: () => import('./BelowFoldContent.vue'),
72
hydrate: hydrateOnVisible({
73
rootMargin: '100px', // Hydrate 100px before entering viewport
74
threshold: 0.1 // Hydrate when 10% visible
75
})
76
});
77
78
// Multiple visibility thresholds
79
const ProgressiveImage = defineAsyncComponent({
80
loader: () => import('./ProgressiveImage.vue'),
81
hydrate: hydrateOnVisible({
82
threshold: [0, 0.25, 0.5, 0.75, 1] // Multiple visibility levels
83
})
84
});
85
```
86
87
### Media Query Hydration
88
89
Hydrate components based on CSS media query conditions.
90
91
```typescript { .api }
92
/**
93
* Hydrates component when media query matches
94
* @param query - CSS media query string
95
* @returns Hydration strategy function
96
*/
97
function hydrateOnMediaQuery(query: string): HydrationStrategy;
98
```
99
100
**Usage Examples:**
101
102
```typescript
103
import { defineAsyncComponent, hydrateOnMediaQuery } from "@vue/runtime-core";
104
105
// Hydrate only on desktop
106
const DesktopWidget = defineAsyncComponent({
107
loader: () => import('./DesktopWidget.vue'),
108
hydrate: hydrateOnMediaQuery('(min-width: 1024px)')
109
});
110
111
// Hydrate only on mobile
112
const MobileMenu = defineAsyncComponent({
113
loader: () => import('./MobileMenu.vue'),
114
hydrate: hydrateOnMediaQuery('(max-width: 768px)')
115
});
116
117
// Hydrate based on orientation
118
const LandscapeFeature = defineAsyncComponent({
119
loader: () => import('./LandscapeFeature.vue'),
120
hydrate: hydrateOnMediaQuery('(orientation: landscape)')
121
});
122
123
// Hydrate based on reduced motion preference
124
const AnimatedComponent = defineAsyncComponent({
125
loader: () => import('./AnimatedComponent.vue'),
126
hydrate: hydrateOnMediaQuery('(prefers-reduced-motion: no-preference)')
127
});
128
129
// Complex media queries
130
const HighResComponent = defineAsyncComponent({
131
loader: () => import('./HighResComponent.vue'),
132
hydrate: hydrateOnMediaQuery('(min-resolution: 192dpi) and (min-width: 1200px)')
133
});
134
```
135
136
### Interaction-based Hydration
137
138
Hydrate components when user interacts with them.
139
140
```typescript { .api }
141
/**
142
* Hydrates component on user interaction
143
* @param events - Event name or array of event names to listen for
144
* @returns Hydration strategy function
145
*/
146
function hydrateOnInteraction(events: string | string[]): HydrationStrategy;
147
```
148
149
**Usage Examples:**
150
151
```typescript
152
import { defineAsyncComponent, hydrateOnInteraction } from "@vue/runtime-core";
153
154
// Hydrate on click
155
const InteractiveWidget = defineAsyncComponent({
156
loader: () => import('./InteractiveWidget.vue'),
157
hydrate: hydrateOnInteraction('click')
158
});
159
160
// Hydrate on multiple events
161
const HoverCard = defineAsyncComponent({
162
loader: () => import('./HoverCard.vue'),
163
hydrate: hydrateOnInteraction(['mouseenter', 'focus'])
164
});
165
166
// Hydrate on touch or mouse events
167
const TouchComponent = defineAsyncComponent({
168
loader: () => import('./TouchComponent.vue'),
169
hydrate: hydrateOnInteraction(['touchstart', 'mousedown'])
170
});
171
172
// Form field hydration
173
const RichTextEditor = defineAsyncComponent({
174
loader: () => import('./RichTextEditor.vue'),
175
hydrate: hydrateOnInteraction(['focus', 'click', 'keydown'])
176
});
177
```
178
179
### Combined Hydration Strategies
180
181
Create complex hydration logic by combining multiple strategies.
182
183
```typescript
184
import { defineAsyncComponent, hydrateOnIdle, hydrateOnVisible, hydrateOnMediaQuery, hydrateOnInteraction } from "@vue/runtime-core";
185
186
// Custom combined strategy
187
function createHybridStrategy(): HydrationStrategy {
188
return (hydrate, forEachElement) => {
189
let hydrated = false;
190
191
const doHydrate = () => {
192
if (!hydrated) {
193
hydrated = true;
194
hydrate();
195
}
196
};
197
198
// Hydrate on idle OR visibility OR interaction
199
const idleStrategy = hydrateOnIdle(2000);
200
const visibleStrategy = hydrateOnVisible({ threshold: 0.5 });
201
const interactionStrategy = hydrateOnInteraction(['click', 'focus']);
202
203
// Apply all strategies
204
idleStrategy(doHydrate, forEachElement);
205
visibleStrategy(doHydrate, forEachElement);
206
interactionStrategy(doHydrate, forEachElement);
207
};
208
}
209
210
// Usage
211
const HybridComponent = defineAsyncComponent({
212
loader: () => import('./HybridComponent.vue'),
213
hydrate: createHybridStrategy()
214
});
215
216
// Progressive hydration based on device capabilities
217
function createAdaptiveStrategy(): HydrationStrategy {
218
return (hydrate, forEachElement) => {
219
// Fast devices: hydrate immediately when visible
220
if (navigator.hardwareConcurrency > 4) {
221
hydrateOnVisible()(hydrate, forEachElement);
222
}
223
// Slow devices: hydrate only on interaction
224
else {
225
hydrateOnInteraction('click')(hydrate, forEachElement);
226
}
227
};
228
}
229
230
const AdaptiveComponent = defineAsyncComponent({
231
loader: () => import('./AdaptiveComponent.vue'),
232
hydrate: createAdaptiveStrategy()
233
});
234
```
235
236
### Advanced Usage Patterns
237
238
```typescript
239
// Conditional hydration based on feature detection
240
const AdvancedChart = defineAsyncComponent({
241
loader: () => import('./AdvancedChart.vue'),
242
hydrate: (() => {
243
// Only hydrate if Canvas is supported
244
if (typeof HTMLCanvasElement !== 'undefined') {
245
return hydrateOnVisible();
246
}
247
// Fallback: never hydrate (static content only)
248
return () => {};
249
})()
250
});
251
252
// Time-based hydration
253
function hydrateAfterDelay(delay: number): HydrationStrategy {
254
return (hydrate) => {
255
setTimeout(hydrate, delay);
256
};
257
}
258
259
const DelayedComponent = defineAsyncComponent({
260
loader: () => import('./DelayedComponent.vue'),
261
hydrate: hydrateAfterDelay(5000) // Hydrate after 5 seconds
262
});
263
264
// Hydrate based on network conditions
265
function hydrateOnGoodConnection(): HydrationStrategy {
266
return (hydrate) => {
267
const connection = (navigator as any).connection;
268
if (connection && connection.effectiveType === '4g') {
269
hydrateOnIdle()(hydrate, () => {});
270
} else {
271
hydrateOnInteraction('click')(hydrate, () => {});
272
}
273
};
274
}
275
276
const NetworkAwareComponent = defineAsyncComponent({
277
loader: () => import('./NetworkAwareComponent.vue'),
278
hydrate: hydrateOnGoodConnection()
279
});
280
```
281
282
## Types
283
284
```typescript { .api }
285
/**
286
* Hydration strategy function type
287
*/
288
interface HydrationStrategy {
289
(hydrate: () => void, forEachElement: (cb: (el: Element) => void) => void): void;
290
}
291
292
/**
293
* Factory function for creating hydration strategies
294
*/
295
interface HydrationStrategyFactory<T extends any[] = any[]> {
296
(...args: T): HydrationStrategy;
297
}
298
299
/**
300
* Intersection observer options for visibility-based hydration
301
*/
302
interface IntersectionObserverInit {
303
root?: Element | Document | null;
304
rootMargin?: string;
305
threshold?: number | number[];
306
}
307
```
308
309
## Performance Considerations
310
311
### Strategy Selection Guide
312
313
- **`hydrateOnIdle`**: Best for non-critical components that don't need immediate interactivity
314
- **`hydrateOnVisible`**: Ideal for below-the-fold content and images
315
- **`hydrateOnMediaQuery`**: Perfect for responsive components that only work on certain device types
316
- **`hydrateOnInteraction`**: Excellent for interactive widgets that users might not engage with
317
318
### Best Practices
319
320
1. **Prioritize Critical Path**: Always hydrate above-the-fold interactive content immediately
321
2. **Progressive Enhancement**: Design components to work without JavaScript first
322
3. **Fallback Strategies**: Provide timeout fallbacks to ensure eventual hydration
323
4. **Test Real Conditions**: Test hydration strategies on various devices and network conditions
324
5. **Monitor Performance**: Track metrics to ensure hydration strategies improve performance
325
326
### Example Application Structure
327
328
```typescript
329
const App = defineComponent({
330
components: {
331
// Critical: Hydrate immediately
332
Header: defineComponent(/* immediate hydration */),
333
Navigation: defineComponent(/* immediate hydration */),
334
335
// Important: Hydrate when visible
336
Hero: defineAsyncComponent({
337
loader: () => import('./Hero.vue'),
338
hydrate: hydrateOnVisible({ threshold: 0.1 })
339
}),
340
341
// Secondary: Hydrate when idle
342
Sidebar: defineAsyncComponent({
343
loader: () => import('./Sidebar.vue'),
344
hydrate: hydrateOnIdle(1000)
345
}),
346
347
// Interactive: Hydrate on interaction
348
ContactForm: defineAsyncComponent({
349
loader: () => import('./ContactForm.vue'),
350
hydrate: hydrateOnInteraction(['focus', 'click'])
351
}),
352
353
// Conditional: Hydrate based on device
354
MobileMenu: defineAsyncComponent({
355
loader: () => import('./MobileMenu.vue'),
356
hydrate: hydrateOnMediaQuery('(max-width: 768px)')
357
})
358
}
359
});
360
```