or run

npx @tessl/cli init
Log in

Version

Tile

Overview

Evals

Files

Files

docs

context.mderror-handling.mdindex.mdinfinite-queries.mdmutations.mdparallel-queries.mdqueries.mdssr.mdstatus.md

ssr.mddocs/

0

# SSR & Hydration

1

2

Server-side rendering support with state dehydration and hydration for seamless SSR/SSG integration. React Query provides utilities to serialize server-side data and rehydrate it on the client.

3

4

## Capabilities

5

6

### useHydrate Hook

7

8

Hook for hydrating QueryClient with server-side rendered state.

9

10

```typescript { .api }

11

/**

12

* Hydrate QueryClient with server-side state

13

* @param state - Dehydrated state from server

14

* @param options - Hydration configuration options

15

*/

16

function useHydrate(

17

state: unknown,

18

options?: HydrateOptions & ContextOptions

19

): void;

20

21

interface HydrateOptions {

22

/** Function to determine which queries should be hydrated */

23

shouldDehydrateQuery?: ShouldDehydrateQueryFunction;

24

/** Function to determine which mutations should be hydrated */

25

shouldDehydrateMutation?: ShouldDehydrateMutationFunction;

26

/** Default options for hydrated queries */

27

defaultOptions?: {

28

queries?: QueryOptions;

29

mutations?: MutationOptions;

30

};

31

}

32

33

interface ContextOptions {

34

/** Custom React context to use */

35

context?: React.Context<QueryClient | undefined>;

36

}

37

38

type ShouldDehydrateQueryFunction = (query: Query) => boolean;

39

type ShouldDehydrateMutationFunction = (mutation: Mutation) => boolean;

40

```

41

42

**Usage Examples:**

43

44

```typescript

45

import { useHydrate } from "react-query";

46

47

// Basic hydration

48

function App({ dehydratedState }: { dehydratedState: any }) {

49

useHydrate(dehydratedState);

50

51

return (

52

<div>

53

<UserProfile />

54

<PostsList />

55

</div>

56

);

57

}

58

59

// Hydration with options

60

function AppWithOptions({ dehydratedState }: { dehydratedState: any }) {

61

useHydrate(dehydratedState, {

62

defaultOptions: {

63

queries: {

64

staleTime: 60 * 1000 // 1 minute

65

}

66

}

67

});

68

69

return <MainContent />;

70

}

71

72

// Conditional hydration

73

function ConditionalHydration({ dehydratedState, shouldHydrate }: {

74

dehydratedState: any;

75

shouldHydrate: boolean;

76

}) {

77

useHydrate(shouldHydrate ? dehydratedState : undefined);

78

79

return <Content />;

80

}

81

```

82

83

### Hydrate Component

84

85

Component wrapper for hydrating server-side state.

86

87

```typescript { .api }

88

/**

89

* Component that hydrates QueryClient with server-side state

90

* @param props - Hydration props with state and options

91

* @returns Children with hydrated state

92

*/

93

function Hydrate(props: HydrateProps): React.ReactElement;

94

95

interface HydrateProps {

96

/** Dehydrated state from server */

97

state?: unknown;

98

/** Hydration configuration options */

99

options?: HydrateOptions;

100

/** Child components to render */

101

children?: React.ReactNode;

102

}

103

```

104

105

**Usage Examples:**

106

107

```typescript

108

import { Hydrate, QueryClient, QueryClientProvider } from "react-query";

109

110

// Basic component usage

111

function App({ pageProps }: { pageProps: any }) {

112

const queryClient = new QueryClient();

113

114

return (

115

<QueryClientProvider client={queryClient}>

116

<Hydrate state={pageProps.dehydratedState}>

117

<MyApp {...pageProps} />

118

</Hydrate>

119

</QueryClientProvider>

120

);

121

}

122

123

// Next.js integration

124

function MyApp({ Component, pageProps }: AppProps) {

125

const [queryClient] = useState(() => new QueryClient());

126

127

return (

128

<QueryClientProvider client={queryClient}>

129

<Hydrate state={pageProps.dehydratedState}>

130

<Component {...pageProps} />

131

</Hydrate>

132

</QueryClientProvider>

133

);

134

}

135

136

// Multiple hydration levels

137

function NestedHydration({

138

globalState,

139

pageState,

140

children

141

}: {

142

globalState: any;

143

pageState: any;

144

children: React.ReactNode;

145

}) {

146

return (

147

<Hydrate state={globalState}>

148

<div>

149

<GlobalNav />

150

<Hydrate state={pageState}>

151

{children}

152

</Hydrate>

153

</div>

154

</Hydrate>

155

);

156

}

157

```

158

159

### IsRestoringProvider Component

160

161

Context provider for tracking restoration state during SSR hydration.

162

163

```typescript { .api }

164

/**

165

* Provider for restoration state context

166

* Used internally to track hydration state

167

*/

168

const IsRestoringProvider: React.Provider<boolean>;

169

170

/**

171

* Hook to check if app is currently restoring from SSR

172

* @returns Boolean indicating if restoration is in progress

173

*/

174

function useIsRestoring(): boolean;

175

```

176

177

**Usage Examples:**

178

179

```typescript

180

import { useIsRestoring, IsRestoringProvider } from "react-query";

181

182

// Check restoration state

183

function MyComponent() {

184

const isRestoring = useIsRestoring();

185

186

if (isRestoring) {

187

return <div>Restoring from server...</div>;

188

}

189

190

return <div>Client-side rendering active</div>;

191

}

192

193

// Custom restoration provider (rarely needed)

194

function CustomRestorationProvider({ children }: { children: React.ReactNode }) {

195

const [isRestoring, setIsRestoring] = useState(true);

196

197

useEffect(() => {

198

// Custom logic to determine when restoration is complete

199

const timer = setTimeout(() => setIsRestoring(false), 100);

200

return () => clearTimeout(timer);

201

}, []);

202

203

return (

204

<IsRestoringProvider value={isRestoring}>

205

{children}

206

</IsRestoringProvider>

207

);

208

}

209

```

210

211

### Core Hydration Functions

212

213

Low-level functions for dehydrating and hydrating QueryClient state.

214

215

```typescript { .api }

216

/**

217

* Serialize QueryClient state for server-side rendering

218

* @param client - QueryClient instance to dehydrate

219

* @param options - Dehydration configuration

220

* @returns Serializable state object

221

*/

222

function dehydrate(

223

client: QueryClient,

224

options?: DehydrateOptions

225

): DehydratedState;

226

227

/**

228

* Restore QueryClient state from serialized data

229

* @param client - QueryClient instance to hydrate

230

* @param dehydratedState - Serialized state from dehydrate

231

* @param options - Hydration configuration

232

*/

233

function hydrate(

234

client: QueryClient,

235

dehydratedState: unknown,

236

options?: HydrateOptions

237

): void;

238

239

interface DehydrateOptions {

240

/** Function to determine which queries should be dehydrated */

241

shouldDehydrateQuery?: ShouldDehydrateQueryFunction;

242

/** Function to determine which mutations should be dehydrated */

243

shouldDehydrateMutation?: ShouldDehydrateMutationFunction;

244

}

245

246

interface DehydratedState {

247

/** Serialized queries */

248

queries: Array<{

249

queryKey: QueryKey;

250

queryHash: string;

251

state: QueryState;

252

}>;

253

/** Serialized mutations */

254

mutations: Array<{

255

mutationKey?: MutationKey;

256

state: MutationState;

257

}>;

258

}

259

```

260

261

## Advanced Usage Patterns

262

263

### Next.js Integration

264

265

Complete Next.js SSR/SSG setup with React Query:

266

267

```typescript

268

// pages/_app.tsx

269

import { useState } from 'react';

270

import { Hydrate, QueryClient, QueryClientProvider } from 'react-query';

271

import { ReactQueryDevtools } from 'react-query/devtools';

272

import type { AppProps } from 'next/app';

273

274

export default function MyApp({ Component, pageProps }: AppProps) {

275

const [queryClient] = useState(() => new QueryClient({

276

defaultOptions: {

277

queries: {

278

staleTime: 60 * 1000, // 1 minute

279

refetchOnWindowFocus: false,

280

},

281

},

282

}));

283

284

return (

285

<QueryClientProvider client={queryClient}>

286

<Hydrate state={pageProps.dehydratedState}>

287

<Component {...pageProps} />

288

</Hydrate>

289

<ReactQueryDevtools initialIsOpen={false} />

290

</QueryClientProvider>

291

);

292

}

293

294

// pages/users/[id].tsx

295

import { GetServerSideProps } from 'next';

296

import { QueryClient, dehydrate } from 'react-query';

297

298

export default function UserPage({ userId }: { userId: string }) {

299

const { data: user } = useQuery({

300

queryKey: ['user', userId],

301

queryFn: () => fetchUser(userId)

302

});

303

304

const { data: posts } = useQuery({

305

queryKey: ['posts', userId],

306

queryFn: () => fetchUserPosts(userId)

307

});

308

309

return (

310

<div>

311

<h1>{user?.name}</h1>

312

<PostsList posts={posts} />

313

</div>

314

);

315

}

316

317

export const getServerSideProps: GetServerSideProps = async ({ params }) => {

318

const queryClient = new QueryClient();

319

const userId = params?.id as string;

320

321

// Prefetch data on server

322

await queryClient.prefetchQuery({

323

queryKey: ['user', userId],

324

queryFn: () => fetchUser(userId)

325

});

326

327

await queryClient.prefetchQuery({

328

queryKey: ['posts', userId],

329

queryFn: () => fetchUserPosts(userId)

330

});

331

332

return {

333

props: {

334

dehydratedState: dehydrate(queryClient),

335

userId

336

}

337

};

338

};

339

```

340

341

### Selective Hydration

342

343

Only hydrating specific queries to reduce bundle size:

344

345

```typescript

346

// Server-side dehydration with filtering

347

export const getServerSideProps: GetServerSideProps = async () => {

348

const queryClient = new QueryClient();

349

350

// Prefetch multiple queries

351

await Promise.all([

352

queryClient.prefetchQuery({

353

queryKey: ['user', 'current'],

354

queryFn: fetchCurrentUser

355

}),

356

queryClient.prefetchQuery({

357

queryKey: ['posts', 'popular'],

358

queryFn: fetchPopularPosts

359

}),

360

queryClient.prefetchQuery({

361

queryKey: ['analytics', 'daily'],

362

queryFn: fetchDailyAnalytics

363

})

364

]);

365

366

return {

367

props: {

368

dehydratedState: dehydrate(queryClient, {

369

shouldDehydrateQuery: (query) => {

370

// Only dehydrate user and posts, not analytics

371

const queryKey = query.queryKey[0] as string;

372

return ['user', 'posts'].includes(queryKey);

373

}

374

})

375

}

376

};

377

};

378

379

// Client-side selective hydration

380

function App({ dehydratedState }: { dehydratedState: any }) {

381

useHydrate(dehydratedState, {

382

shouldDehydrateQuery: (query) => {

383

// Only hydrate queries that are not stale

384

return query.state.dataUpdatedAt > Date.now() - 60000; // 1 minute

385

}

386

});

387

388

return <MainContent />;

389

}

390

```

391

392

### Progressive Hydration

393

394

Hydrating data progressively as components mount:

395

396

```typescript

397

function ProgressiveHydrationApp({ dehydratedState }: { dehydratedState: any }) {

398

const [hydratedSections, setHydratedSections] = useState<string[]>([]);

399

400

const hydrateSection = (section: string) => {

401

if (!hydratedSections.includes(section)) {

402

setHydratedSections(prev => [...prev, section]);

403

}

404

};

405

406

return (

407

<div>

408

{/* Always hydrate critical data */}

409

<Hydrate state={dehydratedState.critical}>

410

<Header />

411

</Hydrate>

412

413

{/* Progressively hydrate sections */}

414

<InView onChange={(inView) => inView && hydrateSection('main')}>

415

{hydratedSections.includes('main') ? (

416

<Hydrate state={dehydratedState.main}>

417

<MainContent />

418

</Hydrate>

419

) : (

420

<div>Loading main content...</div>

421

)}

422

</InView>

423

424

<InView onChange={(inView) => inView && hydrateSection('sidebar')}>

425

{hydratedSections.includes('sidebar') ? (

426

<Hydrate state={dehydratedState.sidebar}>

427

<Sidebar />

428

</Hydrate>

429

) : (

430

<div>Loading sidebar...</div>

431

)}

432

</InView>

433

</div>

434

);

435

}

436

```

437

438

### Error Handling in SSR

439

440

Handling errors during server-side prefetching:

441

442

```typescript

443

// Robust server-side prefetching

444

export const getServerSideProps: GetServerSideProps = async ({ params }) => {

445

const queryClient = new QueryClient({

446

defaultOptions: {

447

queries: {

448

retry: false, // Don't retry on server

449

staleTime: Infinity, // Keep data fresh until client hydration

450

},

451

},

452

});

453

454

const userId = params?.id as string;

455

456

try {

457

// Prefetch critical data

458

await queryClient.prefetchQuery({

459

queryKey: ['user', userId],

460

queryFn: () => fetchUser(userId)

461

});

462

463

// Prefetch optional data (don't fail if this errors)

464

await queryClient.prefetchQuery({

465

queryKey: ['posts', userId],

466

queryFn: () => fetchUserPosts(userId)

467

}).catch(error => {

468

console.warn('Failed to prefetch posts:', error);

469

});

470

471

} catch (error) {

472

console.error('Failed to prefetch user:', error);

473

474

// Return error page or redirect

475

return {

476

notFound: true,

477

};

478

}

479

480

return {

481

props: {

482

dehydratedState: dehydrate(queryClient),

483

userId

484

}

485

};

486

};

487

488

// Client-side error handling during hydration

489

function ErrorBoundaryWithHydration({

490

children,

491

dehydratedState

492

}: {

493

children: React.ReactNode;

494

dehydratedState: any;

495

}) {

496

const [hydrationError, setHydrationError] = useState<Error | null>(null);

497

498

useEffect(() => {

499

try {

500

// Attempt hydration

501

useHydrate(dehydratedState);

502

} catch (error) {

503

setHydrationError(error as Error);

504

}

505

}, [dehydratedState]);

506

507

if (hydrationError) {

508

return (

509

<div>

510

<h2>Hydration Error</h2>

511

<p>Failed to restore server state. Falling back to client-side fetching.</p>

512

<button onClick={() => setHydrationError(null)}>

513

Try Again

514

</button>

515

</div>

516

);

517

}

518

519

return <>{children}</>;

520

}

521

```

522

523

### IsRestoringProvider and useIsRestoring

524

525

Provider and hook for tracking SSR hydration restoration state during client-side hydration.

526

527

```typescript { .api }

528

/**

529

* Provider component for managing restoration state during SSR hydration

530

* @param value - Boolean indicating if restoration is in progress

531

* @param children - Child components

532

*/

533

function IsRestoringProvider(props: {

534

value: boolean;

535

children: React.ReactNode;

536

}): React.ReactElement;

537

538

/**

539

* Hook to check if queries are currently being restored during SSR hydration

540

* @returns Boolean indicating if restoration is in progress

541

*/

542

function useIsRestoring(): boolean;

543

```

544

545

**Usage Examples:**

546

547

```typescript

548

import { IsRestoringProvider, useIsRestoring } from "react-query";

549

550

// App-level restoration tracking

551

function App({ children }: { children: React.ReactNode }) {

552

const [isRestoring, setIsRestoring] = useState(true);

553

554

useEffect(() => {

555

// Set restoration complete after hydration

556

const timer = setTimeout(() => setIsRestoring(false), 100);

557

return () => clearTimeout(timer);

558

}, []);

559

560

return (

561

<IsRestoringProvider value={isRestoring}>

562

<div className={isRestoring ? 'restoring' : 'restored'}>

563

{children}

564

</div>

565

</IsRestoringProvider>

566

);

567

}

568

569

// Component that adapts behavior during restoration

570

function DataComponent() {

571

const isRestoring = useIsRestoring();

572

const { data, isLoading } = useQuery({

573

queryKey: ['data'],

574

queryFn: fetchData

575

});

576

577

// Don't show loading state during restoration to prevent flash

578

if (isRestoring) {

579

return <div className="skeleton">Loading...</div>;

580

}

581

582

if (isLoading) {

583

return <div className="spinner">Fetching data...</div>;

584

}

585

586

return <div>{JSON.stringify(data)}</div>;

587

}

588

589

// Conditional rendering based on restoration state

590

function OptimizedComponent() {

591

const isRestoring = useIsRestoring();

592

593

return (

594

<div>

595

{isRestoring ? (

596

// Show static content during restoration

597

<div className="static-content">

598

<h1>Welcome</h1>

599

<p>Loading your personalized content...</p>

600

</div>

601

) : (

602

// Show dynamic content after restoration

603

<DynamicContent />

604

)}

605

</div>

606

);

607

}

608

609

// SSR-aware loading states

610

function SmartLoadingComponent() {

611

const isRestoring = useIsRestoring();

612

const { data, isLoading, isFetching } = useQuery({

613

queryKey: ['smart-data'],

614

queryFn: fetchSmartData

615

});

616

617

// During restoration, rely on server-rendered content

618

if (isRestoring) {

619

return data ? (

620

<div className="hydrated-content">{data.content}</div>

621

) : (

622

<div className="placeholder-content">Content loading...</div>

623

);

624

}

625

626

// After restoration, show normal loading states

627

if (isLoading) {

628

return <div className="loading-spinner">Loading...</div>;

629

}

630

631

if (isFetching) {

632

return (

633

<div className="refreshing">

634

<div className="content">{data?.content}</div>

635

<div className="refresh-indicator">Updating...</div>

636

</div>

637

);

638

}

639

640

return <div className="content">{data?.content}</div>;

641

}

642

```

643

644

### Custom Hydration Logic

645

646

Implementing custom hydration strategies:

647

648

```typescript

649

function useCustomHydration(dehydratedState: any) {

650

const queryClient = useQueryClient();

651

const [isHydrated, setIsHydrated] = useState(false);

652

653

useEffect(() => {

654

if (!dehydratedState || isHydrated) return;

655

656

// Custom hydration logic

657

const hydrateData = async () => {

658

try {

659

// Validate dehydrated state

660

if (!isValidDehydratedState(dehydratedState)) {

661

console.warn('Invalid dehydrated state, skipping hydration');

662

return;

663

}

664

665

// Transform data before hydration if needed

666

const transformedState = transformDehydratedState(dehydratedState);

667

668

// Hydrate with custom options

669

hydrate(queryClient, transformedState, {

670

defaultOptions: {

671

queries: {

672

staleTime: 30 * 1000, // 30 seconds

673

cacheTime: 5 * 60 * 1000, // 5 minutes

674

}

675

}

676

});

677

678

setIsHydrated(true);

679

} catch (error) {

680

console.error('Custom hydration failed:', error);

681

}

682

};

683

684

hydrateData();

685

}, [dehydratedState, queryClient, isHydrated]);

686

687

return isHydrated;

688

}

689

690

function isValidDehydratedState(state: any): boolean {

691

return (

692

state &&

693

typeof state === 'object' &&

694

Array.isArray(state.queries) &&

695

Array.isArray(state.mutations)

696

);

697

}

698

699

function transformDehydratedState(state: any): any {

700

// Transform or filter the state as needed

701

return {

702

...state,

703

queries: state.queries.filter((query: any) => {

704

// Only hydrate recent queries

705

return query.state.dataUpdatedAt > Date.now() - 60000;

706

})

707

};

708

}

709

```