@decocms/start 0.19.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.
Files changed (185) hide show
  1. package/.cursor/skills/deco-api-call-dedup/SKILL.md +443 -0
  2. package/.cursor/skills/deco-apps-architecture/SKILL.md +255 -0
  3. package/.cursor/skills/deco-apps-architecture/app-pattern.md +288 -0
  4. package/.cursor/skills/deco-apps-architecture/commerce-types.md +239 -0
  5. package/.cursor/skills/deco-apps-architecture/new-app-guide.md +268 -0
  6. package/.cursor/skills/deco-apps-architecture/scripts-codegen.md +148 -0
  7. package/.cursor/skills/deco-apps-architecture/shared-utils.md +181 -0
  8. package/.cursor/skills/deco-apps-architecture/vtex-deep-structure.md +253 -0
  9. package/.cursor/skills/deco-apps-architecture/website-app.md +169 -0
  10. package/.cursor/skills/deco-apps-vtex-porting/SKILL.md +189 -0
  11. package/.cursor/skills/deco-apps-vtex-porting/adaptation-patterns.md +335 -0
  12. package/.cursor/skills/deco-apps-vtex-porting/commerce-porting.md +155 -0
  13. package/.cursor/skills/deco-apps-vtex-porting/cookie-auth-patterns.md +148 -0
  14. package/.cursor/skills/deco-apps-vtex-porting/structure-map.md +234 -0
  15. package/.cursor/skills/deco-apps-vtex-porting/transform-mapping.md +99 -0
  16. package/.cursor/skills/deco-apps-vtex-porting/website-porting.md +194 -0
  17. package/.cursor/skills/deco-apps-vtex-review/SKILL.md +234 -0
  18. package/.cursor/skills/deco-async-rendering-architecture/SKILL.md +270 -0
  19. package/.cursor/skills/deco-async-rendering-site-guide/SKILL.md +417 -0
  20. package/.cursor/skills/deco-cms-layout-caching/SKILL.md +293 -0
  21. package/.cursor/skills/deco-cms-route-config/SKILL.md +388 -0
  22. package/.cursor/skills/deco-core-architecture/SKILL.md +185 -0
  23. package/.cursor/skills/deco-core-architecture/blocks.md +196 -0
  24. package/.cursor/skills/deco-core-architecture/deco-vs-deco-start.md +191 -0
  25. package/.cursor/skills/deco-core-architecture/engine.md +220 -0
  26. package/.cursor/skills/deco-core-architecture/hooks-components.md +157 -0
  27. package/.cursor/skills/deco-core-architecture/plugins-clients.md +136 -0
  28. package/.cursor/skills/deco-core-architecture/runtime.md +116 -0
  29. package/.cursor/skills/deco-core-architecture/site-usage.md +165 -0
  30. package/.cursor/skills/deco-e2e-testing/SKILL.md +372 -0
  31. package/.cursor/skills/deco-e2e-testing/discovery.md +337 -0
  32. package/.cursor/skills/deco-e2e-testing/scripts/scaffold.sh +81 -0
  33. package/.cursor/skills/deco-e2e-testing/selectors.md +175 -0
  34. package/.cursor/skills/deco-e2e-testing/templates/package.json +18 -0
  35. package/.cursor/skills/deco-e2e-testing/templates/playwright.config.ts +65 -0
  36. package/.cursor/skills/deco-e2e-testing/templates/scripts/baseline.ts +279 -0
  37. package/.cursor/skills/deco-e2e-testing/templates/scripts/run-e2e.ts +194 -0
  38. package/.cursor/skills/deco-e2e-testing/templates/specs/ecommerce-flow.spec.ts +612 -0
  39. package/.cursor/skills/deco-e2e-testing/templates/tsconfig.json +12 -0
  40. package/.cursor/skills/deco-e2e-testing/templates/utils/metrics-collector.ts +918 -0
  41. package/.cursor/skills/deco-e2e-testing/troubleshooting.md +602 -0
  42. package/.cursor/skills/deco-edge-caching/SKILL.md +316 -0
  43. package/.cursor/skills/deco-full-analysis/SKILL.md +898 -0
  44. package/.cursor/skills/deco-full-analysis/checklists/asset-optimization.md +251 -0
  45. package/.cursor/skills/deco-full-analysis/checklists/bug-fix.md +189 -0
  46. package/.cursor/skills/deco-full-analysis/checklists/cache-strategy.md +144 -0
  47. package/.cursor/skills/deco-full-analysis/checklists/dependency-update.md +150 -0
  48. package/.cursor/skills/deco-full-analysis/checklists/hydration-fix.md +191 -0
  49. package/.cursor/skills/deco-full-analysis/checklists/image-optimization.md +180 -0
  50. package/.cursor/skills/deco-full-analysis/checklists/loader-optimization.md +165 -0
  51. package/.cursor/skills/deco-full-analysis/checklists/seo-fix.md +183 -0
  52. package/.cursor/skills/deco-full-analysis/checklists/site-cleanup.md +281 -0
  53. package/.cursor/skills/deco-full-analysis/discovery.md +548 -0
  54. package/.cursor/skills/deco-incident-debugging/SKILL.md +378 -0
  55. package/.cursor/skills/deco-incident-debugging/headless-mode.md +510 -0
  56. package/.cursor/skills/deco-incident-debugging/learnings-index.md +227 -0
  57. package/.cursor/skills/deco-incident-debugging/triage-workflow.md +312 -0
  58. package/.cursor/skills/deco-islands-migration/SKILL.md +251 -0
  59. package/.cursor/skills/deco-loader-n-plus-1-detector/SKILL.md +275 -0
  60. package/.cursor/skills/deco-performance-audit/SKILL.md +530 -0
  61. package/.cursor/skills/deco-performance-audit/tools-reference.md +428 -0
  62. package/.cursor/skills/deco-performance-audit/workflow.md +457 -0
  63. package/.cursor/skills/deco-server-functions-invoke/SKILL.md +92 -0
  64. package/.cursor/skills/deco-server-functions-invoke/architecture.md +166 -0
  65. package/.cursor/skills/deco-server-functions-invoke/generator.md +122 -0
  66. package/.cursor/skills/deco-server-functions-invoke/problem.md +98 -0
  67. package/.cursor/skills/deco-server-functions-invoke/troubleshooting.md +110 -0
  68. package/.cursor/skills/deco-site-deployment/SKILL.md +396 -0
  69. package/.cursor/skills/deco-site-memory-debugging/SKILL.md +121 -0
  70. package/.cursor/skills/deco-site-memory-debugging/cdp-connection.md +222 -0
  71. package/.cursor/skills/deco-site-memory-debugging/memory-analysis.md +362 -0
  72. package/.cursor/skills/deco-site-patterns/SKILL.md +124 -0
  73. package/.cursor/skills/deco-site-patterns/app-composition.md +337 -0
  74. package/.cursor/skills/deco-site-patterns/client-patterns.md +341 -0
  75. package/.cursor/skills/deco-site-patterns/cms-wiring.md +230 -0
  76. package/.cursor/skills/deco-site-patterns/section-patterns.md +340 -0
  77. package/.cursor/skills/deco-site-scaling-tuning/SKILL.md +240 -0
  78. package/.cursor/skills/deco-site-scaling-tuning/analysis-scripts.md +267 -0
  79. package/.cursor/skills/deco-start-architecture/SKILL.md +218 -0
  80. package/.cursor/skills/deco-start-architecture/admin-protocol.md +156 -0
  81. package/.cursor/skills/deco-start-architecture/cms-resolution.md +201 -0
  82. package/.cursor/skills/deco-start-architecture/code-quality.md +158 -0
  83. package/.cursor/skills/deco-start-architecture/gap-analysis.md +129 -0
  84. package/.cursor/skills/deco-start-architecture/sdk-utilities.md +197 -0
  85. package/.cursor/skills/deco-start-architecture/worker-entry-caching.md +154 -0
  86. package/.cursor/skills/deco-startup-analysis/SKILL.md +248 -0
  87. package/.cursor/skills/deco-storefront-test-checklist/SKILL.md +369 -0
  88. package/.cursor/skills/deco-tanstack-hydration-fixes/SKILL.md +468 -0
  89. package/.cursor/skills/deco-tanstack-navigation/SKILL.md +681 -0
  90. package/.cursor/skills/deco-tanstack-search/SKILL.md +411 -0
  91. package/.cursor/skills/deco-tanstack-storefront-patterns/SKILL.md +1013 -0
  92. package/.cursor/skills/deco-to-tanstack-migration/SKILL.md +518 -0
  93. package/.cursor/skills/deco-to-tanstack-migration/references/codemod-commands.md +174 -0
  94. package/.cursor/skills/deco-to-tanstack-migration/references/commerce/README.md +78 -0
  95. package/.cursor/skills/deco-to-tanstack-migration/references/deco-framework/README.md +128 -0
  96. package/.cursor/skills/deco-to-tanstack-migration/references/gotchas.md +719 -0
  97. package/.cursor/skills/deco-to-tanstack-migration/references/imports/README.md +70 -0
  98. package/.cursor/skills/deco-to-tanstack-migration/references/platform-hooks/README.md +154 -0
  99. package/.cursor/skills/deco-to-tanstack-migration/references/signals/README.md +220 -0
  100. package/.cursor/skills/deco-to-tanstack-migration/references/vite-config/README.md +78 -0
  101. package/.cursor/skills/deco-to-tanstack-migration/templates/package-json.md +55 -0
  102. package/.cursor/skills/deco-to-tanstack-migration/templates/root-route.md +110 -0
  103. package/.cursor/skills/deco-to-tanstack-migration/templates/router.md +96 -0
  104. package/.cursor/skills/deco-to-tanstack-migration/templates/setup-ts.md +167 -0
  105. package/.cursor/skills/deco-to-tanstack-migration/templates/vite-config.md +122 -0
  106. package/.cursor/skills/deco-to-tanstack-migration/templates/worker-entry.md +67 -0
  107. package/.cursor/skills/deco-typescript-fixes/SKILL.md +178 -0
  108. package/.cursor/skills/deco-typescript-fixes/common-fixes.md +330 -0
  109. package/.cursor/skills/deco-typescript-fixes/strategy.md +148 -0
  110. package/.cursor/skills/deco-variant-selection-perf/SKILL.md +272 -0
  111. package/.cursor/skills/deco-vtex-fetch-cache/SKILL.md +225 -0
  112. package/.cursor/skills/find-skills/SKILL.md +133 -0
  113. package/.cursor/skills/incident-report/SKILL.md +179 -0
  114. package/.cursor/skills/incident-report/references/5-whys.md +75 -0
  115. package/.cursor/skills/incident-report/templates/client-report.md +187 -0
  116. package/.cursor/skills/incident-report/templates/internal-report.md +206 -0
  117. package/.cursor/skills/template-skill/SKILL.md +38 -0
  118. package/.github/workflows/release.yml +32 -0
  119. package/.releaserc.json +25 -0
  120. package/CLAUDE.md +135 -0
  121. package/GAP_ANALYSIS.md +224 -0
  122. package/GAP_ANALYSIS_V2.md +1013 -0
  123. package/biome.json +39 -0
  124. package/knip.json +5 -0
  125. package/package.json +87 -0
  126. package/scripts/generate-blocks.ts +69 -0
  127. package/scripts/generate-invoke.ts +378 -0
  128. package/scripts/generate-schema.ts +657 -0
  129. package/src/admin/cors.ts +29 -0
  130. package/src/admin/decofile.ts +72 -0
  131. package/src/admin/index.ts +24 -0
  132. package/src/admin/invoke.ts +163 -0
  133. package/src/admin/liveControls.ts +29 -0
  134. package/src/admin/meta.ts +70 -0
  135. package/src/admin/render.ts +205 -0
  136. package/src/admin/schema.ts +686 -0
  137. package/src/admin/setup.ts +44 -0
  138. package/src/cms/index.ts +59 -0
  139. package/src/cms/loader.ts +180 -0
  140. package/src/cms/registry.ts +162 -0
  141. package/src/cms/resolve.ts +1005 -0
  142. package/src/cms/sectionLoaders.ts +294 -0
  143. package/src/hooks/DecoPageRenderer.tsx +444 -0
  144. package/src/hooks/LazySection.tsx +109 -0
  145. package/src/hooks/LiveControls.tsx +108 -0
  146. package/src/hooks/SectionErrorFallback.tsx +85 -0
  147. package/src/hooks/index.ts +8 -0
  148. package/src/index.ts +5 -0
  149. package/src/matchers/builtins.ts +184 -0
  150. package/src/matchers/posthog.ts +154 -0
  151. package/src/middleware/decoState.ts +55 -0
  152. package/src/middleware/healthMetrics.ts +131 -0
  153. package/src/middleware/index.ts +80 -0
  154. package/src/middleware/liveness.ts +21 -0
  155. package/src/middleware/observability.ts +205 -0
  156. package/src/routes/adminRoutes.ts +83 -0
  157. package/src/routes/cmsRoute.ts +302 -0
  158. package/src/routes/components.tsx +34 -0
  159. package/src/routes/index.ts +15 -0
  160. package/src/sdk/analytics.ts +72 -0
  161. package/src/sdk/cacheHeaders.ts +268 -0
  162. package/src/sdk/cachedLoader.ts +206 -0
  163. package/src/sdk/clx.ts +3 -0
  164. package/src/sdk/cookie.ts +39 -0
  165. package/src/sdk/createInvoke.ts +57 -0
  166. package/src/sdk/csp.ts +59 -0
  167. package/src/sdk/env.ts +27 -0
  168. package/src/sdk/index.ts +63 -0
  169. package/src/sdk/instrumentedFetch.ts +137 -0
  170. package/src/sdk/invoke.ts +133 -0
  171. package/src/sdk/mergeCacheControl.ts +150 -0
  172. package/src/sdk/redirects.ts +217 -0
  173. package/src/sdk/requestContext.ts +184 -0
  174. package/src/sdk/serverTimings.ts +68 -0
  175. package/src/sdk/signal.ts +41 -0
  176. package/src/sdk/sitemap.ts +143 -0
  177. package/src/sdk/urlUtils.ts +117 -0
  178. package/src/sdk/useDevice.ts +82 -0
  179. package/src/sdk/useId.ts +7 -0
  180. package/src/sdk/useScript.ts +101 -0
  181. package/src/sdk/workerEntry.ts +703 -0
  182. package/src/sdk/wrapCaughtErrors.ts +107 -0
  183. package/src/types/index.ts +39 -0
  184. package/src/types/widgets.ts +13 -0
  185. package/tsconfig.json +13 -0
@@ -0,0 +1,417 @@
1
+ ---
2
+ name: deco-async-rendering-site-guide
3
+ description: Site-level guide for implementing Async Section Rendering in Deco storefronts on TanStack Start. Covers LoadingFallback implementation with detailed product card skeletons, setup.ts configuration using CMS Lazy.tsx wrappers (respectCmsLazy), adding Lazy wrappers to CMS page JSONs, route wiring ($.tsx, index.tsx), alwaysEager sections, NavigationProgress for SPA transitions, and diagnosing dev warnings. Use when adding async rendering to a new Deco site, creating LoadingFallback components, wrapping CMS sections in Lazy, debugging deferred sections, or optimizing page payload.
4
+ ---
5
+
6
+ # Deco Async Section Rendering — Site Implementation Guide
7
+
8
+ How to configure and use Async Section Rendering in your Deco storefront.
9
+
10
+ ## When to Use This Skill
11
+
12
+ - Setting up async section rendering on a new or existing Deco site
13
+ - Creating `LoadingFallback` components for sections
14
+ - Adding `Lazy.tsx` wrappers to CMS page JSONs
15
+ - Debugging the red dashed "Missing LoadingFallback" dev warning
16
+ - Optimizing page payload size
17
+ - Preventing flash-white during SPA navigation
18
+
19
+ ---
20
+
21
+ ## Quick Start (3 steps)
22
+
23
+ ### 1. `src/setup.ts` — Enable async rendering
24
+
25
+ ```ts
26
+ import {
27
+ setAsyncRenderingConfig,
28
+ registerCacheableSections,
29
+ } from "@decocms/start/cms";
30
+
31
+ // Uses CMS Lazy.tsx wrappers as the source of truth for deferral.
32
+ // No foldThreshold needed — editors control what's lazy via CMS admin.
33
+ setAsyncRenderingConfig({
34
+ alwaysEager: [
35
+ "site/sections/Header/Header.tsx",
36
+ "site/sections/Footer/Footer.tsx",
37
+ "site/sections/Theme/Theme.tsx",
38
+ "site/sections/Miscellaneous/CookieConsent.tsx",
39
+ "site/sections/Social/WhatsApp.tsx",
40
+ "site/sections/Social/UserInteractions.tsx",
41
+ ],
42
+ });
43
+
44
+ // Optional: SWR cache for heavy section loaders
45
+ registerCacheableSections({
46
+ "site/sections/Product/ProductShelf.tsx": { maxAge: 180_000 },
47
+ "site/sections/Product/ProductTabbedShelf.tsx": { maxAge: 180_000 },
48
+ "site/sections/Content/Faq.tsx": { maxAge: 1_800_000 },
49
+ });
50
+ ```
51
+
52
+ ### 2. Wrap sections in Lazy in CMS JSONs
53
+
54
+ In `.deco/blocks/pages-*.json`, wrap below-the-fold sections:
55
+
56
+ **Before:**
57
+ ```json
58
+ { "__resolveType": "site/sections/Product/ProductShelf.tsx", "products": {...} }
59
+ ```
60
+
61
+ **After:**
62
+ ```json
63
+ {
64
+ "__resolveType": "website/sections/Rendering/Lazy.tsx",
65
+ "section": {
66
+ "__resolveType": "site/sections/Product/ProductShelf.tsx",
67
+ "products": {...}
68
+ }
69
+ }
70
+ ```
71
+
72
+ **Rules for which sections to wrap:**
73
+ - First 3 visible content sections → **keep eager** (above the fold)
74
+ - Header, Footer, Theme, CookieConsent → **always eager** (in `alwaysEager`)
75
+ - SEO sections → **skip** (they're metadata, not visual)
76
+ - Everything else below the fold → **wrap in Lazy**
77
+
78
+ ### 3. Add LoadingFallback to every lazy section
79
+
80
+ Export `LoadingFallback` from the section file. See detailed patterns below.
81
+
82
+ ---
83
+
84
+ ## CMS Lazy Wrapper Strategy
85
+
86
+ ### Page audit checklist
87
+
88
+ For each CMS page (`pages-*.json`):
89
+
90
+ 1. Count sections. Skip pages with ≤ 3 sections.
91
+ 2. Identify above-the-fold content (typically SEO + Header + first 2 content sections).
92
+ 3. Wrap everything else in `website/sections/Rendering/Lazy.tsx`.
93
+ 4. Keep `alwaysEager` sections (Header, Footer, etc.) unwrapped even if they appear at the end.
94
+
95
+ ### Real-world example: Homepage
96
+
97
+ | Index | Section | Status |
98
+ |-------|---------|--------|
99
+ | 0 | Seo | Skip (metadata) |
100
+ | 1 | UserInteractions | Eager (alwaysEager) |
101
+ | 2 | Header | Eager (alwaysEager) |
102
+ | 3 | Carousel | Eager (above fold) |
103
+ | 4 | Slide | **Lazy** |
104
+ | 5 | Categorias | **Lazy** |
105
+ | 6 | ProductTabbedShelf | **Lazy** |
106
+ | 7 | ProductShelf | **Lazy** |
107
+ | ... | ... | **Lazy** |
108
+ | 21 | Footer | Eager (alwaysEager, even if wrapped in Lazy) |
109
+
110
+ Result: 4 eager + 17 lazy → **52% payload reduction**.
111
+
112
+ ---
113
+
114
+ ## Creating LoadingFallback Components
115
+
116
+ ### Key rules
117
+
118
+ 1. **Match dimensions**: Same container classes, padding, and aspect ratio as the real section
119
+ 2. **CSS-only**: Use `skeleton animate-pulse` classes. No JS, no hooks, no data.
120
+ 3. **No props**: `LoadingFallback()` takes zero arguments
121
+ 4. **One per section file**: Export from `src/sections/Foo.tsx`, not from the component file
122
+ 5. **Represent the content**: Skeletons should visually match the final layout
123
+
124
+ ### Product Card Skeleton (reusable pattern)
125
+
126
+ Most shelf/grid sections contain product cards. Define a shared skeleton:
127
+
128
+ ```tsx
129
+ function CardSkeleton() {
130
+ return (
131
+ <div className="card card-compact w-full lg:p-2.5 bg-white rounded-md">
132
+ <div className="skeleton animate-pulse aspect-square w-full rounded" />
133
+ <div className="flex flex-col gap-2 p-2 pt-3">
134
+ <div className="skeleton animate-pulse h-3 w-16 rounded" />
135
+ <div className="skeleton animate-pulse h-4 w-full rounded" />
136
+ <div className="skeleton animate-pulse h-4 w-3/4 rounded" />
137
+ <div className="flex flex-col gap-1 mt-1">
138
+ <div className="skeleton animate-pulse h-3 w-20 rounded" />
139
+ <div className="skeleton animate-pulse h-5 w-28 rounded" />
140
+ <div className="skeleton animate-pulse h-3 w-24 rounded" />
141
+ </div>
142
+ <div className="skeleton animate-pulse h-9 w-full rounded mt-2" />
143
+ </div>
144
+ </div>
145
+ );
146
+ }
147
+ ```
148
+
149
+ This matches the real `ProductCard` layout: image → flag → name (2 lines) → price block (from/to/installment) → buy button.
150
+
151
+ ### Pattern: Product Shelf
152
+
153
+ ```tsx
154
+ export function LoadingFallback() {
155
+ return (
156
+ <div className="w-full flex flex-col spacingComponents">
157
+ <div className="customContainer mx-auto px-4">
158
+ <div className="skeleton animate-pulse h-6 w-48 rounded mb-6" />
159
+ <div className="flex gap-[1%] overflow-hidden">
160
+ <div className="w-full lg:w-[24%] md:w-[32%] shrink-0"><CardSkeleton /></div>
161
+ <div className="hidden md:block lg:w-[24%] md:w-[32%] shrink-0"><CardSkeleton /></div>
162
+ <div className="hidden md:block lg:w-[24%] md:w-[32%] shrink-0"><CardSkeleton /></div>
163
+ <div className="hidden lg:block lg:w-[24%] shrink-0"><CardSkeleton /></div>
164
+ </div>
165
+ </div>
166
+ </div>
167
+ );
168
+ }
169
+ ```
170
+
171
+ ### Pattern: Tabbed Shelf
172
+
173
+ ```tsx
174
+ export function LoadingFallback() {
175
+ return (
176
+ <div className="w-full flex flex-col spacingComponents overflow-hidden">
177
+ <div className="flex flex-col mx-4 lg:max-w-[84vw] w-full lg:mx-auto">
178
+ <div className="skeleton animate-pulse h-4 w-32 rounded mb-4" />
179
+ <div className="flex gap-4 lg:gap-7 mb-4">
180
+ <div className="skeleton animate-pulse h-9 w-28 rounded-[10px]" />
181
+ <div className="skeleton animate-pulse h-9 w-28 rounded-[10px]" />
182
+ <div className="skeleton animate-pulse h-9 w-28 rounded-[10px] hidden md:block" />
183
+ </div>
184
+ <div className="flex gap-[1%] overflow-hidden mt-4">
185
+ {/* Cards: 2 mobile, 3 tablet, 4 desktop */}
186
+ <div className="w-[44%] lg:w-[24%] md:w-[32%] shrink-0"><CardSkeleton /></div>
187
+ <div className="w-[44%] lg:w-[24%] md:w-[32%] shrink-0"><CardSkeleton /></div>
188
+ <div className="hidden md:block lg:w-[24%] md:w-[32%] shrink-0"><CardSkeleton /></div>
189
+ <div className="hidden lg:block lg:w-[24%] shrink-0"><CardSkeleton /></div>
190
+ </div>
191
+ </div>
192
+ </div>
193
+ );
194
+ }
195
+ ```
196
+
197
+ ### Pattern: Search Result (PLP)
198
+
199
+ ```tsx
200
+ export function LoadingFallback() {
201
+ return (
202
+ <div className="w-full customContainer px-4 py-8 flex gap-6" style={{ minHeight: 600 }}>
203
+ {/* Sidebar filters */}
204
+ <div className="hidden lg:flex flex-col gap-6 w-64 shrink-0">
205
+ <div className="skeleton animate-pulse h-7 w-32 rounded" />
206
+ {Array.from({ length: 4 }).map((_, i) => (
207
+ <div key={i} className="flex flex-col gap-3 pb-4 border-b border-gray-200">
208
+ <div className="skeleton animate-pulse h-5 w-24 rounded" />
209
+ {Array.from({ length: 4 }).map((_, j) => (
210
+ <div key={j} className="flex items-center gap-2">
211
+ <div className="skeleton animate-pulse h-4 w-4 rounded" />
212
+ <div className="skeleton animate-pulse h-3 w-20 rounded" />
213
+ </div>
214
+ ))}
215
+ </div>
216
+ ))}
217
+ </div>
218
+ {/* Product grid */}
219
+ <div className="flex-1">
220
+ <div className="flex items-center justify-between mb-4">
221
+ <div className="skeleton animate-pulse h-7 w-48 rounded" />
222
+ <div className="skeleton animate-pulse h-8 w-32 rounded" />
223
+ </div>
224
+ <div className="grid grid-cols-2 lg:grid-cols-4 gap-4">
225
+ {Array.from({ length: 8 }).map((_, i) => (
226
+ <CardSkeleton key={i} />
227
+ ))}
228
+ </div>
229
+ </div>
230
+ </div>
231
+ );
232
+ }
233
+ ```
234
+
235
+ ### Pattern: Full-width Banner/Carousel
236
+
237
+ ```tsx
238
+ export function LoadingFallback() {
239
+ return (
240
+ <div className="w-full">
241
+ <div className="skeleton animate-pulse w-full h-[300px] lg:h-[420px]" />
242
+ </div>
243
+ );
244
+ }
245
+ ```
246
+
247
+ ### Pattern: FAQ Accordion
248
+
249
+ ```tsx
250
+ export function LoadingFallback() {
251
+ return (
252
+ <div className="w-full customContainer px-4 py-8 flex flex-col gap-4 lg:py-10 lg:px-40"
253
+ style={{ minHeight: 400 }}>
254
+ <div className="skeleton animate-pulse h-6 w-48 mx-auto rounded" />
255
+ {Array.from({ length: 5 }).map((_, i) => (
256
+ <div key={i} className="skeleton animate-pulse h-12 w-full rounded" />
257
+ ))}
258
+ </div>
259
+ );
260
+ }
261
+ ```
262
+
263
+ ### Pattern: Testimonials/Cards Grid
264
+
265
+ ```tsx
266
+ export function LoadingFallback() {
267
+ return (
268
+ <div className="w-full customContainer px-4 py-8 flex flex-col gap-8">
269
+ <div className="skeleton animate-pulse h-6 w-48 rounded mx-auto" />
270
+ <div className="grid grid-cols-1 lg:grid-cols-3 gap-6">
271
+ {Array.from({ length: 3 }).map((_, i) => (
272
+ <div key={i} className="flex flex-col gap-3 p-6 bg-white rounded-lg">
273
+ <div className="skeleton animate-pulse w-16 h-16 rounded-full" />
274
+ <div className="skeleton animate-pulse h-4 w-32 rounded" />
275
+ <div className="skeleton animate-pulse h-4 w-full rounded" />
276
+ <div className="skeleton animate-pulse h-4 w-3/4 rounded" />
277
+ </div>
278
+ ))}
279
+ </div>
280
+ </div>
281
+ );
282
+ }
283
+ ```
284
+
285
+ ### Pattern: Footer
286
+
287
+ ```tsx
288
+ export function LoadingFallback() {
289
+ return (
290
+ <div className="bg-[#f3f3f3] w-full" style={{ minHeight: 600 }}>
291
+ <div className="customContainer px-4 py-10">
292
+ <div className="grid grid-cols-2 lg:grid-cols-4 gap-8 mb-8">
293
+ {Array.from({ length: 4 }).map((_, i) => (
294
+ <div key={i} className="flex flex-col gap-3">
295
+ <div className="skeleton animate-pulse h-5 w-32 rounded" />
296
+ {Array.from({ length: 5 }).map((_, j) => (
297
+ <div key={j} className="skeleton animate-pulse h-3 w-24 rounded" />
298
+ ))}
299
+ </div>
300
+ ))}
301
+ </div>
302
+ <div className="skeleton animate-pulse h-16 w-32 rounded mx-auto" />
303
+ </div>
304
+ </div>
305
+ );
306
+ }
307
+ ```
308
+
309
+ ---
310
+
311
+ ## SPA Navigation: NavigationProgress
312
+
313
+ **Do NOT use `pendingComponent`** on CMS routes — it replaces the entire page content (Header/Footer disappear, causing a "flash white").
314
+
315
+ Instead, add a root-level progress bar in `__root.tsx`:
316
+
317
+ ```tsx
318
+ import { useRouterState } from "@tanstack/react-router";
319
+
320
+ const PROGRESS_CSS = `
321
+ @keyframes progressSlide { from { transform: translateX(-100%); } to { transform: translateX(100%); } }
322
+ .nav-progress-bar { animation: progressSlide 1s ease-in-out infinite; }
323
+ `;
324
+
325
+ function NavigationProgress() {
326
+ const isLoading = useRouterState({ select: (s) => s.isLoading });
327
+ if (!isLoading) return null;
328
+ return (
329
+ <div className="fixed top-0 left-0 right-0 z-[9999] h-1 bg-primary/20 overflow-hidden">
330
+ <style dangerouslySetInnerHTML={{ __html: PROGRESS_CSS }} />
331
+ <div className="nav-progress-bar h-full w-1/3 bg-primary rounded-full" />
332
+ </div>
333
+ );
334
+ }
335
+ ```
336
+
337
+ Add `<NavigationProgress />` before your main layout in `RootLayout`.
338
+
339
+ ---
340
+
341
+ ## Configuration Reference
342
+
343
+ ### `setAsyncRenderingConfig(options)`
344
+
345
+ | Option | Type | Default | Description |
346
+ |--------|------|---------|-------------|
347
+ | `respectCmsLazy` | `boolean` | `true` | Use CMS Lazy.tsx wrappers as deferral source |
348
+ | `foldThreshold` | `number` | `Infinity` | Fallback for non-wrapped sections (Infinity = only Lazy-wrapped defer) |
349
+ | `alwaysEager` | `string[]` | `[]` | Section keys that are ALWAYS eager regardless |
350
+
351
+ ### `registerCacheableSections(configs)`
352
+
353
+ ```ts
354
+ registerCacheableSections({
355
+ "site/sections/Product/ProductShelf.tsx": { maxAge: 180_000 }, // 3 min SWR
356
+ });
357
+ ```
358
+
359
+ Good candidates: Product shelves (2-3 min), FAQ/content (15-30 min). NOT for PDP ProductInfo (must be per-product fresh).
360
+
361
+ ---
362
+
363
+ ## Debugging
364
+
365
+ ### Section not being deferred
366
+
367
+ 1. Is `setAsyncRenderingConfig()` called in `setup.ts`?
368
+ 2. Is the section wrapped in `website/sections/Rendering/Lazy.tsx` in the CMS JSON?
369
+ 3. Is the section key in `alwaysEager`?
370
+ 4. Is it a layout section (`registerLayoutSections`)?
371
+ 5. Is it wrapped in a multivariate flag? (always eager)
372
+ 6. Is the user-agent a bot? (bots always get full eager)
373
+
374
+ ### Verifying with curl
375
+
376
+ ```bash
377
+ # Normal user — count deferred sections
378
+ curl -s http://localhost:5173/ | grep -c 'data-deferred'
379
+
380
+ # Bot — should have 0 deferred
381
+ curl -s -A "Googlebot/2.1" http://localhost:5173/ | grep -c 'data-deferred'
382
+
383
+ # Compare payload size
384
+ curl -s -o /dev/null -w "Normal: %{size_download}\n" http://localhost:5173/
385
+ curl -s -o /dev/null -w "Bot: %{size_download}\n" -A "Googlebot/2.1" http://localhost:5173/
386
+ ```
387
+
388
+ ### InvalidCharacterError with sections
389
+
390
+ If you see `Failed to execute 'createElement'` with a section path as tag name, the component is using `{ Component, props }` destructuring directly as JSX. Use `SectionRenderer` or `SectionList` from `@decocms/start/hooks` instead.
391
+
392
+ ---
393
+
394
+ ## Performance Impact
395
+
396
+ Measured on `espacosmart-storefront`:
397
+
398
+ | Page | Before | After | Reduction |
399
+ |------|--------|-------|-----------|
400
+ | Homepage (22 sections) | 8.7 MB | 4.2 MB | **52%** |
401
+ | PDP (8 sections) | 8.3 MB | 3.6 MB | **56%** |
402
+ | PLP (6 sections) | 646 KB | ~400 KB | **38%** |
403
+
404
+ ---
405
+
406
+ ## Checklist for New Sites
407
+
408
+ - [ ] Call `setAsyncRenderingConfig()` in `setup.ts` with `alwaysEager` sections
409
+ - [ ] Audit all CMS page JSONs — wrap below-fold sections in `Lazy.tsx`
410
+ - [ ] Add `LoadingFallback` export to every section used in Lazy wrappers
411
+ - [ ] Use detailed skeletons (product card structure, not just gray boxes)
412
+ - [ ] Add `NavigationProgress` to `__root.tsx` (NOT `pendingComponent` on routes)
413
+ - [ ] Pass `deferredSections` and `loadDeferredSectionFn` in `$.tsx` and `index.tsx`
414
+ - [ ] Optionally call `registerCacheableSections()` for heavy section loaders
415
+ - [ ] Verify with `curl` that bots get full eager pages
416
+ - [ ] Measure payload reduction with `curl -o /dev/null -w "%{size_download}"`
417
+ - [ ] Run dev mode and fix all red "Missing LoadingFallback" warnings