0
# SSR Context
1
2
Vue's Server-Side Rendering (SSR) context system enables sharing data between server and client during the hydration process, supporting universal applications with server-rendered initial state.
3
4
## Capabilities
5
6
### SSR Context Access
7
8
Access and provide SSR context data during server-side rendering.
9
10
```typescript { .api }
11
/**
12
* Gets the current SSR context (only available during SSR)
13
* @returns SSR context object or undefined on client
14
*/
15
function useSSRContext<T = Record<string, any>>(): T | undefined;
16
17
/**
18
* Injection key for SSR context
19
*/
20
const ssrContextKey: InjectionKey<Record<string, any>>;
21
```
22
23
**Usage Examples:**
24
25
```typescript
26
import { defineComponent, useSSRContext, ssrContextKey, inject } from "@vue/runtime-core";
27
28
// Basic SSR context usage
29
const SSRComponent = defineComponent({
30
setup() {
31
const ssrContext = useSSRContext();
32
33
if (ssrContext) {
34
// We're on the server
35
console.log('Rendering on server');
36
37
// Add data to SSR context
38
ssrContext.title = 'My App - Home Page';
39
ssrContext.meta = {
40
description: 'Welcome to my application',
41
keywords: 'vue, ssr, typescript'
42
};
43
44
// Add CSS files to be included
45
ssrContext.css = ssrContext.css || [];
46
ssrContext.css.push('/styles/home.css');
47
48
// Add preload hints
49
ssrContext.preloadLinks = ssrContext.preloadLinks || [];
50
ssrContext.preloadLinks.push({
51
href: '/api/data',
52
as: 'fetch',
53
crossorigin: 'anonymous'
54
});
55
} else {
56
// We're on the client
57
console.log('Hydrating on client');
58
}
59
60
return {};
61
}
62
});
63
64
// Using injection key for SSR context
65
const InjectionComponent = defineComponent({
66
setup() {
67
const ssrContext = inject(ssrContextKey);
68
69
if (ssrContext) {
70
// Server-side logic
71
ssrContext.userData = {
72
timestamp: Date.now(),
73
userAgent: 'server'
74
};
75
}
76
77
return {};
78
}
79
});
80
81
// Conditional rendering based on SSR
82
const UniversalComponent = defineComponent({
83
setup() {
84
const isSSR = !!useSSRContext();
85
86
return () => {
87
if (isSSR) {
88
return h('div', 'Server-rendered content');
89
} else {
90
return h('div', 'Client-rendered content');
91
}
92
};
93
}
94
});
95
```
96
97
### SSR Data Management
98
99
Manage data transfer between server and client rendering.
100
101
```typescript
102
// SSR data store
103
const useSSRDataStore = () => {
104
const ssrContext = useSSRContext();
105
106
const setSSRData = (key: string, data: any) => {
107
if (ssrContext) {
108
ssrContext.state = ssrContext.state || {};
109
ssrContext.state[key] = data;
110
}
111
};
112
113
const getSSRData = (key: string, defaultValue: any = null) => {
114
if (ssrContext?.state) {
115
return ssrContext.state[key] ?? defaultValue;
116
}
117
118
// On client, try to get from window.__SSR_STATE__
119
if (typeof window !== 'undefined' && window.__SSR_STATE__) {
120
return window.__SSR_STATE__[key] ?? defaultValue;
121
}
122
123
return defaultValue;
124
};
125
126
return {
127
setSSRData,
128
getSSRData,
129
isSSR: !!ssrContext
130
};
131
};
132
133
// Usage in components
134
const DataComponent = defineComponent({
135
async setup() {
136
const { setSSRData, getSSRData, isSSR } = useSSRDataStore();
137
const data = ref(null);
138
139
if (isSSR) {
140
// Fetch data on server
141
try {
142
const response = await fetch('https://api.example.com/data');
143
const serverData = await response.json();
144
145
data.value = serverData;
146
setSSRData('componentData', serverData);
147
} catch (error) {
148
console.error('SSR data fetch failed:', error);
149
setSSRData('componentData', null);
150
}
151
} else {
152
// Get data from SSR state on client
153
data.value = getSSRData('componentData');
154
155
// If no SSR data, fetch on client
156
if (!data.value) {
157
const response = await fetch('https://api.example.com/data');
158
data.value = await response.json();
159
}
160
}
161
162
return { data };
163
}
164
});
165
```
166
167
### SSR Metadata Management
168
169
Manage document metadata during server-side rendering.
170
171
```typescript
172
// Head management composable
173
const useSSRHead = () => {
174
const ssrContext = useSSRContext();
175
176
const setTitle = (title: string) => {
177
if (ssrContext) {
178
ssrContext.title = title;
179
} else if (typeof document !== 'undefined') {
180
document.title = title;
181
}
182
};
183
184
const addMeta = (meta: { name?: string; property?: string; content: string }) => {
185
if (ssrContext) {
186
ssrContext.meta = ssrContext.meta || [];
187
ssrContext.meta.push(meta);
188
} else if (typeof document !== 'undefined') {
189
const metaElement = document.createElement('meta');
190
if (meta.name) metaElement.name = meta.name;
191
if (meta.property) metaElement.setAttribute('property', meta.property);
192
metaElement.content = meta.content;
193
document.head.appendChild(metaElement);
194
}
195
};
196
197
const addLink = (link: { rel: string; href: string; [key: string]: string }) => {
198
if (ssrContext) {
199
ssrContext.links = ssrContext.links || [];
200
ssrContext.links.push(link);
201
} else if (typeof document !== 'undefined') {
202
const linkElement = document.createElement('link');
203
Object.entries(link).forEach(([key, value]) => {
204
linkElement.setAttribute(key, value);
205
});
206
document.head.appendChild(linkElement);
207
}
208
};
209
210
const addScript = (script: { src?: string; innerHTML?: string; [key: string]: any }) => {
211
if (ssrContext) {
212
ssrContext.scripts = ssrContext.scripts || [];
213
ssrContext.scripts.push(script);
214
} else if (typeof document !== 'undefined') {
215
const scriptElement = document.createElement('script');
216
if (script.src) scriptElement.src = script.src;
217
if (script.innerHTML) scriptElement.innerHTML = script.innerHTML;
218
219
Object.entries(script).forEach(([key, value]) => {
220
if (key !== 'src' && key !== 'innerHTML') {
221
scriptElement.setAttribute(key, value);
222
}
223
});
224
225
document.head.appendChild(scriptElement);
226
}
227
};
228
229
return {
230
setTitle,
231
addMeta,
232
addLink,
233
addScript
234
};
235
};
236
237
// SEO component
238
const SEOComponent = defineComponent({
239
props: {
240
title: String,
241
description: String,
242
keywords: String,
243
image: String,
244
url: String
245
},
246
247
setup(props) {
248
const { setTitle, addMeta, addLink } = useSSRHead();
249
250
// Set page title
251
if (props.title) {
252
setTitle(props.title);
253
}
254
255
// Basic meta tags
256
if (props.description) {
257
addMeta({ name: 'description', content: props.description });
258
}
259
260
if (props.keywords) {
261
addMeta({ name: 'keywords', content: props.keywords });
262
}
263
264
// Open Graph meta tags
265
if (props.title) {
266
addMeta({ property: 'og:title', content: props.title });
267
}
268
269
if (props.description) {
270
addMeta({ property: 'og:description', content: props.description });
271
}
272
273
if (props.image) {
274
addMeta({ property: 'og:image', content: props.image });
275
}
276
277
if (props.url) {
278
addMeta({ property: 'og:url', content: props.url });
279
addLink({ rel: 'canonical', href: props.url });
280
}
281
282
// Twitter Card meta tags
283
addMeta({ name: 'twitter:card', content: 'summary_large_image' });
284
285
if (props.title) {
286
addMeta({ name: 'twitter:title', content: props.title });
287
}
288
289
if (props.description) {
290
addMeta({ name: 'twitter:description', content: props.description });
291
}
292
293
if (props.image) {
294
addMeta({ name: 'twitter:image', content: props.image });
295
}
296
297
return () => null; // This component doesn't render anything
298
}
299
});
300
```
301
302
### SSR Performance Optimization
303
304
Optimize performance with resource hints and preloading.
305
306
```typescript
307
// Resource preloading
308
const useSSRPreload = () => {
309
const ssrContext = useSSRContext();
310
311
const preloadResource = (href: string, as: string, crossorigin?: string) => {
312
if (ssrContext) {
313
ssrContext.preloadLinks = ssrContext.preloadLinks || [];
314
ssrContext.preloadLinks.push({
315
rel: 'preload',
316
href,
317
as,
318
...(crossorigin && { crossorigin })
319
});
320
} else if (typeof document !== 'undefined') {
321
const link = document.createElement('link');
322
link.rel = 'preload';
323
link.href = href;
324
link.as = as;
325
if (crossorigin) link.crossOrigin = crossorigin;
326
document.head.appendChild(link);
327
}
328
};
329
330
const prefetchResource = (href: string) => {
331
if (ssrContext) {
332
ssrContext.prefetchLinks = ssrContext.prefetchLinks || [];
333
ssrContext.prefetchLinks.push({
334
rel: 'prefetch',
335
href
336
});
337
} else if (typeof document !== 'undefined') {
338
const link = document.createElement('link');
339
link.rel = 'prefetch';
340
link.href = href;
341
document.head.appendChild(link);
342
}
343
};
344
345
return {
346
preloadResource,
347
prefetchResource
348
};
349
};
350
351
// Critical CSS management
352
const useSSRCriticalCSS = () => {
353
const ssrContext = useSSRContext();
354
355
const addCriticalCSS = (css: string) => {
356
if (ssrContext) {
357
ssrContext.criticalCSS = ssrContext.criticalCSS || [];
358
ssrContext.criticalCSS.push(css);
359
}
360
};
361
362
const addStylesheet = (href: string, media = 'all') => {
363
if (ssrContext) {
364
ssrContext.stylesheets = ssrContext.stylesheets || [];
365
ssrContext.stylesheets.push({ href, media });
366
} else if (typeof document !== 'undefined') {
367
const link = document.createElement('link');
368
link.rel = 'stylesheet';
369
link.href = href;
370
link.media = media;
371
document.head.appendChild(link);
372
}
373
};
374
375
return {
376
addCriticalCSS,
377
addStylesheet
378
};
379
};
380
```
381
382
### Advanced SSR Patterns
383
384
```typescript
385
// SSR-safe component wrapper
386
const createSSRSafeComponent = <P extends Record<string, any>>(
387
component: Component<P>,
388
fallback?: Component<P>
389
) => {
390
return defineComponent({
391
props: component.props,
392
setup(props, ctx) {
393
const isSSR = !!useSSRContext();
394
395
if (isSSR && fallback) {
396
return () => h(fallback, props);
397
}
398
399
return () => h(component, props);
400
}
401
});
402
};
403
404
// Client-only component
405
const ClientOnly = defineComponent({
406
setup(_, { slots }) {
407
const isSSR = !!useSSRContext();
408
const isMounted = ref(false);
409
410
onMounted(() => {
411
isMounted.value = true;
412
});
413
414
return () => {
415
if (isSSR || !isMounted.value) {
416
return slots.fallback?.() || null;
417
}
418
return slots.default?.();
419
};
420
}
421
});
422
423
// SSR context provider
424
const SSRContextProvider = defineComponent({
425
props: {
426
context: { type: Object, required: true }
427
},
428
429
setup(props, { slots }) {
430
provide(ssrContextKey, props.context);
431
432
return () => slots.default?.();
433
}
434
});
435
436
// Universal data fetching
437
const useUniversalFetch = <T>(
438
url: string,
439
options: RequestInit = {}
440
): { data: Ref<T | null>; loading: Ref<boolean>; error: Ref<Error | null> } => {
441
const data = ref<T | null>(null);
442
const loading = ref(true);
443
const error = ref<Error | null>(null);
444
const { setSSRData, getSSRData, isSSR } = useSSRDataStore();
445
446
const cacheKey = `fetch_${url}`;
447
448
const fetchData = async () => {
449
try {
450
loading.value = true;
451
error.value = null;
452
453
const response = await fetch(url, options);
454
if (!response.ok) {
455
throw new Error(`HTTP ${response.status}: ${response.statusText}`);
456
}
457
458
const result = await response.json();
459
data.value = result;
460
461
if (isSSR) {
462
setSSRData(cacheKey, result);
463
}
464
} catch (err) {
465
error.value = err instanceof Error ? err : new Error(String(err));
466
if (isSSR) {
467
setSSRData(cacheKey, null);
468
}
469
} finally {
470
loading.value = false;
471
}
472
};
473
474
if (isSSR) {
475
// Fetch on server
476
fetchData();
477
} else {
478
// Try to get from SSR cache first
479
const cachedData = getSSRData(cacheKey);
480
if (cachedData !== null) {
481
data.value = cachedData;
482
loading.value = false;
483
} else {
484
// Fetch on client if not in cache
485
fetchData();
486
}
487
}
488
489
return { data, loading, error };
490
};
491
```
492
493
## Types
494
495
```typescript { .api }
496
interface SSRContext extends Record<string, any> {
497
// HTML document metadata
498
title?: string;
499
meta?: Array<{ name?: string; property?: string; content: string }>;
500
links?: Array<Record<string, string>>;
501
scripts?: Array<{ src?: string; innerHTML?: string; [key: string]: any }>;
502
503
// Resource optimization
504
preloadLinks?: Array<{ rel: 'preload'; href: string; as: string; crossorigin?: string }>;
505
prefetchLinks?: Array<{ rel: 'prefetch'; href: string }>;
506
stylesheets?: Array<{ href: string; media?: string }>;
507
criticalCSS?: string[];
508
509
// Application state
510
state?: Record<string, any>;
511
512
// Request context (server-only)
513
url?: string;
514
request?: any;
515
response?: any;
516
}
517
518
// Global SSR state interface
519
declare global {
520
interface Window {
521
__SSR_STATE__?: Record<string, any>;
522
}
523
}
524
525
type SSRContextKey = InjectionKey<SSRContext>;
526
```
527
528
## Server-Side Integration
529
530
### Express.js Integration
531
532
```typescript
533
// Server-side rendering with Express
534
app.get('*', async (req, res) => {
535
const ssrContext: SSRContext = {
536
url: req.url,
537
request: req,
538
response: res,
539
state: {}
540
};
541
542
try {
543
const html = await renderToString(app, ssrContext);
544
545
// Build HTML with SSR context data
546
const finalHtml = `
547
<!DOCTYPE html>
548
<html>
549
<head>
550
<title>${ssrContext.title || 'My App'}</title>
551
${ssrContext.meta?.map(meta =>
552
`<meta ${meta.name ? `name="${meta.name}"` : ''}
553
${meta.property ? `property="${meta.property}"` : ''}
554
content="${meta.content}">`
555
).join('\n') || ''}
556
557
${ssrContext.preloadLinks?.map(link =>
558
`<link rel="${link.rel}" href="${link.href}" as="${link.as}"
559
${link.crossorigin ? `crossorigin="${link.crossorigin}"` : ''}>`
560
).join('\n') || ''}
561
562
${ssrContext.criticalCSS?.map(css =>
563
`<style>${css}</style>`
564
).join('\n') || ''}
565
</head>
566
<body>
567
<div id="app">${html}</div>
568
<script>
569
window.__SSR_STATE__ = ${JSON.stringify(ssrContext.state)};
570
</script>
571
<script src="/dist/client.js"></script>
572
</body>
573
</html>
574
`;
575
576
res.send(finalHtml);
577
} catch (error) {
578
console.error('SSR Error:', error);
579
res.status(500).send('Server Error');
580
}
581
});
582
```
583
584
## Best Practices
585
586
1. **Hydration Mismatch Prevention**: Ensure server and client render identical content
587
2. **Performance Optimization**: Use resource preloading and critical CSS extraction
588
3. **Error Handling**: Gracefully handle SSR failures with client-side fallbacks
589
4. **State Management**: Properly serialize and transfer state between server and client
590
5. **SEO Optimization**: Use SSR context for meta tags and structured data