or run

npx @tessl/cli init
Log in

Version

Tile

Overview

Evals

Files

Files

docs

app-lifecycle.mdconfiguration.mdcore.mddata-fetching.mdhead.mdindex.mdmodule-dev.mdnavigation.mdperformance.mdssr.mdstate.md

ssr.mddocs/

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

```