@decocms/start 0.28.3 → 0.29.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
@@ -0,0 +1,616 @@
1
+ # Hydration & SSR — Migration to TanStack Native Patterns
2
+
3
+ > Migration guide for `@decocms/start` to adopt TanStack Router/Start native SSR, hydration, and deferred data patterns. Eliminates custom server function workarounds and aligns with the framework's execution model.
4
+
5
+ ---
6
+
7
+ ## Table of Contents
8
+
9
+ 1. [Current Architecture & Problems](#1-current-architecture--problems)
10
+ 2. [TanStack Native Patterns We Should Adopt](#2-tanstack-native-patterns-we-should-adopt)
11
+ 3. [Migration Plan](#3-migration-plan)
12
+ 4. [Issue-by-Issue Fix Guide](#4-issue-by-issue-fix-guide)
13
+ 5. [Testing Checklist](#5-testing-checklist)
14
+
15
+ ---
16
+
17
+ ## 1. Current Architecture & Problems
18
+
19
+ ### How it works today
20
+
21
+ ```
22
+ ┌─────────────────────────────────────────────────────────┐
23
+ │ CMS Page Request │
24
+ │ │
25
+ │ 1. loadCmsPage (GET server function) │
26
+ │ ├── resolves eager sections (Header, Footer, Theme) │
27
+ │ └── extracts deferred section metadata │
28
+ │ │
29
+ │ 2. SSR renders eager sections + skeleton placeholders │
30
+ │ │
31
+ │ 3. Client hydrates, IntersectionObserver fires │
32
+ │ └── loadDeferredSection (POST server function) │
33
+ │ └── resolves section + runs loader │
34
+ │ └── returns enriched props │
35
+ │ │
36
+ │ 4. Client renders the section, fades in │
37
+ └─────────────────────────────────────────────────────────┘
38
+ ```
39
+
40
+ ### Problems found in production
41
+
42
+ #### P1: `loadDeferredSection` fails in Cloudflare Workers dev mode
43
+
44
+ **Error:**
45
+ ```
46
+ Cannot perform I/O on behalf of a different request.
47
+ I/O objects (such as streams, request/response bodies, and others) created
48
+ in the context of one request handler cannot be accessed from a different
49
+ request's handler. (I/O type: SpanParent)
50
+ ```
51
+
52
+ **Root cause:** TanStack Start splits server functions into `?tss-serverfn-split` modules. In Vite dev mode, the Cloudflare worker module runner caches modules per-request. Request A (SSR page load) caches modules with SpanParent I/O objects, Request B (server function POST from client) tries to reuse them — fails because I/O objects are tied to Request A's context.
53
+
54
+ **Impact:** ALL deferred sections fail to load in dev mode. Sites must force sections eager via `alwaysEager`, defeating the purpose of async rendering.
55
+
56
+ #### P2: Eager sections without `registerSectionsSync` render blank
57
+
58
+ When a section is eager (in `resolvedSections`) but NOT registered via `registerSectionsSync()`, `DecoPageRenderer` renders it via `React.lazy` wrapped in `<Suspense fallback={null}>`. During hydration, if the lazy module isn't available synchronously, React unmounts the server HTML and shows the fallback (null) — a blank area.
59
+
60
+ **Why this wasn't noticed before:** Before hydration fixes, script mismatches caused React to do a full client re-render instead of hydration. `React.lazy` works fine on fresh renders, only fails during hydration.
61
+
62
+ #### P3: `useScript(fn)` causes hydration mismatch
63
+
64
+ `useScript(fn)` calls `fn.toString()` + `minifyJs()` to produce inline JavaScript. Vite compiles SSR and client bundles separately — React Compiler transforms may differ, producing different function body strings. Since `dangerouslySetInnerHTML.__html` is checked during hydration, any difference causes:
65
+
66
+ ```
67
+ Warning: A tree hydrated but some attributes of the server rendered HTML
68
+ didn't match the client properties...
69
+ dangerouslySetInnerHTML.__html
70
+ ```
71
+
72
+ **Affected:** `useScriptAsDataURI` has the same issue (it wraps `useScript`).
73
+
74
+ #### P4: Third-party scripts injected into `<head>` break hydration
75
+
76
+ GTM, Emarsys, and similar scripts inject `<script>` elements into `<head>` before React hydration begins. This shifts the DOM tree — React expects the same child count/order that the server rendered, finds extra nodes, and fails hydration.
77
+
78
+ #### P5: N+1 VTEX API calls when all sections are eager
79
+
80
+ When sites force all sections eager (workaround for P1), a typical homepage with 8-12 product shelves fires 16-24+ concurrent VTEX API calls during SSR. This causes:
81
+ - VTEX 503 rate limiting
82
+ - 10+ second SSR times
83
+ - OrderForm/login resolution failures
84
+
85
+ ---
86
+
87
+ ## 2. TanStack Native Patterns We Should Adopt
88
+
89
+ ### 2.1 Deferred Data Loading with `defer()` + `<Await>`
90
+
91
+ **Source:** https://tanstack.com/router/latest/docs/guide/deferred-data-loading
92
+
93
+ TanStack Router has native deferred data support. Instead of a custom server function POST, we can return unawaited promises from the route loader:
94
+
95
+ ```tsx
96
+ // Route loader
97
+ loader: async () => {
98
+ // Fast: resolve immediately (Header, Footer, Theme)
99
+ const eagerSections = await resolveEagerSections(page);
100
+
101
+ // Slow: don't await — starts resolving, streams when ready
102
+ const deferredSectionsPromise = resolveDeferredSections(page);
103
+
104
+ return {
105
+ eagerSections,
106
+ deferredSections: deferredSectionsPromise, // unawaited!
107
+ };
108
+ }
109
+ ```
110
+
111
+ ```tsx
112
+ // Component
113
+ function CmsPage() {
114
+ const { eagerSections, deferredSections } = Route.useLoaderData();
115
+
116
+ return (
117
+ <>
118
+ {eagerSections.map(s => <SectionRenderer key={s.index} section={s} />)}
119
+ <Await promise={deferredSections} fallback={<SectionSkeletons />}>
120
+ {(sections) => sections.map(s => <SectionRenderer key={s.index} section={s} />)}
121
+ </Await>
122
+ </>
123
+ );
124
+ }
125
+ ```
126
+
127
+ **Benefits:**
128
+ - Works in dev mode (no separate server function request)
129
+ - SSR streaming sends skeleton HTML first, then resolved sections
130
+ - Native TanStack cache/invalidation
131
+ - No IntersectionObserver needed for initial load
132
+
133
+ **With React 19 `use()` hook:**
134
+ ```tsx
135
+ // React 19 alternative to <Await>
136
+ function DeferredSections({ promise }) {
137
+ const sections = use(promise);
138
+ return sections.map(s => <SectionRenderer key={s.index} section={s} />);
139
+ }
140
+ ```
141
+
142
+ ### 2.2 `<ClientOnly>` for Browser-Dependent Components
143
+
144
+ **Source:** https://tanstack.com/router/latest/docs/api/router/clientOnlyComponent
145
+
146
+ For components that use browser APIs or produce non-deterministic output (analytics, GTM, geolocation):
147
+
148
+ ```tsx
149
+ import { ClientOnly } from '@tanstack/react-router';
150
+
151
+ // Analytics scripts — no SSR, no hydration mismatch
152
+ function GlobalAnalytics() {
153
+ return (
154
+ <ClientOnly fallback={null}>
155
+ <VtexIsEvents />
156
+ <Sourei gtmId="GTM-XXXXX" />
157
+ </ClientOnly>
158
+ );
159
+ }
160
+ ```
161
+
162
+ **This replaces:** `suppressHydrationWarning`, moving scripts from `<head>` to `<body>`, converting `useScript(fn)` to string constants.
163
+
164
+ ### 2.3 `useHydrated` Hook
165
+
166
+ **Source:** TanStack Router execution model
167
+
168
+ For components that need different render output pre/post hydration:
169
+
170
+ ```tsx
171
+ import { useHydrated } from '@tanstack/react-router';
172
+
173
+ function CartButton() {
174
+ const hydrated = useHydrated();
175
+
176
+ if (!hydrated) {
177
+ // SSR: render loading skeleton
178
+ return <CartSkeleton />;
179
+ }
180
+
181
+ // Client: render interactive cart
182
+ return <InteractiveCart />;
183
+ }
184
+ ```
185
+
186
+ ### 2.4 Selective SSR (`ssr: 'data-only'`)
187
+
188
+ **Source:** https://tanstack.com/start/latest/docs/framework/react/guide/selective-ssr
189
+
190
+ For routes where the loader should run on server but the component shouldn't render (shows `pendingComponent` as skeleton):
191
+
192
+ ```tsx
193
+ export const Route = createFileRoute('/product/$slug')({
194
+ ssr: 'data-only', // loader runs on server, component renders on client
195
+ pendingComponent: () => <PDPSkeleton />,
196
+ loader: async () => {
197
+ return await loadProductData(); // runs server-side
198
+ },
199
+ component: ProductPage, // renders client-side only
200
+ });
201
+ ```
202
+
203
+ **Use cases:**
204
+ - PDP with complex client-side interactions (image zoom, variant selector)
205
+ - Pages with lots of `useEffect` dependencies
206
+
207
+ ### 2.5 `createIsomorphicFn` for Environment-Specific Logic
208
+
209
+ ```tsx
210
+ import { createIsomorphicFn } from '@tanstack/react-start';
211
+
212
+ const getDeviceInfo = createIsomorphicFn()
213
+ .server(() => ({ source: 'cf-headers', device: getDeviceFromHeaders() }))
214
+ .client(() => ({ source: 'window', device: getDeviceFromWindow() }));
215
+ ```
216
+
217
+ ### 2.6 Loaders Are Isomorphic (Critical Understanding)
218
+
219
+ **Route loaders run on BOTH server (SSR) and client (SPA navigation).** They are NOT server-only.
220
+
221
+ ```tsx
222
+ // ❌ Wrong assumption: loader is server-only
223
+ loader: () => {
224
+ const secret = process.env.API_KEY; // EXPOSED to client bundle
225
+ return fetch(`/api?key=${secret}`);
226
+ }
227
+
228
+ // ✅ Correct: use server function for server-only operations
229
+ const fetchSecurely = createServerFn().handler(() => {
230
+ const secret = process.env.API_KEY; // server-only
231
+ return fetch(`/api?key=${secret}`);
232
+ });
233
+
234
+ loader: () => fetchSecurely() // isomorphic call
235
+ ```
236
+
237
+ **Implication for `@decocms/start`:** The `loadCmsPage` server function is correct — it's called from the loader and executes server-side. But CMS section loaders that access server-only resources (KV, D1) must also be wrapped in server functions.
238
+
239
+ ---
240
+
241
+ ## 3. Migration Plan
242
+
243
+ ### Phase 1: Fix Hydration Mismatches (site-level, no framework changes)
244
+
245
+ | Task | Pattern | Files |
246
+ |------|---------|-------|
247
+ | Wrap analytics in `<ClientOnly>` | 2.2 | `GlobalAnalytics.tsx`, `Sourei.tsx` |
248
+ | Fix invalid HTML (`<span>` in `<option>`) | Standard React | `Sort.tsx` |
249
+ | Fix `selected` on `<option>` → `defaultValue` | Standard React | `Sort.tsx` |
250
+ | Move third-party scripts out of `<head>` | 2.2 | `__root.tsx` |
251
+
252
+ ### Phase 2: Replace `useScript(fn)` with safe alternatives (framework)
253
+
254
+ | Task | Pattern | Files |
255
+ |------|---------|-------|
256
+ | Deprecate `useScript(fn)` | 2.2 | `sdk/useScript.ts` |
257
+ | Add `inlineScript(str)` helper | New utility | `sdk/useScript.ts` |
258
+ | Add dev warning when `fn.toString()` differs | DX improvement | `sdk/useScript.ts` |
259
+ | Document string constant pattern | Docs | This file |
260
+
261
+ **New helper:**
262
+ ```tsx
263
+ // sdk/useScript.ts
264
+
265
+ /** @deprecated Use plain string constants with dangerouslySetInnerHTML instead.
266
+ * fn.toString() produces different output in SSR vs client Vite builds,
267
+ * causing hydration mismatches. */
268
+ export function useScript(fn: Function, ...args: unknown[]): string { ... }
269
+
270
+ /** Safe inline script — returns props for <script> element. */
271
+ export function inlineScript(js: string) {
272
+ return { dangerouslySetInnerHTML: { __html: js } } as const;
273
+ }
274
+ ```
275
+
276
+ ### Phase 3: Adopt `defer()` + `<Await>` for deferred sections (framework)
277
+
278
+ This is the biggest change. Replace the custom `loadDeferredSection` POST server function with TanStack Router's native deferred data loading.
279
+
280
+ #### 3.1 Change `cmsRoute.ts` loader to return deferred promises
281
+
282
+ ```tsx
283
+ // BEFORE (current)
284
+ loader: async () => {
285
+ const page = await loadCmsPage({ data: { path, searchParams } });
286
+ return {
287
+ resolvedSections: page.resolvedSections, // eager sections
288
+ deferredSections: page.deferredSections, // metadata only
289
+ // client must call loadDeferredSection() POST to resolve each one
290
+ };
291
+ }
292
+
293
+ // AFTER (native deferred)
294
+ loader: async () => {
295
+ const page = await loadCmsPage({ data: { path, searchParams } });
296
+
297
+ // Start resolving deferred sections NOW but don't await
298
+ const deferredPromise = resolveDeferredSectionsInParallel(
299
+ page.deferredSections, page.pagePath, page.pageUrl
300
+ );
301
+
302
+ return {
303
+ resolvedSections: page.resolvedSections, // eager — awaited
304
+ deferredSections: deferredPromise, // deferred — streaming!
305
+ };
306
+ }
307
+ ```
308
+
309
+ #### 3.2 Change `DecoPageRenderer` to use `<Await>`
310
+
311
+ ```tsx
312
+ // BEFORE (current)
313
+ function DecoPageRenderer({ resolvedSections, deferredSections, loadDeferredSectionFn }) {
314
+ const merged = mergeSections(resolvedSections, deferredSections);
315
+ return merged.map(section =>
316
+ section.type === 'deferred'
317
+ ? <DeferredSectionWrapper ... loadFn={loadDeferredSectionFn} />
318
+ : <EagerSectionWrapper ... />
319
+ );
320
+ }
321
+
322
+ // AFTER (native deferred)
323
+ function DecoPageRenderer({ resolvedSections, deferredSectionsPromise }) {
324
+ return (
325
+ <>
326
+ {resolvedSections.map(s => <SectionRenderer key={s.index} section={s} />)}
327
+ <Await
328
+ promise={deferredSectionsPromise}
329
+ fallback={<DeferredSkeletons sections={deferredSections} />}
330
+ >
331
+ {(resolved) => resolved.map(s => <SectionRenderer key={s.index} section={s} />)}
332
+ </Await>
333
+ </>
334
+ );
335
+ }
336
+ ```
337
+
338
+ #### 3.3 Keep IntersectionObserver as optimization (optional)
339
+
340
+ For below-the-fold deferred sections, we can still use IntersectionObserver to delay client-side rendering until scroll. But the data is already loaded (streamed) — we just defer the React render.
341
+
342
+ ```tsx
343
+ function LazyRenderSection({ section }) {
344
+ const [visible, setVisible] = useState(false);
345
+ const ref = useRef(null);
346
+
347
+ useEffect(() => {
348
+ const io = new IntersectionObserver(
349
+ ([entry]) => { if (entry.isIntersecting) setVisible(true); },
350
+ { rootMargin: '300px' }
351
+ );
352
+ if (ref.current) io.observe(ref.current);
353
+ return () => io.disconnect();
354
+ }, []);
355
+
356
+ if (!visible) return <div ref={ref}><SectionSkeleton section={section} /></div>;
357
+ return <SectionRenderer section={section} />;
358
+ }
359
+ ```
360
+
361
+ ### Phase 4: Add `<ClientOnly>` support for section registration (framework)
362
+
363
+ Allow sections to declare they're client-only:
364
+
365
+ ```tsx
366
+ // setup.ts
367
+ registerSectionsSync({
368
+ "site/sections/Sourei/Sourei.tsx": SoureiModule,
369
+ }, { clientOnly: true }); // wraps in <ClientOnly> automatically
370
+ ```
371
+
372
+ Or per-section:
373
+ ```tsx
374
+ registerSection("site/sections/Sourei/Sourei.tsx", SoureiModule, {
375
+ clientOnly: true,
376
+ loadingFallback: () => null,
377
+ });
378
+ ```
379
+
380
+ ### Phase 5: Add dev warnings for common mistakes (framework)
381
+
382
+ ```tsx
383
+ // DecoPageRenderer.tsx — warn if eager section is not sync-registered
384
+ if (import.meta.env.DEV && !getSyncComponent(section.component)) {
385
+ console.warn(
386
+ `[DecoPageRenderer] Eager section "${section.component}" is not in registerSectionsSync(). ` +
387
+ `This will cause blank content during hydration. Add it to registerSectionsSync() in setup.ts.`
388
+ );
389
+ }
390
+
391
+ // useScript.ts — warn about fn.toString() risk
392
+ if (import.meta.env.DEV) {
393
+ const ssrStr = fn.toString();
394
+ console.warn(
395
+ `[useScript] Using fn.toString() for "${fn.name || 'anonymous'}". ` +
396
+ `This may produce different output in SSR vs client builds. ` +
397
+ `Consider using a plain string constant instead.`
398
+ );
399
+ }
400
+ ```
401
+
402
+ ---
403
+
404
+ ## 4. Issue-by-Issue Fix Guide
405
+
406
+ ### P1 Fix: Server function I/O error → Use `defer()` (Phase 3)
407
+
408
+ **Before:** Client makes POST to `loadDeferredSection` → separate request → I/O error
409
+ **After:** Deferred sections resolve in the SAME request via `defer()` → streamed to client
410
+
411
+ No separate server function request = no cross-request I/O issue.
412
+
413
+ ### P2 Fix: Blank eager sections → Dev warning + `syncThenable` fallback (Phase 5)
414
+
415
+ **Quick fix:** Warning in dev mode when an eager section isn't sync-registered.
416
+
417
+ **Proper fix:** In `DecoPageRenderer`, for eager sections without sync registration, create a `syncThenable` from the server-resolved component module instead of using bare `React.lazy`:
418
+
419
+ ```tsx
420
+ // If the component was resolved on the server, pre-populate the lazy cache
421
+ // with a syncThenable so hydration doesn't trigger Suspense
422
+ const resolvedModule = getResolvedComponent(section.component);
423
+ if (resolvedModule) {
424
+ const syncLazy = React.lazy(() => syncThenable({ default: resolvedModule }));
425
+ // This won't trigger Suspense during hydration
426
+ }
427
+ ```
428
+
429
+ ### P3 Fix: `useScript(fn)` mismatch → Deprecate + `inlineScript()` helper (Phase 2)
430
+
431
+ **Site-level workaround (now):** Convert to plain string constants.
432
+ **Framework fix (Phase 2):** Deprecate `useScript(fn)`, add `inlineScript(str)` helper.
433
+
434
+ ### P4 Fix: Third-party scripts in head → `<ClientOnly>` (Phase 1)
435
+
436
+ **Site-level:** Wrap analytics/GTM in `<ClientOnly fallback={null}>`.
437
+ **Framework:** Add `clientOnly` option to section registration (Phase 4).
438
+
439
+ ### P5 Fix: N+1 VTEX calls → Concurrency limiter + keep shelves deferred (Phase 3)
440
+
441
+ With `defer()`, deferred sections resolve server-side but stream progressively. Add a concurrency limiter:
442
+
443
+ ```tsx
444
+ // sdk/concurrency.ts
445
+ export function createConcurrencyLimiter(max: number) {
446
+ let inflight = 0;
447
+ const queue: Array<() => void> = [];
448
+
449
+ return async function limit<T>(fn: () => Promise<T>): Promise<T> {
450
+ if (inflight >= max) {
451
+ await new Promise<void>(resolve => queue.push(resolve));
452
+ }
453
+ inflight++;
454
+ try {
455
+ return await fn();
456
+ } finally {
457
+ inflight--;
458
+ queue.shift()?.();
459
+ }
460
+ };
461
+ }
462
+
463
+ // Usage in VTEX fetch
464
+ const vtexLimit = createConcurrencyLimiter(6);
465
+ const response = await vtexLimit(() => fetch(vtexUrl));
466
+ ```
467
+
468
+ ---
469
+
470
+ ## 5. Testing Checklist
471
+
472
+ ### Hydration
473
+
474
+ - [ ] No `dangerouslySetInnerHTML.__html` mismatch warnings in console
475
+ - [ ] No "hydration failed" React warnings
476
+ - [ ] Server HTML matches client render (inspect source vs DOM)
477
+ - [ ] `suppressHydrationWarning` only on `<html>` and `<body>` (not as a blanket fix)
478
+
479
+ ### Deferred Sections
480
+
481
+ - [ ] Skeletons show immediately on page load
482
+ - [ ] Deferred content appears progressively (not all at once)
483
+ - [ ] Back/forward navigation shows cached content instantly
484
+ - [ ] SPA navigation to PLP shows skeleton → products
485
+ - [ ] SearchResult preserves URL params (filters, sort, pagination) after hydration
486
+
487
+ ### Performance
488
+
489
+ - [ ] SSR time < 3s for homepage (with shelves deferred)
490
+ - [ ] SSR time < 2s for PLP (SearchResult deferred or eager with fast VTEX)
491
+ - [ ] No VTEX 503 errors during SSR
492
+ - [ ] CLS < 0.1 (skeletons match final content dimensions)
493
+ - [ ] FCP < 1.5s (eager sections render immediately)
494
+
495
+ ### Dev Mode
496
+
497
+ - [ ] Deferred sections load in dev mode (no I/O error)
498
+ - [ ] HMR works for section components
499
+ - [ ] Console shows helpful warnings for misconfigured sections
500
+
501
+ ### Edge Cases
502
+
503
+ - [ ] Bot/crawler gets full HTML (no deferred skeletons)
504
+ - [ ] JavaScript disabled: eager sections visible, deferred shows skeleton
505
+ - [ ] Slow network: skeleton persists, no blank flash
506
+ - [ ] Multiple deferred sections on same page all resolve
507
+
508
+ ---
509
+
510
+ ## 6. TanStack CLI — Querying Official Docs
511
+
512
+ The `@tanstack/cli` package provides direct access to official TanStack documentation from the terminal. Use it to research implementation details, find examples, and verify patterns before coding.
513
+
514
+ ### Installation
515
+
516
+ ```bash
517
+ npm install -g @tanstack/cli
518
+ # or use without installing:
519
+ npx -y @tanstack/cli <command>
520
+ ```
521
+
522
+ ### Key Commands
523
+
524
+ #### Search docs by topic
525
+
526
+ ```bash
527
+ # Search across a specific library
528
+ tanstack search-docs "server functions" --library start --json
529
+ tanstack search-docs "hydration SSR" --library start --json
530
+ tanstack search-docs "deferred data streaming" --library router --json
531
+ tanstack search-docs "ClientOnly" --library router --json
532
+ tanstack search-docs "createServerFn middleware" --library start --json
533
+
534
+ # Useful searches for this migration:
535
+ tanstack search-docs "Await defer" --library router --json
536
+ tanstack search-docs "useHydrated" --library router --json
537
+ tanstack search-docs "Selective SSR data-only" --library start --json
538
+ tanstack search-docs "code execution patterns" --library start --json
539
+ tanstack search-docs "external data loading" --library router --json
540
+ ```
541
+
542
+ #### Read a specific doc page
543
+
544
+ ```bash
545
+ # Format: tanstack doc <library> <path> --json
546
+ tanstack doc start framework/react/guide/hydration-errors --json
547
+ tanstack doc start framework/react/guide/selective-ssr --json
548
+ tanstack doc start framework/react/guide/execution-model --json
549
+ tanstack doc start framework/react/guide/server-functions --json
550
+ tanstack doc start framework/react/guide/middleware --json
551
+ tanstack doc router guide/deferred-data-loading --json
552
+ tanstack doc router guide/ssr --json
553
+ tanstack doc router guide/external-data-loading --json
554
+ tanstack doc router guide/data-loading --json
555
+ ```
556
+
557
+ #### List available libraries
558
+
559
+ ```bash
560
+ tanstack libraries --json
561
+ ```
562
+
563
+ #### Explore ecosystem tools
564
+
565
+ ```bash
566
+ tanstack ecosystem --category database --json
567
+ tanstack ecosystem --category auth --json
568
+ ```
569
+
570
+ ### Recommended Research Workflow
571
+
572
+ When implementing a phase of this migration:
573
+
574
+ ```bash
575
+ # 1. Search for the topic
576
+ tanstack search-docs "defer Await streaming" --library router --json
577
+
578
+ # 2. Read the most relevant result
579
+ tanstack doc router guide/deferred-data-loading --json
580
+
581
+ # 3. Check for related patterns in Start
582
+ tanstack search-docs "streaming SSR" --library start --json
583
+
584
+ # 4. Look for API reference
585
+ tanstack doc router api/router/awaitComponent --json
586
+
587
+ # 5. Check for breaking changes or version-specific notes
588
+ tanstack search-docs "migration breaking changes" --library start --json
589
+ ```
590
+
591
+ ### Key Doc Pages for This Migration
592
+
593
+ | Phase | Doc Page | Command |
594
+ |-------|----------|---------|
595
+ | Phase 1 | Hydration Errors | `tanstack doc start framework/react/guide/hydration-errors --json` |
596
+ | Phase 2 | Code Execution Patterns | `tanstack doc start framework/react/guide/code-execution-patterns --json` |
597
+ | Phase 3 | Deferred Data Loading | `tanstack doc router guide/deferred-data-loading --json` |
598
+ | Phase 3 | SSR Streaming | `tanstack doc router guide/ssr --json` |
599
+ | Phase 3 | External Data Loading | `tanstack doc router guide/external-data-loading --json` |
600
+ | Phase 4 | ClientOnly Component | `tanstack doc router api/router/clientOnlyComponent --json` |
601
+ | Phase 4 | Execution Model | `tanstack doc start framework/react/guide/execution-model --json` |
602
+ | All | Server Functions | `tanstack doc start framework/react/guide/server-functions --json` |
603
+ | All | Middleware | `tanstack doc start framework/react/guide/middleware --json` |
604
+
605
+ ---
606
+
607
+ ## References
608
+
609
+ - [TanStack Router — Deferred Data Loading](https://tanstack.com/router/latest/docs/guide/deferred-data-loading)
610
+ - [TanStack Start — Hydration Errors](https://tanstack.com/start/latest/docs/framework/react/guide/hydration-errors)
611
+ - [TanStack Start — Selective SSR](https://tanstack.com/start/latest/docs/framework/react/guide/selective-ssr)
612
+ - [TanStack Start — Execution Model](https://tanstack.com/start/latest/docs/framework/react/guide/execution-model)
613
+ - [TanStack Router — ClientOnly Component](https://tanstack.com/router/latest/docs/api/router/clientOnlyComponent)
614
+ - [TanStack Router — SSR Guide](https://tanstack.com/router/latest/docs/guide/ssr)
615
+ - [Cloudflare Workers — Cross-Request I/O](https://developers.cloudflare.com/workers/runtime-apis/context/)
616
+ - [TanStack CLI (`@tanstack/cli`)](https://www.npmjs.com/package/@tanstack/cli) — Query official docs from the terminal