Summary
buildSegment returns a SegmentKey, but hashSegment() only serializes a fixed set of known fields — device, loggedIn, salesChannel, regionId, flags. Any custom field a consumer adds to the returned object is silently dropped from the cache key, with no type error and no runtime warning. The result is a cache-segmentation that appears to work but doesn't: requests that should land in different cache buckets share one entry and the worker serves the wrong cached response.
How it bites
createDecoWorkerEntry({ buildSegment }) accepts a buildSegment: (request) => SegmentKey. A site that needs an extra cache dimension naturally writes:
buildSegment: (request) => ({
device,
...(regionId ? { regionId } : {}),
...(delivery ? { delivery } : {}), // custom dimension — store/CEP selection
}),
This compiles (excess-property checks are skipped on object spread) and looks correct, but hashSegment ignores delivery:
function hashSegment(seg: SegmentKey): string {
const parts: string[] = [seg.device];
if (seg.loggedIn) parts.push("auth");
if (seg.salesChannel) parts.push(`sc=${seg.salesChannel}`);
if (seg.regionId) parts.push(`r=${seg.regionId}`);
if (seg.flags?.length) parts.push(`f=${seg.flags.sort().join(",")}`);
return parts.join("|");
}
So two requests that differ only by the custom dimension produce the same __seg hash → same cache key → the first-cached response is served to both. We hit this on a storefront where store/CEP selection (delivery-promise) must vary the PLP cache: the x-cache-segment header read desktop|r=RJ even with the store cookies set, and the store PLP kept serving the no-store cached page (1192 products instead of 924). Confirmed by invoking the loader directly (correct, store-filtered result) vs. the cached SSR path (wrong).
Workaround
Route the custom token through the one extensible field hashSegment honors — flags:
...(delivery ? { flags: [delivery] } : {}),
x-cache-segment then correctly shows desktop|r=RJ|f=pp:<storeId> and the cache varies.
Suggested fix (any one)
- Serialize all enumerable keys in
hashSegment (stable-sorted) so custom dimensions just work; or
- Make
SegmentKey exact / reject excess properties so the compiler catches an unsupported field at the buildSegment call site; or
- Document clearly that
flags is the only consumer-extensible segmentation field, and that custom keys are ignored.
Option 1 is the least surprising for consumers. Option 2 turns the silent failure into a compile error.
Environment
@decocms/start (resolved from src/sdk/workerEntry.ts — hashSegment, SegmentKey)
- Cloudflare Workers, TanStack Start storefront
Summary
buildSegmentreturns aSegmentKey, buthashSegment()only serializes a fixed set of known fields —device,loggedIn,salesChannel,regionId,flags. Any custom field a consumer adds to the returned object is silently dropped from the cache key, with no type error and no runtime warning. The result is a cache-segmentation that appears to work but doesn't: requests that should land in different cache buckets share one entry and the worker serves the wrong cached response.How it bites
createDecoWorkerEntry({ buildSegment })accepts abuildSegment: (request) => SegmentKey. A site that needs an extra cache dimension naturally writes:This compiles (excess-property checks are skipped on object spread) and looks correct, but
hashSegmentignoresdelivery:So two requests that differ only by the custom dimension produce the same
__seghash → same cache key → the first-cached response is served to both. We hit this on a storefront where store/CEP selection (delivery-promise) must vary the PLP cache: thex-cache-segmentheader readdesktop|r=RJeven with the store cookies set, and the store PLP kept serving the no-store cached page (1192 products instead of 924). Confirmed by invoking the loader directly (correct, store-filtered result) vs. the cached SSR path (wrong).Workaround
Route the custom token through the one extensible field
hashSegmenthonors —flags:x-cache-segmentthen correctly showsdesktop|r=RJ|f=pp:<storeId>and the cache varies.Suggested fix (any one)
hashSegment(stable-sorted) so custom dimensions just work; orSegmentKeyexact / reject excess properties so the compiler catches an unsupported field at thebuildSegmentcall site; orflagsis the only consumer-extensible segmentation field, and that custom keys are ignored.Option 1 is the least surprising for consumers. Option 2 turns the silent failure into a compile error.
Environment
@decocms/start(resolved fromsrc/sdk/workerEntry.ts—hashSegment,SegmentKey)