or run

npx @tessl/cli init
Log in

Version

Tile

Overview

Evals

Files

Files

docs

asset-resolution.mdbuiltin-components.mdcomponents.mdcomposition-helpers.mddependency-injection.mderror-handling.mdhydration.mdindex.mdinternal-render-helpers.mdlifecycle.mdreactivity.mdscheduler-timing.mdssr-context.mdvdom-rendering.mdwatch-effects.md

ssr-context.mddocs/

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