@decocms/start 0.19.0 → 0.20.2
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.
|
@@ -41,72 +41,41 @@ The catch-all route handles all CMS-managed pages (PDP, PLP, institutional pages
|
|
|
41
41
|
|
|
42
42
|
```typescript
|
|
43
43
|
// src/routes/$.tsx
|
|
44
|
-
import { createFileRoute } from "@tanstack/react-router";
|
|
45
|
-
import { cmsRouteConfig,
|
|
44
|
+
import { createFileRoute, notFound } from "@tanstack/react-router";
|
|
45
|
+
import { cmsRouteConfig, NotFoundPage } from "@decocms/start/routes";
|
|
46
46
|
import { DecoPageRenderer } from "@decocms/start/hooks";
|
|
47
|
-
import type { ResolvedSection, DeferredSection } from "@decocms/start/cms";
|
|
48
|
-
import type { CacheProfile } from "@decocms/start/sdk/cacheHeaders";
|
|
49
|
-
import { cacheHeaders, routeCacheDefaults } from "@decocms/start/sdk/cacheHeaders";
|
|
50
47
|
|
|
51
|
-
const
|
|
48
|
+
const config = cmsRouteConfig({
|
|
52
49
|
siteName: "My Store",
|
|
53
50
|
defaultTitle: "My Store - Default Title",
|
|
54
51
|
ignoreSearchParams: ["skuId"],
|
|
55
52
|
});
|
|
56
53
|
|
|
57
|
-
type PageData = {
|
|
58
|
-
resolvedSections: ResolvedSection[];
|
|
59
|
-
deferredSections: DeferredSection[];
|
|
60
|
-
cacheProfile: CacheProfile;
|
|
61
|
-
name: string;
|
|
62
|
-
path: string;
|
|
63
|
-
params: Record<string, string>;
|
|
64
|
-
} | null;
|
|
65
|
-
|
|
66
54
|
export const Route = createFileRoute("/$")({
|
|
67
|
-
|
|
68
|
-
|
|
69
|
-
|
|
70
|
-
|
|
71
|
-
|
|
72
|
-
return cacheHeaders(data?.cacheProfile ?? "listing");
|
|
73
|
-
},
|
|
74
|
-
head: ({ loaderData }) => {
|
|
75
|
-
const data = loaderData as PageData;
|
|
76
|
-
return {
|
|
77
|
-
meta: [
|
|
78
|
-
{
|
|
79
|
-
title: data?.name
|
|
80
|
-
? `${data.name} | My Store`
|
|
81
|
-
: "My Store - Default Title",
|
|
82
|
-
},
|
|
83
|
-
],
|
|
84
|
-
};
|
|
55
|
+
loaderDeps: config.loaderDeps,
|
|
56
|
+
loader: async (ctx) => {
|
|
57
|
+
const page = await config.loader(ctx);
|
|
58
|
+
if (!page) throw notFound();
|
|
59
|
+
return page;
|
|
85
60
|
},
|
|
86
61
|
component: CmsPage,
|
|
87
62
|
notFoundComponent: NotFoundPage,
|
|
63
|
+
staleTime: config.staleTime,
|
|
64
|
+
gcTime: config.gcTime,
|
|
65
|
+
headers: config.headers,
|
|
66
|
+
head: config.head,
|
|
88
67
|
});
|
|
89
68
|
|
|
90
69
|
function CmsPage() {
|
|
91
|
-
const
|
|
92
|
-
const { _splat } = Route.useParams();
|
|
93
|
-
const actualPath = `/${_splat ?? ""}`;
|
|
94
|
-
|
|
95
|
-
if (!data) return <NotFoundPage />;
|
|
96
|
-
|
|
70
|
+
const page = Route.useLoaderData();
|
|
97
71
|
return (
|
|
98
|
-
<
|
|
99
|
-
sections={
|
|
100
|
-
|
|
101
|
-
pagePath={actualPath}
|
|
102
|
-
loadDeferredSectionFn={(d) => loadDeferredSection({ data: d }) as Promise<ResolvedSection | null>}
|
|
103
|
-
/>
|
|
72
|
+
<div>
|
|
73
|
+
<DecoPageRenderer sections={page.resolvedSections} />
|
|
74
|
+
</div>
|
|
104
75
|
);
|
|
105
76
|
}
|
|
106
77
|
```
|
|
107
78
|
|
|
108
|
-
**CRITICAL**: The `...routeCacheDefaults("listing")` spread is essential. Without it, every SPA navigation triggers a full server re-fetch even when the data was just loaded seconds ago. This is the most common cause of perceived slow navigation.
|
|
109
|
-
|
|
110
79
|
### `cmsRouteConfig` Options
|
|
111
80
|
|
|
112
81
|
```typescript
|
|
@@ -183,41 +152,27 @@ Hardcoded to `/` path — no params, no deps.
|
|
|
183
152
|
```typescript
|
|
184
153
|
// src/routes/index.tsx
|
|
185
154
|
import { createFileRoute } from "@tanstack/react-router";
|
|
186
|
-
import { cmsHomeRouteConfig
|
|
155
|
+
import { cmsHomeRouteConfig } from "@decocms/start/routes";
|
|
187
156
|
import { DecoPageRenderer } from "@decocms/start/hooks";
|
|
188
|
-
import type { ResolvedSection, DeferredSection } from "@decocms/start/cms";
|
|
189
157
|
|
|
190
|
-
const
|
|
158
|
+
const config = cmsHomeRouteConfig({
|
|
191
159
|
defaultTitle: "My Store - Homepage",
|
|
192
160
|
});
|
|
193
161
|
|
|
194
|
-
type HomeData = {
|
|
195
|
-
resolvedSections: ResolvedSection[];
|
|
196
|
-
deferredSections: DeferredSection[];
|
|
197
|
-
} | null;
|
|
198
|
-
|
|
199
162
|
export const Route = createFileRoute("/")({
|
|
200
|
-
...
|
|
163
|
+
...config,
|
|
201
164
|
component: HomePage,
|
|
202
165
|
});
|
|
203
166
|
|
|
204
167
|
function HomePage() {
|
|
205
|
-
const
|
|
206
|
-
if (!
|
|
207
|
-
|
|
208
|
-
|
|
209
|
-
|
|
210
|
-
sections={data.resolvedSections ?? []}
|
|
211
|
-
deferredSections={data.deferredSections ?? []}
|
|
212
|
-
pagePath="/"
|
|
213
|
-
loadDeferredSectionFn={(d) => loadDeferredSection({ data: d }) as Promise<ResolvedSection | null>}
|
|
214
|
-
/>
|
|
215
|
-
);
|
|
168
|
+
const page = Route.useLoaderData();
|
|
169
|
+
if (!page) {
|
|
170
|
+
return <div>Loading...</div>;
|
|
171
|
+
}
|
|
172
|
+
return <DecoPageRenderer sections={page.resolvedSections} />;
|
|
216
173
|
}
|
|
217
174
|
```
|
|
218
175
|
|
|
219
|
-
`cmsHomeRouteConfig` already includes `routeCacheDefaults("static")` and `cacheHeaders("static")`, giving the homepage a 5-min client staleTime and 24h edge TTL. Do NOT add additional cache config.
|
|
220
|
-
|
|
221
176
|
### `cmsHomeRouteConfig` Options
|
|
222
177
|
|
|
223
178
|
```typescript
|
|
@@ -353,17 +308,15 @@ The root route contains site-specific elements that should NOT be in the framewo
|
|
|
353
308
|
|
|
354
309
|
### Production
|
|
355
310
|
|
|
356
|
-
Set by `routeCacheDefaults(profile)` based on page type
|
|
311
|
+
Set by `routeCacheDefaults(profile)` based on page type:
|
|
357
312
|
|
|
358
313
|
| Profile | staleTime | gcTime |
|
|
359
314
|
|---------|-----------|--------|
|
|
360
315
|
| static | 5 min | 30 min |
|
|
361
|
-
| product |
|
|
362
|
-
| listing |
|
|
363
|
-
| search |
|
|
364
|
-
| cart | 0 | 0 |
|
|
316
|
+
| product | 5 min | 30 min |
|
|
317
|
+
| listing | 2 min | 10 min |
|
|
318
|
+
| search | 60s | 5 min |
|
|
365
319
|
| private | 0 | 0 |
|
|
366
|
-
| none | 0 | 0 |
|
|
367
320
|
|
|
368
321
|
### Development
|
|
369
322
|
|
|
@@ -181,116 +181,6 @@ const cachedPLP = createCachedLoader("vtex/plp", vtexPLP, {
|
|
|
181
181
|
|
|
182
182
|
This is per-isolate in-memory cache (V8 Map). Resets on cold start. Includes request deduplication (single-flight) and LRU eviction at 500 entries.
|
|
183
183
|
|
|
184
|
-
## Cache Versioning with BUILD_HASH
|
|
185
|
-
|
|
186
|
-
Deploy-time cache busting uses a `BUILD_HASH` environment variable (typically the git short SHA) passed to `wrangler deploy`. The worker-entry appends this to cache keys so deploying a new version automatically serves fresh content.
|
|
187
|
-
|
|
188
|
-
```yaml
|
|
189
|
-
# .github/workflows/deploy.yml
|
|
190
|
-
- name: Deploy to Cloudflare Workers
|
|
191
|
-
run: npx wrangler deploy
|
|
192
|
-
env:
|
|
193
|
-
BUILD_HASH: ${{ github.sha }}
|
|
194
|
-
```
|
|
195
|
-
|
|
196
|
-
The worker-entry reads `env.BUILD_HASH` and injects it into cache keys. On new deploys, old cache entries simply expire naturally — no purge needed.
|
|
197
|
-
|
|
198
|
-
## Site-Level Cache Pattern Registration
|
|
199
|
-
|
|
200
|
-
For sites with known institutional/static pages that would otherwise get the conservative 2-min "listing" TTL, register explicit patterns in `setup.ts`:
|
|
201
|
-
|
|
202
|
-
```ts
|
|
203
|
-
// setup.ts
|
|
204
|
-
import { registerCachePattern } from "@decocms/start/sdk/cacheHeaders";
|
|
205
|
-
|
|
206
|
-
// Institutional pages — content changes rarely, promote to 24h edge TTL
|
|
207
|
-
registerCachePattern({
|
|
208
|
-
test: (p) =>
|
|
209
|
-
p.startsWith("/institucional") ||
|
|
210
|
-
p.startsWith("/central-de-atendimento") ||
|
|
211
|
-
p.startsWith("/politica-de-") ||
|
|
212
|
-
p.startsWith("/termos-") ||
|
|
213
|
-
p === "/fale-conosco" ||
|
|
214
|
-
p === "/trabalhe-conosco" ||
|
|
215
|
-
p === "/cadastro" ||
|
|
216
|
-
p === "/televendas",
|
|
217
|
-
profile: "static",
|
|
218
|
-
});
|
|
219
|
-
|
|
220
|
-
// Promotional/collection pages — already listing-like, but explicit is better
|
|
221
|
-
registerCachePattern({
|
|
222
|
-
test: (p) =>
|
|
223
|
-
p.startsWith("/ofertas") ||
|
|
224
|
-
p.startsWith("/b/") ||
|
|
225
|
-
p.startsWith("/festival-"),
|
|
226
|
-
profile: "listing",
|
|
227
|
-
});
|
|
228
|
-
```
|
|
229
|
-
|
|
230
|
-
Custom patterns are evaluated before built-in ones. This is the recommended way to tune caching per-site without modifying the framework.
|
|
231
|
-
|
|
232
|
-
## Client-Side Route Caching (routeCacheDefaults)
|
|
233
|
-
|
|
234
|
-
Without `routeCacheDefaults`, every SPA navigation triggers a fresh server request even if the data was just loaded. This is the most common cause of "slow navigation" reports.
|
|
235
|
-
|
|
236
|
-
The catch-all route `$.tsx` MUST include `routeCacheDefaults`:
|
|
237
|
-
|
|
238
|
-
```ts
|
|
239
|
-
export const Route = createFileRoute("/$")({
|
|
240
|
-
...routeCacheDefaults("listing"), // <-- client-side cache: 1min stale, 5min gc
|
|
241
|
-
loaderDeps: routeConfig.loaderDeps,
|
|
242
|
-
loader: routeConfig.loader,
|
|
243
|
-
headers: ({ loaderData }) => {
|
|
244
|
-
return cacheHeaders(loaderData?.cacheProfile ?? "listing");
|
|
245
|
-
},
|
|
246
|
-
component: CmsPage,
|
|
247
|
-
});
|
|
248
|
-
```
|
|
249
|
-
|
|
250
|
-
The homepage should use `cmsHomeRouteConfig` which already includes `routeCacheDefaults("static")`:
|
|
251
|
-
|
|
252
|
-
```ts
|
|
253
|
-
export const Route = createFileRoute("/")({
|
|
254
|
-
...cmsHomeRouteConfig({ defaultTitle: "My Store" }),
|
|
255
|
-
component: HomePage,
|
|
256
|
-
});
|
|
257
|
-
```
|
|
258
|
-
|
|
259
|
-
## Cache Analysis & Debugging with Stats Lake
|
|
260
|
-
|
|
261
|
-
Deco sites emit CDN usage data to a ClickHouse stats-lake. This enables cache performance analysis:
|
|
262
|
-
|
|
263
|
-
```sql
|
|
264
|
-
-- Cache status breakdown for a site
|
|
265
|
-
SELECT
|
|
266
|
-
JSONExtractString(extra, 'cacheStatus') AS cache_status,
|
|
267
|
-
count() AS requests,
|
|
268
|
-
round(count() * 100.0 / sum(count()) OVER (), 2) AS pct
|
|
269
|
-
FROM fact_usage_daily
|
|
270
|
-
WHERE site_id = <site_id>
|
|
271
|
-
AND date >= today() - 7
|
|
272
|
-
GROUP BY cache_status
|
|
273
|
-
ORDER BY requests DESC;
|
|
274
|
-
```
|
|
275
|
-
|
|
276
|
-
### Understanding "unknown" Cache Status
|
|
277
|
-
|
|
278
|
-
When the Cloudflare Worker uses `caches.default.match()/put()` to serve cached responses internally, the outer CDN reports `cf-cache-status: DYNAMIC` because the Worker is the origin. The stats-lake logs this as "unknown" or empty.
|
|
279
|
-
|
|
280
|
-
This means a high "unknown" percentage does NOT indicate a caching problem — it means the Worker's internal Cache API is handling the request before it reaches the origin CDN layer. This is expected and desirable behavior.
|
|
281
|
-
|
|
282
|
-
To verify actual cache performance:
|
|
283
|
-
1. Check `X-Cache: HIT|MISS` header (set by the worker-entry)
|
|
284
|
-
2. Check `X-Cache-Profile` header (shows which profile was detected)
|
|
285
|
-
3. Query stats-lake grouping by `cacheStatus` AND response status codes
|
|
286
|
-
|
|
287
|
-
### Comparing Staging vs Production Cache
|
|
288
|
-
|
|
289
|
-
When migrating to TanStack Workers, compare cache metrics:
|
|
290
|
-
- Production (Deno/Fresh on Kubernetes) typically shows high HIT rates because traffic volume keeps caches warm
|
|
291
|
-
- Staging Workers may show lower HIT rates due to lower traffic, plus "unknown" status from internal Cache API
|
|
292
|
-
- The "unknown" requests on Workers are functionally equivalent to HITs — they're served from the Worker's Cache API without hitting the origin server function
|
|
293
|
-
|
|
294
184
|
## Key Constraints
|
|
295
185
|
|
|
296
186
|
- **Cache API ignores `s-maxage`** — the factory uses `max-age` equal to `sMaxAge` when storing in Cache API
|
|
@@ -298,8 +188,6 @@ When migrating to TanStack Workers, compare cache metrics:
|
|
|
298
188
|
- **Device keys add a query param** — `__cf_device=mobile|desktop` is appended to cache keys, so purging must clear both
|
|
299
189
|
- **Non-200 responses are never cached** — only 200 OK goes into Cache API
|
|
300
190
|
- **`/_server` paths always bypass cache** — TanStack Start RPC requests are never edge-cached
|
|
301
|
-
- **UTM parameters are stripped** — `utm_*`, `gclid`, `fbclid` are removed from cache keys to improve hit rates
|
|
302
|
-
- **Segment hashing** — user segments (from matchers/flags) are hashed into the cache key so different audiences get different cached responses
|
|
303
191
|
|
|
304
192
|
## Package Exports
|
|
305
193
|
|
|
@@ -6,7 +6,7 @@ on:
|
|
|
6
6
|
|
|
7
7
|
permissions:
|
|
8
8
|
contents: write
|
|
9
|
-
|
|
9
|
+
id-token: write
|
|
10
10
|
issues: write
|
|
11
11
|
pull-requests: write
|
|
12
12
|
|
|
@@ -23,10 +23,11 @@ jobs:
|
|
|
23
23
|
node-version: 22
|
|
24
24
|
registry-url: https://registry.npmjs.org
|
|
25
25
|
|
|
26
|
+
- run: npm install -g npm@latest
|
|
27
|
+
|
|
26
28
|
- run: npm install
|
|
27
29
|
|
|
28
30
|
- name: Release
|
|
29
31
|
run: npx semantic-release
|
|
30
32
|
env:
|
|
31
33
|
GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }}
|
|
32
|
-
NODE_AUTH_TOKEN: ${{ secrets.NPM_TOKEN }}
|
package/.releaserc.json
CHANGED
|
@@ -15,7 +15,10 @@
|
|
|
15
15
|
]
|
|
16
16
|
}],
|
|
17
17
|
"@semantic-release/release-notes-generator",
|
|
18
|
-
"@semantic-release/
|
|
18
|
+
["@semantic-release/exec", {
|
|
19
|
+
"prepareCmd": "npm version ${nextRelease.version} --no-git-tag-version",
|
|
20
|
+
"publishCmd": "npm publish --access public"
|
|
21
|
+
}],
|
|
19
22
|
"@semantic-release/github",
|
|
20
23
|
["@semantic-release/git", {
|
|
21
24
|
"assets": ["package.json"],
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@decocms/start",
|
|
3
|
-
"version": "0.
|
|
3
|
+
"version": "0.20.2",
|
|
4
4
|
"type": "module",
|
|
5
5
|
"description": "Deco framework for TanStack Start - CMS bridge, admin protocol, hooks, schema generation",
|
|
6
6
|
"main": "./src/index.ts",
|
|
@@ -76,6 +76,7 @@
|
|
|
76
76
|
},
|
|
77
77
|
"devDependencies": {
|
|
78
78
|
"@biomejs/biome": "^2.4.6",
|
|
79
|
+
"@semantic-release/exec": "^7.1.0",
|
|
79
80
|
"@semantic-release/git": "^10.0.1",
|
|
80
81
|
"@tanstack/store": "^0.9.1",
|
|
81
82
|
"@types/react": "^19.0.0",
|