@decocms/start 0.23.1 → 0.24.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.
|
@@ -41,41 +41,72 @@ 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
|
|
45
|
-
import { cmsRouteConfig,
|
|
44
|
+
import { createFileRoute } from "@tanstack/react-router";
|
|
45
|
+
import { cmsRouteConfig, loadDeferredSection } 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";
|
|
47
50
|
|
|
48
|
-
const
|
|
51
|
+
const routeConfig = cmsRouteConfig({
|
|
49
52
|
siteName: "My Store",
|
|
50
53
|
defaultTitle: "My Store - Default Title",
|
|
51
54
|
ignoreSearchParams: ["skuId"],
|
|
52
55
|
});
|
|
53
56
|
|
|
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
|
+
|
|
54
66
|
export const Route = createFileRoute("/$")({
|
|
55
|
-
|
|
56
|
-
|
|
57
|
-
|
|
58
|
-
|
|
59
|
-
|
|
67
|
+
...routeCacheDefaults("listing"),
|
|
68
|
+
loaderDeps: routeConfig.loaderDeps,
|
|
69
|
+
loader: routeConfig.loader as any,
|
|
70
|
+
headers: ({ loaderData }) => {
|
|
71
|
+
const data = loaderData as PageData;
|
|
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
|
+
};
|
|
60
85
|
},
|
|
61
86
|
component: CmsPage,
|
|
62
87
|
notFoundComponent: NotFoundPage,
|
|
63
|
-
staleTime: config.staleTime,
|
|
64
|
-
gcTime: config.gcTime,
|
|
65
|
-
headers: config.headers,
|
|
66
|
-
head: config.head,
|
|
67
88
|
});
|
|
68
89
|
|
|
69
90
|
function CmsPage() {
|
|
70
|
-
const
|
|
91
|
+
const data = Route.useLoaderData() as PageData;
|
|
92
|
+
const { _splat } = Route.useParams();
|
|
93
|
+
const actualPath = `/${_splat ?? ""}`;
|
|
94
|
+
|
|
95
|
+
if (!data) return <NotFoundPage />;
|
|
96
|
+
|
|
71
97
|
return (
|
|
72
|
-
<
|
|
73
|
-
|
|
74
|
-
|
|
98
|
+
<DecoPageRenderer
|
|
99
|
+
sections={data.resolvedSections ?? []}
|
|
100
|
+
deferredSections={data.deferredSections ?? []}
|
|
101
|
+
pagePath={actualPath}
|
|
102
|
+
loadDeferredSectionFn={(d) => loadDeferredSection({ data: d }) as Promise<ResolvedSection | null>}
|
|
103
|
+
/>
|
|
75
104
|
);
|
|
76
105
|
}
|
|
77
106
|
```
|
|
78
107
|
|
|
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
|
+
|
|
79
110
|
### `cmsRouteConfig` Options
|
|
80
111
|
|
|
81
112
|
```typescript
|
|
@@ -152,27 +183,41 @@ Hardcoded to `/` path — no params, no deps.
|
|
|
152
183
|
```typescript
|
|
153
184
|
// src/routes/index.tsx
|
|
154
185
|
import { createFileRoute } from "@tanstack/react-router";
|
|
155
|
-
import { cmsHomeRouteConfig } from "@decocms/start/routes";
|
|
186
|
+
import { cmsHomeRouteConfig, loadDeferredSection } from "@decocms/start/routes";
|
|
156
187
|
import { DecoPageRenderer } from "@decocms/start/hooks";
|
|
188
|
+
import type { ResolvedSection, DeferredSection } from "@decocms/start/cms";
|
|
157
189
|
|
|
158
|
-
const
|
|
190
|
+
const homeConfig = cmsHomeRouteConfig({
|
|
159
191
|
defaultTitle: "My Store - Homepage",
|
|
160
192
|
});
|
|
161
193
|
|
|
194
|
+
type HomeData = {
|
|
195
|
+
resolvedSections: ResolvedSection[];
|
|
196
|
+
deferredSections: DeferredSection[];
|
|
197
|
+
} | null;
|
|
198
|
+
|
|
162
199
|
export const Route = createFileRoute("/")({
|
|
163
|
-
...
|
|
200
|
+
...homeConfig,
|
|
164
201
|
component: HomePage,
|
|
165
202
|
});
|
|
166
203
|
|
|
167
204
|
function HomePage() {
|
|
168
|
-
const
|
|
169
|
-
if (!
|
|
170
|
-
|
|
171
|
-
|
|
172
|
-
|
|
205
|
+
const data = Route.useLoaderData() as HomeData;
|
|
206
|
+
if (!data) return null;
|
|
207
|
+
|
|
208
|
+
return (
|
|
209
|
+
<DecoPageRenderer
|
|
210
|
+
sections={data.resolvedSections ?? []}
|
|
211
|
+
deferredSections={data.deferredSections ?? []}
|
|
212
|
+
pagePath="/"
|
|
213
|
+
loadDeferredSectionFn={(d) => loadDeferredSection({ data: d }) as Promise<ResolvedSection | null>}
|
|
214
|
+
/>
|
|
215
|
+
);
|
|
173
216
|
}
|
|
174
217
|
```
|
|
175
218
|
|
|
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
|
+
|
|
176
221
|
### `cmsHomeRouteConfig` Options
|
|
177
222
|
|
|
178
223
|
```typescript
|
|
@@ -308,15 +353,17 @@ The root route contains site-specific elements that should NOT be in the framewo
|
|
|
308
353
|
|
|
309
354
|
### Production
|
|
310
355
|
|
|
311
|
-
Set by `routeCacheDefaults(profile)` based on page type:
|
|
356
|
+
Set by `routeCacheDefaults(profile)` based on page type (from `cacheHeaders.ts`):
|
|
312
357
|
|
|
313
358
|
| Profile | staleTime | gcTime |
|
|
314
359
|
|---------|-----------|--------|
|
|
315
360
|
| static | 5 min | 30 min |
|
|
316
|
-
| product |
|
|
317
|
-
| listing |
|
|
318
|
-
| search |
|
|
361
|
+
| product | 1 min | 5 min |
|
|
362
|
+
| listing | 1 min | 5 min |
|
|
363
|
+
| search | 30s | 2 min |
|
|
364
|
+
| cart | 0 | 0 |
|
|
319
365
|
| private | 0 | 0 |
|
|
366
|
+
| none | 0 | 0 |
|
|
320
367
|
|
|
321
368
|
### Development
|
|
322
369
|
|
|
@@ -181,6 +181,116 @@ 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
|
+
|
|
184
294
|
## Key Constraints
|
|
185
295
|
|
|
186
296
|
- **Cache API ignores `s-maxage`** — the factory uses `max-age` equal to `sMaxAge` when storing in Cache API
|
|
@@ -188,6 +298,8 @@ This is per-isolate in-memory cache (V8 Map). Resets on cold start. Includes req
|
|
|
188
298
|
- **Device keys add a query param** — `__cf_device=mobile|desktop` is appended to cache keys, so purging must clear both
|
|
189
299
|
- **Non-200 responses are never cached** — only 200 OK goes into Cache API
|
|
190
300
|
- **`/_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
|
|
191
303
|
|
|
192
304
|
## Package Exports
|
|
193
305
|
|
package/package.json
CHANGED
package/src/sdk/workerEntry.ts
CHANGED
|
@@ -69,6 +69,13 @@ export interface SegmentKey {
|
|
|
69
69
|
loggedIn?: boolean;
|
|
70
70
|
/** Commerce sales channel / price list. */
|
|
71
71
|
salesChannel?: string;
|
|
72
|
+
/**
|
|
73
|
+
* VTEX region ID for regionalized pricing/availability.
|
|
74
|
+
* When present, cache entries are segmented per region.
|
|
75
|
+
* Sites without regionalization should omit this field
|
|
76
|
+
* to avoid unnecessary cache fragmentation.
|
|
77
|
+
*/
|
|
78
|
+
regionId?: string;
|
|
72
79
|
/** Sorted list of active A/B flag names for cache cohort splitting. */
|
|
73
80
|
flags?: string[];
|
|
74
81
|
}
|
|
@@ -128,6 +135,10 @@ export interface DecoWorkerEntryOptions {
|
|
|
128
135
|
* device: /mobile|android|iphone/i.test(request.headers.get("user-agent") ?? "") ? "mobile" : "desktop",
|
|
129
136
|
* loggedIn: vtx.isLoggedIn,
|
|
130
137
|
* salesChannel: vtx.salesChannel,
|
|
138
|
+
* // Include regionId only if the site uses VTEX regionalization.
|
|
139
|
+
* // When present, cache entries split by region; omit it for
|
|
140
|
+
* // non-regionalized sites to maximize cache sharing.
|
|
141
|
+
* regionId: vtx.regionId ?? undefined,
|
|
131
142
|
* };
|
|
132
143
|
* },
|
|
133
144
|
* });
|
|
@@ -366,6 +377,7 @@ export function createDecoWorkerEntry(
|
|
|
366
377
|
const parts: string[] = [seg.device];
|
|
367
378
|
if (seg.loggedIn) parts.push("auth");
|
|
368
379
|
if (seg.salesChannel) parts.push(`sc=${seg.salesChannel}`);
|
|
380
|
+
if (seg.regionId) parts.push(`r=${seg.regionId}`);
|
|
369
381
|
if (seg.flags?.length) parts.push(`f=${seg.flags.sort().join(",")}`);
|
|
370
382
|
return parts.join("|");
|
|
371
383
|
}
|
|
@@ -415,6 +427,32 @@ export function createDecoWorkerEntry(
|
|
|
415
427
|
|
|
416
428
|
// -- Purge handler ----------------------------------------------------------
|
|
417
429
|
|
|
430
|
+
interface PurgeRequestBody {
|
|
431
|
+
paths?: string[];
|
|
432
|
+
countries?: string[];
|
|
433
|
+
/** Sales channels to include in segment combos. Defaults to ["1"]. */
|
|
434
|
+
salesChannels?: string[];
|
|
435
|
+
/** Region IDs to include in segment combos. Each ID generates additional entries. */
|
|
436
|
+
regionIds?: string[];
|
|
437
|
+
}
|
|
438
|
+
|
|
439
|
+
function buildPurgeSegments(body: PurgeRequestBody): SegmentKey[] {
|
|
440
|
+
const devices: Array<"mobile" | "desktop"> = ["mobile", "desktop"];
|
|
441
|
+
const channels = body.salesChannels ?? ["1"];
|
|
442
|
+
const regions: Array<string | undefined> = [undefined, ...(body.regionIds ?? [])];
|
|
443
|
+
|
|
444
|
+
const segments: SegmentKey[] = [];
|
|
445
|
+
for (const device of devices) {
|
|
446
|
+
for (const salesChannel of channels) {
|
|
447
|
+
for (const regionId of regions) {
|
|
448
|
+
segments.push({ device, salesChannel, regionId });
|
|
449
|
+
}
|
|
450
|
+
}
|
|
451
|
+
segments.push({ device });
|
|
452
|
+
}
|
|
453
|
+
return segments;
|
|
454
|
+
}
|
|
455
|
+
|
|
418
456
|
async function handlePurge(request: Request, env: Record<string, unknown>): Promise<Response> {
|
|
419
457
|
if (purgeTokenEnv === false) {
|
|
420
458
|
return new Response("Purge disabled", { status: 404 });
|
|
@@ -425,7 +463,7 @@ export function createDecoWorkerEntry(
|
|
|
425
463
|
return new Response("Unauthorized", { status: 401 });
|
|
426
464
|
}
|
|
427
465
|
|
|
428
|
-
let body:
|
|
466
|
+
let body: PurgeRequestBody;
|
|
429
467
|
try {
|
|
430
468
|
body = await request.json();
|
|
431
469
|
} catch {
|
|
@@ -437,10 +475,6 @@ export function createDecoWorkerEntry(
|
|
|
437
475
|
return new Response('Body must include "paths": ["/", "/page"]', { status: 400 });
|
|
438
476
|
}
|
|
439
477
|
|
|
440
|
-
// Geo strings to purge location-specific cache variants.
|
|
441
|
-
// Pass ["BR", "BR|São Paulo|Curitiba", ...] to purge specific geo variants.
|
|
442
|
-
// Each string must match the __cf_geo param format: "country|region|city".
|
|
443
|
-
// When omitted, only the non-geo cache entry is purged.
|
|
444
478
|
const geoVariants = body.countries ?? [];
|
|
445
479
|
|
|
446
480
|
const cache =
|
|
@@ -455,25 +489,18 @@ export function createDecoWorkerEntry(
|
|
|
455
489
|
const baseUrl = new URL(request.url).origin;
|
|
456
490
|
const purged: string[] = [];
|
|
457
491
|
|
|
458
|
-
// If using segment-based keys, purge requires known segment combos.
|
|
459
|
-
// For simplicity, purge common combos: both devices, default sales channel.
|
|
460
|
-
const segments: SegmentKey[] = buildSegment
|
|
461
|
-
? [
|
|
462
|
-
{ device: "mobile" },
|
|
463
|
-
{ device: "desktop" },
|
|
464
|
-
{ device: "mobile", salesChannel: "1" },
|
|
465
|
-
{ device: "desktop", salesChannel: "1" },
|
|
466
|
-
]
|
|
467
|
-
: [];
|
|
468
|
-
|
|
469
|
-
// Purge both without geo (non-geo-targeted) and with each specified geo variant
|
|
470
492
|
const geoKeys: (string | null)[] = [null, ...geoVariants];
|
|
471
493
|
|
|
472
494
|
for (const p of paths) {
|
|
473
|
-
if (buildSegment
|
|
495
|
+
if (buildSegment) {
|
|
496
|
+
const segments = buildPurgeSegments(body);
|
|
474
497
|
for (const seg of segments) {
|
|
475
498
|
for (const cc of geoKeys) {
|
|
476
499
|
const url = new URL(p, baseUrl);
|
|
500
|
+
if (cacheVersionEnv !== false) {
|
|
501
|
+
const version = (env[cacheVersionEnv] as string) || "";
|
|
502
|
+
if (version) url.searchParams.set("__v", version);
|
|
503
|
+
}
|
|
477
504
|
url.searchParams.set("__seg", hashSegment(seg));
|
|
478
505
|
if (cc) url.searchParams.set("__cf_geo", cc);
|
|
479
506
|
const key = new Request(url.toString(), { method: "GET" });
|
|
@@ -493,6 +520,10 @@ export function createDecoWorkerEntry(
|
|
|
493
520
|
for (const device of devices) {
|
|
494
521
|
for (const cc of geoKeys) {
|
|
495
522
|
const url = new URL(p, baseUrl);
|
|
523
|
+
if (cacheVersionEnv !== false) {
|
|
524
|
+
const version = (env[cacheVersionEnv] as string) || "";
|
|
525
|
+
if (version) url.searchParams.set("__v", version);
|
|
526
|
+
}
|
|
496
527
|
if (device) url.searchParams.set("__cf_device", device);
|
|
497
528
|
if (cc) url.searchParams.set("__cf_geo", cc);
|
|
498
529
|
const key = new Request(url.toString(), { method: "GET" });
|
|
@@ -643,16 +674,22 @@ export function createDecoWorkerEntry(
|
|
|
643
674
|
const origin = await serverEntry.fetch(request, env, ctx);
|
|
644
675
|
const profile = getProfile(url);
|
|
645
676
|
|
|
646
|
-
// If the profile is private/none/cart, strip any public cache headers
|
|
647
|
-
// the route may have set (prevents the search caching bug)
|
|
648
677
|
if (profile === "private" || profile === "none" || profile === "cart") {
|
|
649
678
|
const resp = new Response(origin.body, origin);
|
|
650
679
|
resp.headers.set("Cache-Control", "private, no-cache, no-store, must-revalidate");
|
|
651
680
|
resp.headers.delete("CDN-Cache-Control");
|
|
681
|
+
resp.headers.set("X-Cache", "BYPASS");
|
|
682
|
+
resp.headers.set("X-Cache-Reason", `non-cacheable:${profile}`);
|
|
652
683
|
return resp;
|
|
653
684
|
}
|
|
654
685
|
|
|
655
|
-
|
|
686
|
+
const resp = new Response(origin.body, origin);
|
|
687
|
+
const reason = request.method !== "GET"
|
|
688
|
+
? `method:${request.method}`
|
|
689
|
+
: "bypass-path";
|
|
690
|
+
resp.headers.set("X-Cache", "BYPASS");
|
|
691
|
+
resp.headers.set("X-Cache-Reason", reason);
|
|
692
|
+
return resp;
|
|
656
693
|
}
|
|
657
694
|
|
|
658
695
|
// Cacheable request — build segment-aware cache key
|
|
@@ -696,7 +733,10 @@ export function createDecoWorkerEntry(
|
|
|
696
733
|
const origin = await serverEntry.fetch(request, env, ctx);
|
|
697
734
|
|
|
698
735
|
if (origin.status !== 200) {
|
|
699
|
-
|
|
736
|
+
const resp = new Response(origin.body, origin);
|
|
737
|
+
resp.headers.set("X-Cache", "BYPASS");
|
|
738
|
+
resp.headers.set("X-Cache-Reason", `status:${origin.status}`);
|
|
739
|
+
return resp;
|
|
700
740
|
}
|
|
701
741
|
|
|
702
742
|
// Responses with Set-Cookie must never be cached — they carry
|
|
@@ -717,10 +757,11 @@ export function createDecoWorkerEntry(
|
|
|
717
757
|
const profile = getProfile(url);
|
|
718
758
|
const profileConfig = getCacheProfileConfig(profile);
|
|
719
759
|
|
|
720
|
-
// Don't cache non-public profiles
|
|
721
760
|
if (!profileConfig.isPublic || profileConfig.sMaxAge === 0) {
|
|
722
761
|
const resp = new Response(origin.body, origin);
|
|
723
762
|
resp.headers.set("Cache-Control", "private, no-cache, no-store, must-revalidate");
|
|
763
|
+
resp.headers.set("X-Cache", "BYPASS");
|
|
764
|
+
resp.headers.set("X-Cache-Reason", `profile:${profile}`);
|
|
724
765
|
return resp;
|
|
725
766
|
}
|
|
726
767
|
|