0
# SSR & Hydration
1
2
Server-side rendering utilities for hydration, request handling, and server-client data synchronization. Nuxt provides comprehensive SSR support with seamless client-side hydration and server-side utilities.
3
4
> **Note**: This module uses H3 types for server-side request handling. H3Event and related types are available through Nuxt's server context or can be imported from 'h3'.
5
6
## Capabilities
7
8
### Client-Side Hydration
9
10
Handle client-side hydration of server-rendered content.
11
12
```typescript { .api }
13
/**
14
* Handle client-side hydration of server state
15
* @param key - Key for the hydration data
16
* @param get - Function to get server value
17
* @param set - Function to set client value
18
*/
19
function useHydration<K, T>(key: K, get: () => T, set: (value: T) => void): void;
20
21
/**
22
* Run callback before hydration on specific element
23
* @param callback - Callback function receiving element
24
*/
25
function onPrehydrate(callback: (el: HTMLElement) => void): void;
26
```
27
28
**Usage Examples:**
29
30
```typescript
31
// Basic hydration
32
const serverTime = ref(new Date());
33
const clientTime = ref(new Date());
34
35
useHydration("time-sync",
36
() => serverTime.value,
37
(value) => { clientTime.value = value; }
38
);
39
40
// DOM element hydration
41
onPrehydrate((el) => {
42
// Initialize client-side only features
43
el.addEventListener("click", handleClick);
44
45
// Apply client-side styles
46
el.classList.add("hydrated");
47
});
48
49
// State hydration with validation
50
const serverData = ref<UserData | null>(null);
51
const clientData = ref<UserData | null>(null);
52
53
useHydration("user-data",
54
() => serverData.value,
55
(value) => {
56
// Validate before setting
57
if (value && isValidUserData(value)) {
58
clientData.value = value;
59
}
60
}
61
);
62
63
// Complex hydration example
64
const serverCart = useState<CartItem[]>("server-cart", () => []);
65
const clientCart = useState<CartItem[]>("client-cart", () => []);
66
67
useHydration("shopping-cart",
68
() => serverCart.value,
69
(items) => {
70
// Merge server cart with local storage
71
const localCart = JSON.parse(localStorage.getItem("cart") || "[]");
72
clientCart.value = [...items, ...localCart];
73
}
74
);
75
```
76
77
### Server-Side Request Handling
78
79
Access server-side request information and modify responses.
80
81
```typescript { .api }
82
/**
83
* Access request headers (server-side only)
84
* @param include - Optional array of headers to include
85
* @returns Headers object
86
*/
87
function useRequestHeaders(include?: string[]): Record<string, string>;
88
89
/**
90
* Get the current H3 request event (server-side only)
91
* @returns H3Event object with request/response utilities
92
*/
93
function useRequestEvent(): H3Event;
94
95
/**
96
* Get server-side fetch function with request context
97
* @returns Fetch function configured with current request context
98
*/
99
function useRequestFetch(): $Fetch;
100
101
/**
102
* Set response status code and message (server-side only)
103
* @param code - HTTP status code
104
* @param message - Optional status message
105
*/
106
function setResponseStatus(code: number, message?: string): void;
107
108
/**
109
* Get/set response header (server-side only)
110
* @param name - Header name
111
* @returns Header value reference
112
*/
113
function useResponseHeader(name: string): { value?: string };
114
```
115
116
**Usage Examples:**
117
118
```typescript
119
// Access request headers
120
const headers = useRequestHeaders();
121
console.log("User-Agent:", headers["user-agent"]);
122
123
// Get specific headers
124
const authHeaders = useRequestHeaders(["authorization", "x-api-key"]);
125
126
// Get request event
127
const event = useRequestEvent();
128
console.log("Request method:", event.node.req.method);
129
console.log("Request URL:", event.node.req.url);
130
131
// Server-side fetch with context
132
const requestFetch = useRequestFetch();
133
const data = await requestFetch("/api/internal/data");
134
135
// Set response status
136
setResponseStatus(201, "Created");
137
138
// Set response headers
139
const cacheHeader = useResponseHeader("cache-control");
140
cacheHeader.value = "public, max-age=3600";
141
142
// Authentication example
143
const authHeader = useRequestHeaders(["authorization"]);
144
const token = authHeader.authorization?.replace("Bearer ", "");
145
146
if (!token) {
147
setResponseStatus(401, "Unauthorized");
148
throw createError({
149
statusCode: 401,
150
statusMessage: "Missing authentication token"
151
});
152
}
153
154
// CORS handling
155
const corsHeader = useResponseHeader("access-control-allow-origin");
156
corsHeader.value = "*";
157
158
// API rate limiting
159
const rateLimitHeader = useResponseHeader("x-ratelimit-remaining");
160
rateLimitHeader.value = "99";
161
```
162
163
### Prerendering
164
165
Configure routes for static generation and prerendering.
166
167
```typescript { .api }
168
/**
169
* Add routes for prerendering during build
170
* @param routes - Route or array of routes to prerender
171
*/
172
function prerenderRoutes(routes: string | string[]): void;
173
```
174
175
**Usage Examples:**
176
177
```typescript
178
// Add single route
179
prerenderRoutes("/sitemap.xml");
180
181
// Add multiple routes
182
prerenderRoutes([
183
"/about",
184
"/contact",
185
"/privacy-policy"
186
]);
187
188
// Dynamic route prerendering
189
const { data: products } = await useFetch("/api/products");
190
const productRoutes = products.value?.map(p => `/products/${p.slug}`) || [];
191
prerenderRoutes(productRoutes);
192
193
// Conditional prerendering
194
if (process.env.PRERENDER_BLOG === "true") {
195
const { data: posts } = await useFetch("/api/posts");
196
const postRoutes = posts.value?.map(p => `/blog/${p.slug}`) || [];
197
prerenderRoutes(postRoutes);
198
}
199
```
200
201
### Server-Side Data Fetching
202
203
Advanced patterns for server-side data fetching and caching.
204
205
```typescript
206
// Server-only data fetching
207
const { data: serverSecrets } = await useAsyncData("secrets", async () => {
208
// This only runs on server
209
return {
210
apiKey: process.env.API_KEY,
211
dbUrl: process.env.DATABASE_URL
212
};
213
}, {
214
server: true,
215
client: false
216
});
217
218
// Request-specific caching
219
const event = useRequestEvent();
220
const userId = getCookie(event, "user-id");
221
222
const { data: userData } = await useAsyncData(`user-${userId}`, () =>
223
$fetch(`/api/users/${userId}`)
224
);
225
226
// Server-side authentication
227
const event = useRequestEvent();
228
const token = getCookie(event, "auth-token");
229
230
if (!token) {
231
setResponseStatus(401);
232
throw createError({
233
statusCode: 401,
234
statusMessage: "Authentication required"
235
});
236
}
237
238
// Validate token server-side
239
try {
240
const user = await verifyToken(token);
241
event.context.user = user;
242
} catch (error) {
243
setResponseStatus(401);
244
throw createError({
245
statusCode: 401,
246
statusMessage: "Invalid token"
247
});
248
}
249
```
250
251
### Request Context & Middleware
252
253
```typescript
254
// Access request context
255
const event = useRequestEvent();
256
257
// Set context data
258
event.context.startTime = Date.now();
259
event.context.requestId = generateId();
260
261
// Logging middleware
262
export default defineEventHandler(async (event) => {
263
const start = Date.now();
264
265
// Add request ID
266
event.context.requestId = `req_${Date.now()}_${Math.random().toString(36).substr(2, 9)}`;
267
268
// Set response header
269
setHeader(event, "x-request-id", event.context.requestId);
270
271
// Continue with request
272
const response = await next(event);
273
274
// Log completion
275
const duration = Date.now() - start;
276
console.log(`[${event.context.requestId}] ${event.node.req.method} ${event.node.req.url} - ${duration}ms`);
277
278
return response;
279
});
280
281
// Authentication middleware
282
export default defineEventHandler(async (event) => {
283
// Skip auth for public routes
284
if (event.node.req.url?.startsWith("/api/public/")) {
285
return;
286
}
287
288
const token = getCookie(event, "auth-token") || getHeader(event, "authorization")?.replace("Bearer ", "");
289
290
if (!token) {
291
setResponseStatus(event, 401);
292
return { error: "Authentication required" };
293
}
294
295
try {
296
const user = await validateToken(token);
297
event.context.user = user;
298
} catch (error) {
299
setResponseStatus(event, 401);
300
return { error: "Invalid token" };
301
}
302
});
303
```
304
305
### SSR Configuration Patterns
306
307
```typescript
308
// Conditional SSR
309
const { $device } = useNuxtApp();
310
311
// Disable SSR for mobile devices
312
if (process.server && $device.isMobile) {
313
// Redirect to SPA version
314
await navigateTo("/spa" + useRoute().fullPath);
315
}
316
317
// Server-side environment detection
318
const isServer = process.server;
319
const isClient = process.client;
320
const isDev = process.dev;
321
322
// Server-only operations
323
if (process.server) {
324
// Database connections, file system access, etc.
325
const dbConnection = await connectToDatabase();
326
327
// Server-side analytics
328
await trackServerSideEvent({
329
event: "page_view",
330
url: event.node.req.url,
331
userAgent: getHeader(event, "user-agent")
332
});
333
}
334
335
// Client-only operations
336
if (process.client) {
337
// Browser APIs, DOM manipulation, etc.
338
const analytics = await import("~/plugins/analytics.client");
339
analytics.track("page_view");
340
}
341
```
342
343
### Error Handling in SSR
344
345
```typescript
346
// Server-side error handling
347
try {
348
const data = await fetchServerData();
349
} catch (error) {
350
if (process.server) {
351
// Log server errors
352
console.error("Server error:", error);
353
354
// Set appropriate status
355
setResponseStatus(500);
356
}
357
358
throw createError({
359
statusCode: 500,
360
statusMessage: "Internal server error",
361
data: process.dev ? error.stack : undefined
362
});
363
}
364
365
// Hydration error handling
366
try {
367
useHydration("sensitive-data",
368
() => serverData.value,
369
(value) => { clientData.value = value; }
370
);
371
} catch (error) {
372
// Handle hydration mismatch
373
console.warn("Hydration mismatch:", error);
374
375
// Fallback to client-side data
376
clientData.value = await fetchClientData();
377
}
378
379
// Request timeout handling
380
const event = useRequestEvent();
381
const timeout = setTimeout(() => {
382
setResponseStatus(408);
383
throw createError({
384
statusCode: 408,
385
statusMessage: "Request timeout"
386
});
387
}, 30000); // 30 second timeout
388
389
try {
390
const result = await longRunningOperation();
391
clearTimeout(timeout);
392
return result;
393
} catch (error) {
394
clearTimeout(timeout);
395
throw error;
396
}
397
```
398
399
## Advanced SSR Patterns
400
401
```typescript
402
// Progressive hydration
403
const shouldHydrate = ref(false);
404
405
onMounted(() => {
406
// Delay hydration until critical resources load
407
requestIdleCallback(() => {
408
shouldHydrate.value = true;
409
});
410
});
411
412
// Selective hydration based on user interaction
413
const hasInteracted = ref(false);
414
415
useHydration("interactive-component",
416
() => serverComponent.value,
417
(value) => {
418
if (hasInteracted.value) {
419
clientComponent.value = value;
420
}
421
}
422
);
423
424
// Server-side caching
425
const cacheKey = `page:${route.path}:${JSON.stringify(route.query)}`;
426
const cachedData = await redis.get(cacheKey);
427
428
if (cachedData) {
429
return JSON.parse(cachedData);
430
} else {
431
const data = await fetchFreshData();
432
await redis.setex(cacheKey, 300, JSON.stringify(data)); // Cache for 5 minutes
433
return data;
434
}
435
436
// Server-side A/B testing
437
const event = useRequestEvent();
438
const variant = getCookie(event, "ab-test-variant") ||
439
(Math.random() > 0.5 ? "A" : "B");
440
441
setCookie(event, "ab-test-variant", variant, {
442
maxAge: 60 * 60 * 24 * 30 // 30 days
443
});
444
445
const pageConfig = variant === "A" ? configA : configB;
446
```
447
448
## Types
449
450
```typescript { .api }
451
interface H3Event {
452
node: {
453
req: IncomingMessage;
454
res: ServerResponse;
455
};
456
context: Record<string, any>;
457
headers: Record<string, string>;
458
method: string;
459
path: string;
460
body?: any;
461
}
462
463
interface $Fetch {
464
<T = any>(request: string, opts?: FetchOptions): Promise<T>;
465
create(defaults: FetchOptions): $Fetch;
466
}
467
468
interface FetchOptions {
469
method?: string;
470
headers?: Record<string, string>;
471
body?: any;
472
query?: Record<string, any>;
473
timeout?: number;
474
retry?: number;
475
onRequest?: (context: { request: string; options: FetchOptions }) => void;
476
onResponse?: (context: { request: string; response: Response }) => void;
477
onRequestError?: (context: { request: string; error: Error }) => void;
478
onResponseError?: (context: { request: string; response: Response }) => void;
479
}
480
481
interface UserData {
482
id: number;
483
name: string;
484
email: string;
485
preferences: Record<string, any>;
486
}
487
488
interface CartItem {
489
id: number;
490
name: string;
491
price: number;
492
quantity: number;
493
}
494
```
495
496
### Additional Server Context APIs
497
498
Advanced server-side utilities for request context, configuration, and rendering optimization.
499
500
```typescript { .api }
501
/**
502
* Get the full request URL (server-side only)
503
* @returns URL object representing the request URL
504
*/
505
function useRequestURL(): URL;
506
507
/**
508
* Access app configuration (reactive)
509
* @returns Reactive app configuration object
510
*/
511
function useAppConfig<T = AppConfig>(): T;
512
513
/**
514
* Update app configuration reactively
515
* @param config - Configuration updates to merge
516
*/
517
function updateAppConfig<T = AppConfig>(config: Partial<T>): void;
518
519
/**
520
* Check if in preview mode
521
* @returns Object with enabled state and preview data
522
*/
523
function usePreviewMode(): { enabled: boolean };
524
525
/**
526
* Prerender additional routes (server-side only)
527
* @param routes - Routes to prerender
528
*/
529
function prerenderRoutes(routes: string | string[]): void;
530
```
531
532
**Usage Examples:**
533
534
```typescript
535
// Get request URL server-side
536
const requestURL = useRequestURL();
537
console.log('Full URL:', requestURL.href);
538
console.log('Origin:', requestURL.origin);
539
540
// Access app configuration
541
const appConfig = useAppConfig();
542
console.log('App theme:', appConfig.theme);
543
544
// Update app config reactively
545
updateAppConfig({
546
theme: 'dark',
547
apiUrl: 'https://api.example.com'
548
});
549
550
// Check preview mode
551
const { enabled } = usePreviewMode();
552
if (enabled) {
553
console.log('Running in preview mode');
554
}
555
556
// Prerender additional routes
557
prerenderRoutes(['/sitemap.xml', '/robots.txt']);
558
```
559
560
## Additional Types
561
562
```typescript { .api }
563
interface AppConfig {
564
[key: string]: any;
565
}
566
```