@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.
- package/.cursor/skills/deco-api-call-dedup/SKILL.md +443 -0
- package/.cursor/skills/deco-apps-architecture/SKILL.md +255 -0
- package/.cursor/skills/deco-apps-architecture/app-pattern.md +288 -0
- package/.cursor/skills/deco-apps-architecture/commerce-types.md +239 -0
- package/.cursor/skills/deco-apps-architecture/new-app-guide.md +268 -0
- package/.cursor/skills/deco-apps-architecture/scripts-codegen.md +148 -0
- package/.cursor/skills/deco-apps-architecture/shared-utils.md +181 -0
- package/.cursor/skills/deco-apps-architecture/vtex-deep-structure.md +253 -0
- package/.cursor/skills/deco-apps-architecture/website-app.md +169 -0
- package/.cursor/skills/deco-apps-vtex-porting/SKILL.md +189 -0
- package/.cursor/skills/deco-apps-vtex-porting/adaptation-patterns.md +335 -0
- package/.cursor/skills/deco-apps-vtex-porting/commerce-porting.md +155 -0
- package/.cursor/skills/deco-apps-vtex-porting/cookie-auth-patterns.md +148 -0
- package/.cursor/skills/deco-apps-vtex-porting/structure-map.md +234 -0
- package/.cursor/skills/deco-apps-vtex-porting/transform-mapping.md +99 -0
- package/.cursor/skills/deco-apps-vtex-porting/website-porting.md +194 -0
- package/.cursor/skills/deco-apps-vtex-review/SKILL.md +234 -0
- package/.cursor/skills/deco-async-rendering-architecture/SKILL.md +270 -0
- package/.cursor/skills/deco-async-rendering-site-guide/SKILL.md +417 -0
- package/.cursor/skills/deco-cms-layout-caching/SKILL.md +293 -0
- package/.cursor/skills/deco-cms-route-config/SKILL.md +388 -0
- package/.cursor/skills/deco-core-architecture/SKILL.md +185 -0
- package/.cursor/skills/deco-core-architecture/blocks.md +196 -0
- package/.cursor/skills/deco-core-architecture/deco-vs-deco-start.md +191 -0
- package/.cursor/skills/deco-core-architecture/engine.md +220 -0
- package/.cursor/skills/deco-core-architecture/hooks-components.md +157 -0
- package/.cursor/skills/deco-core-architecture/plugins-clients.md +136 -0
- package/.cursor/skills/deco-core-architecture/runtime.md +116 -0
- package/.cursor/skills/deco-core-architecture/site-usage.md +165 -0
- package/.cursor/skills/deco-e2e-testing/SKILL.md +372 -0
- package/.cursor/skills/deco-e2e-testing/discovery.md +337 -0
- package/.cursor/skills/deco-e2e-testing/scripts/scaffold.sh +81 -0
- package/.cursor/skills/deco-e2e-testing/selectors.md +175 -0
- package/.cursor/skills/deco-e2e-testing/templates/package.json +18 -0
- package/.cursor/skills/deco-e2e-testing/templates/playwright.config.ts +65 -0
- package/.cursor/skills/deco-e2e-testing/templates/scripts/baseline.ts +279 -0
- package/.cursor/skills/deco-e2e-testing/templates/scripts/run-e2e.ts +194 -0
- package/.cursor/skills/deco-e2e-testing/templates/specs/ecommerce-flow.spec.ts +612 -0
- package/.cursor/skills/deco-e2e-testing/templates/tsconfig.json +12 -0
- package/.cursor/skills/deco-e2e-testing/templates/utils/metrics-collector.ts +918 -0
- package/.cursor/skills/deco-e2e-testing/troubleshooting.md +602 -0
- package/.cursor/skills/deco-edge-caching/SKILL.md +316 -0
- package/.cursor/skills/deco-full-analysis/SKILL.md +898 -0
- package/.cursor/skills/deco-full-analysis/checklists/asset-optimization.md +251 -0
- package/.cursor/skills/deco-full-analysis/checklists/bug-fix.md +189 -0
- package/.cursor/skills/deco-full-analysis/checklists/cache-strategy.md +144 -0
- package/.cursor/skills/deco-full-analysis/checklists/dependency-update.md +150 -0
- package/.cursor/skills/deco-full-analysis/checklists/hydration-fix.md +191 -0
- package/.cursor/skills/deco-full-analysis/checklists/image-optimization.md +180 -0
- package/.cursor/skills/deco-full-analysis/checklists/loader-optimization.md +165 -0
- package/.cursor/skills/deco-full-analysis/checklists/seo-fix.md +183 -0
- package/.cursor/skills/deco-full-analysis/checklists/site-cleanup.md +281 -0
- package/.cursor/skills/deco-full-analysis/discovery.md +548 -0
- package/.cursor/skills/deco-incident-debugging/SKILL.md +378 -0
- package/.cursor/skills/deco-incident-debugging/headless-mode.md +510 -0
- package/.cursor/skills/deco-incident-debugging/learnings-index.md +227 -0
- package/.cursor/skills/deco-incident-debugging/triage-workflow.md +312 -0
- package/.cursor/skills/deco-islands-migration/SKILL.md +251 -0
- package/.cursor/skills/deco-loader-n-plus-1-detector/SKILL.md +275 -0
- package/.cursor/skills/deco-performance-audit/SKILL.md +530 -0
- package/.cursor/skills/deco-performance-audit/tools-reference.md +428 -0
- package/.cursor/skills/deco-performance-audit/workflow.md +457 -0
- package/.cursor/skills/deco-server-functions-invoke/SKILL.md +92 -0
- package/.cursor/skills/deco-server-functions-invoke/architecture.md +166 -0
- package/.cursor/skills/deco-server-functions-invoke/generator.md +122 -0
- package/.cursor/skills/deco-server-functions-invoke/problem.md +98 -0
- package/.cursor/skills/deco-server-functions-invoke/troubleshooting.md +110 -0
- package/.cursor/skills/deco-site-deployment/SKILL.md +396 -0
- package/.cursor/skills/deco-site-memory-debugging/SKILL.md +121 -0
- package/.cursor/skills/deco-site-memory-debugging/cdp-connection.md +222 -0
- package/.cursor/skills/deco-site-memory-debugging/memory-analysis.md +362 -0
- package/.cursor/skills/deco-site-patterns/SKILL.md +124 -0
- package/.cursor/skills/deco-site-patterns/app-composition.md +337 -0
- package/.cursor/skills/deco-site-patterns/client-patterns.md +341 -0
- package/.cursor/skills/deco-site-patterns/cms-wiring.md +230 -0
- package/.cursor/skills/deco-site-patterns/section-patterns.md +340 -0
- package/.cursor/skills/deco-site-scaling-tuning/SKILL.md +240 -0
- package/.cursor/skills/deco-site-scaling-tuning/analysis-scripts.md +267 -0
- package/.cursor/skills/deco-start-architecture/SKILL.md +218 -0
- package/.cursor/skills/deco-start-architecture/admin-protocol.md +156 -0
- package/.cursor/skills/deco-start-architecture/cms-resolution.md +201 -0
- package/.cursor/skills/deco-start-architecture/code-quality.md +158 -0
- package/.cursor/skills/deco-start-architecture/gap-analysis.md +129 -0
- package/.cursor/skills/deco-start-architecture/sdk-utilities.md +197 -0
- package/.cursor/skills/deco-start-architecture/worker-entry-caching.md +154 -0
- package/.cursor/skills/deco-startup-analysis/SKILL.md +248 -0
- package/.cursor/skills/deco-storefront-test-checklist/SKILL.md +369 -0
- package/.cursor/skills/deco-tanstack-hydration-fixes/SKILL.md +468 -0
- package/.cursor/skills/deco-tanstack-navigation/SKILL.md +681 -0
- package/.cursor/skills/deco-tanstack-search/SKILL.md +411 -0
- package/.cursor/skills/deco-tanstack-storefront-patterns/SKILL.md +1013 -0
- package/.cursor/skills/deco-to-tanstack-migration/SKILL.md +518 -0
- package/.cursor/skills/deco-to-tanstack-migration/references/codemod-commands.md +174 -0
- package/.cursor/skills/deco-to-tanstack-migration/references/commerce/README.md +78 -0
- package/.cursor/skills/deco-to-tanstack-migration/references/deco-framework/README.md +128 -0
- package/.cursor/skills/deco-to-tanstack-migration/references/gotchas.md +719 -0
- package/.cursor/skills/deco-to-tanstack-migration/references/imports/README.md +70 -0
- package/.cursor/skills/deco-to-tanstack-migration/references/platform-hooks/README.md +154 -0
- package/.cursor/skills/deco-to-tanstack-migration/references/signals/README.md +220 -0
- package/.cursor/skills/deco-to-tanstack-migration/references/vite-config/README.md +78 -0
- package/.cursor/skills/deco-to-tanstack-migration/templates/package-json.md +55 -0
- package/.cursor/skills/deco-to-tanstack-migration/templates/root-route.md +110 -0
- package/.cursor/skills/deco-to-tanstack-migration/templates/router.md +96 -0
- package/.cursor/skills/deco-to-tanstack-migration/templates/setup-ts.md +167 -0
- package/.cursor/skills/deco-to-tanstack-migration/templates/vite-config.md +122 -0
- package/.cursor/skills/deco-to-tanstack-migration/templates/worker-entry.md +67 -0
- package/.cursor/skills/deco-typescript-fixes/SKILL.md +178 -0
- package/.cursor/skills/deco-typescript-fixes/common-fixes.md +330 -0
- package/.cursor/skills/deco-typescript-fixes/strategy.md +148 -0
- package/.cursor/skills/deco-variant-selection-perf/SKILL.md +272 -0
- package/.cursor/skills/deco-vtex-fetch-cache/SKILL.md +225 -0
- package/.cursor/skills/find-skills/SKILL.md +133 -0
- package/.cursor/skills/incident-report/SKILL.md +179 -0
- package/.cursor/skills/incident-report/references/5-whys.md +75 -0
- package/.cursor/skills/incident-report/templates/client-report.md +187 -0
- package/.cursor/skills/incident-report/templates/internal-report.md +206 -0
- package/.cursor/skills/template-skill/SKILL.md +38 -0
- package/.github/workflows/release.yml +32 -0
- package/.releaserc.json +25 -0
- package/CLAUDE.md +135 -0
- package/GAP_ANALYSIS.md +224 -0
- package/GAP_ANALYSIS_V2.md +1013 -0
- package/biome.json +39 -0
- package/knip.json +5 -0
- package/package.json +87 -0
- package/scripts/generate-blocks.ts +69 -0
- package/scripts/generate-invoke.ts +378 -0
- package/scripts/generate-schema.ts +657 -0
- package/src/admin/cors.ts +29 -0
- package/src/admin/decofile.ts +72 -0
- package/src/admin/index.ts +24 -0
- package/src/admin/invoke.ts +163 -0
- package/src/admin/liveControls.ts +29 -0
- package/src/admin/meta.ts +70 -0
- package/src/admin/render.ts +205 -0
- package/src/admin/schema.ts +686 -0
- package/src/admin/setup.ts +44 -0
- package/src/cms/index.ts +59 -0
- package/src/cms/loader.ts +180 -0
- package/src/cms/registry.ts +162 -0
- package/src/cms/resolve.ts +1005 -0
- package/src/cms/sectionLoaders.ts +294 -0
- package/src/hooks/DecoPageRenderer.tsx +444 -0
- package/src/hooks/LazySection.tsx +109 -0
- package/src/hooks/LiveControls.tsx +108 -0
- package/src/hooks/SectionErrorFallback.tsx +85 -0
- package/src/hooks/index.ts +8 -0
- package/src/index.ts +5 -0
- package/src/matchers/builtins.ts +184 -0
- package/src/matchers/posthog.ts +154 -0
- package/src/middleware/decoState.ts +55 -0
- package/src/middleware/healthMetrics.ts +131 -0
- package/src/middleware/index.ts +80 -0
- package/src/middleware/liveness.ts +21 -0
- package/src/middleware/observability.ts +205 -0
- package/src/routes/adminRoutes.ts +83 -0
- package/src/routes/cmsRoute.ts +302 -0
- package/src/routes/components.tsx +34 -0
- package/src/routes/index.ts +15 -0
- package/src/sdk/analytics.ts +72 -0
- package/src/sdk/cacheHeaders.ts +268 -0
- package/src/sdk/cachedLoader.ts +206 -0
- package/src/sdk/clx.ts +3 -0
- package/src/sdk/cookie.ts +39 -0
- package/src/sdk/createInvoke.ts +57 -0
- package/src/sdk/csp.ts +59 -0
- package/src/sdk/env.ts +27 -0
- package/src/sdk/index.ts +63 -0
- package/src/sdk/instrumentedFetch.ts +137 -0
- package/src/sdk/invoke.ts +133 -0
- package/src/sdk/mergeCacheControl.ts +150 -0
- package/src/sdk/redirects.ts +217 -0
- package/src/sdk/requestContext.ts +184 -0
- package/src/sdk/serverTimings.ts +68 -0
- package/src/sdk/signal.ts +41 -0
- package/src/sdk/sitemap.ts +143 -0
- package/src/sdk/urlUtils.ts +117 -0
- package/src/sdk/useDevice.ts +82 -0
- package/src/sdk/useId.ts +7 -0
- package/src/sdk/useScript.ts +101 -0
- package/src/sdk/workerEntry.ts +703 -0
- package/src/sdk/wrapCaughtErrors.ts +107 -0
- package/src/types/index.ts +39 -0
- package/src/types/widgets.ts +13 -0
- 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
|