Skip to content

fix(vtex): gate vtex_segment cookie on isCacheableSegment - #1642

Open
igoramf wants to merge 5 commits into
mainfrom
fix/vtex-segment-cookie-utm-cacheable
Open

fix(vtex): gate vtex_segment cookie on isCacheableSegment#1642
igoramf wants to merge 5 commits into
mainfrom
fix/vtex-segment-cookie-utm-cacheable

Conversation

@igoramf

@igoramf igoramf commented Jul 15, 2026

Copy link
Copy Markdown
Contributor

Problem

Set-Cookie: vtex_segment was emitted whenever isAnonymous() returned false, and isAnonymous treats any request carrying utm_source/utm_campaign/utmi_campaign as non-anonymous. vtex_segment is a non-framework ("foreign") cookie, so deco's applyPageCacheDecision forces no-store whenever it is set. Landing pages arriving with UTM params therefore stopped being cacheable — origin dropped from public, max-age=300 to no-store.

Fix

Two changes, both gated on the existing advancedConfigs.removeUTMFromCacheKey flag — the same flag the product loaders already use to strip UTM from their cache key (#1517). The flag is the store's explicit contract: "UTM does not affect content/price here."

1. Cookie gate (utils/segment.ts)

  • flag on → gate on !isCacheableSegment(ctx) (ignores UTM). UTM-only requests emit no cookie → stay cacheable.
  • flag off (default) → keep !isAnonymous(ctx). UTM-carrying requests stay cookie-bearing → no-store. Identical to main.

2. Price simulation (utils/extensions/simulation.ts)

  • flag on → do not feed UTM into marketingData. Since the page is cached with UTM stripped from the key, a UTM-triggered promotion computed here would otherwise be cached and served generically. Campaigns / priceTables / regionId / channel are still simulated.
  • flag off → unchanged (UTM still drives the simulation, and those pages are not cached anyway).

The segment payload/token are untouched — UTM still reaches the VTEX API where relevant and stays on the client (querystring/JS) for GA/pixel. Channel continuity rides on VTEXSC. isAnonymous, isCacheableSegment and the middleware are unchanged.

Behavior

request flag off (default) flag on
UTM-only, default channel cookie + no-store (= main) no cookie → public, max-age=300, UTM out of cache key & simulation
campaigns / priceTables / regionId cookie + no-store cookie + no-store
logged in / channelPrivacy private cookie + no-store cookie + no-store

With the flag off, behavior is identical to main. Enabling the flag is the store's assertion that UTM is analytics-only; the simulation gate makes that assertion airtight against a later UTM-driven promotion being cached generically.

Summary by CodeRabbit

  • Bug Fixes
    • Improved simulation requests by omitting UTM values when configured to exclude them from cache keys.
    • Adjusted segment cookie handling to better preserve CDN caching for cacheable requests.
    • Ensured non-cacheable requests continue receiving the appropriate segment cookie.

Set-Cookie: vtex_segment was emitted whenever isAnonymous returned false,
which includes UTM-only requests. Cloudflare skips caching any response with
Set-Cookie, so landing pages carrying utm_source/utm_campaign stopped being
CDN-cacheable and the origin dropped from public,max-age=300 to no-store.

The gate now mirrors the middleware's own cacheability predicate
(isCacheableSegment). Any response the middleware treats as cacheable stays
Set-Cookie-free, so UTM-only and non-default sales-channel requests become
CDN-cacheable again. Channel continuity still rides on the VTEXSC cookie,
and the segment payload/token are untouched (UTM still reaches the VTEX API
and the client for GA/pixel).

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
@coderabbitai

coderabbitai Bot commented Jul 15, 2026

Copy link
Copy Markdown
Contributor

Review Change Stack

📝 Walkthrough

Walkthrough

UTM cache-key configuration now controls whether UTM fields enter simulation marketing data and whether segment cookies are emitted for cacheable requests.

Changes

UTM cache behavior

Layer / File(s) Summary
UTM-aware payload and cookie handling
vtex/utils/extensions/simulation.ts, vtex/utils/segment.ts
Simulation omits UTM marketing fields when configured to remove them from cache keys. Segment cookie emission now depends on cacheability under the same configuration.

Estimated code review effort: 2 (Simple) | ~10 minutes

Possibly related PRs

  • deco-cx/apps#1517: Updates segment cache-key derivation using the same UTM configuration.
  • deco-cx/apps#1537: Adjusts UTM handling and segment cacheability with the same flag.
  • deco-cx/apps#1562: Changes segment cookie and cacheability logic around UTM handling.

Suggested reviewers: guitavano

🚥 Pre-merge checks | ✅ 4 | ❌ 1

❌ Failed checks (1 warning)

Check name Status Explanation Resolution
Description check ⚠️ Warning The description is detailed, but it omits the required Issue, Loom Video, and Demonstration sections from the template. Add the template sections: What is this Contribution About, Issue Link, Loom Video, and Demonstration Link, with the missing links/details.
✅ Passed checks (4 passed)
Check name Status Explanation
Title check ✅ Passed The title matches the main cookie-gating change and mentions the key cacheability condition.
Docstring Coverage ✅ Passed No functions found in the changed files to evaluate docstring coverage. Skipping docstring coverage check.
Linked Issues check ✅ Passed Check skipped because no linked issues were found for this pull request.
Out of Scope Changes check ✅ Passed Check skipped because no linked issues were found for this pull request.
✨ Finishing Touches
📝 Generate docstrings
  • Create stacked PR
  • Commit on current branch
🧪 Generate unit tests (beta)
  • Create PR with unit tests
  • Commit unit tests in branch fix/vtex-segment-cookie-utm-cacheable

Thanks for using CodeRabbit! It's free for OSS, and your support helps us grow. If you like it, consider giving us a shout-out.

❤️ Share

Comment @coderabbitai help to get the list of available commands.

@github-actions

Copy link
Copy Markdown
Contributor

Tagging Options

Should a new tag be published when this PR is merged?

  • 👍 for Patch 0.159.5 update
  • 🎉 for Minor 0.160.0 update
  • 🚀 for Major 1.0.0 update

@cubic-dev-ai cubic-dev-ai Bot left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

3 issues found across 1 file

Prompt for AI agents (unresolved issues)

Check if these issues are valid — if so, understand the root cause of each and fix them. If appropriate, use sub-agents to investigate and fix each issue separately.


<file name="vtex/utils/segment.ts">

<violation number="1" location="vtex/utils/segment.ts:267">
P1: UTM-only requests now become publicly cacheable here, but `simulation.ts` still treats any UTM as non-anonymous and sends it as `marketingData`; a VTEX promotion keyed by that UTM can therefore produce a campaign price in a response shared with users without the UTM. Requests whose marketing data can affect pricing need to remain out of the public page cache, or those fields need to be excluded from simulation for cacheable requests.</violation>

<violation number="2" location="vtex/utils/segment.ts:267">
P2: Logged-in UTM requests now skip `vtex_segment` even though middleware marks them `no-store`: this gate checks `isCacheableSegment` but not the same auth-cookie predicate used by middleware. The prior `!isAnonymous` path persisted this changed segment, so the gate should also retain the cookie for logged-in requests.</violation>

<violation number="3" location="vtex/utils/segment.ts:267">
P1: This gate removes only `vtex_segment`; a non-default `?sc=...` request still executes the preceding `setCookie(... name: SALES_CHANNEL_COOKIE)` branch and returns `Set-Cookie: VTEXSC`. With the Cloudflare behavior described in this PR, that landing response still bypasses the CDN, so the claimed non-default-channel cacheability fix is incomplete.</violation>
</file>

Reply with feedback, questions, or to request a fix.

Re-trigger cubic

Comment thread vtex/utils/segment.ts Outdated
// UTM-only and non-default sales channel) stay Set-Cookie-free and CDN-
// cacheable. Mirrors the middleware's cacheability check (isCacheableSegment)
// so the cookie gate and the Cache-Control decision never disagree.
if (vtex_segment !== token && !isCacheableSegment(ctx)) {

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

P1: UTM-only requests now become publicly cacheable here, but simulation.ts still treats any UTM as non-anonymous and sends it as marketingData; a VTEX promotion keyed by that UTM can therefore produce a campaign price in a response shared with users without the UTM. Requests whose marketing data can affect pricing need to remain out of the public page cache, or those fields need to be excluded from simulation for cacheable requests.

Prompt for AI agents
Check if this issue is valid — if so, understand the root cause and fix it. At vtex/utils/segment.ts, line 267:

<comment>UTM-only requests now become publicly cacheable here, but `simulation.ts` still treats any UTM as non-anonymous and sends it as `marketingData`; a VTEX promotion keyed by that UTM can therefore produce a campaign price in a response shared with users without the UTM. Requests whose marketing data can affect pricing need to remain out of the public page cache, or those fields need to be excluded from simulation for cacheable requests.</comment>

<file context>
@@ -260,9 +260,11 @@ export const setSegmentBag = (
+  // UTM-only and non-default sales channel) stay Set-Cookie-free and CDN-
+  // cacheable. Mirrors the middleware's cacheability check (isCacheableSegment)
+  // so the cookie gate and the Cache-Control decision never disagree.
+  if (vtex_segment !== token && !isCacheableSegment(ctx)) {
     setCookie(ctx.response.headers, {
       value: token,
</file context>

Comment thread vtex/utils/segment.ts Outdated
// UTM-only and non-default sales channel) stay Set-Cookie-free and CDN-
// cacheable. Mirrors the middleware's cacheability check (isCacheableSegment)
// so the cookie gate and the Cache-Control decision never disagree.
if (vtex_segment !== token && !isCacheableSegment(ctx)) {

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

P1: This gate removes only vtex_segment; a non-default ?sc=... request still executes the preceding setCookie(... name: SALES_CHANNEL_COOKIE) branch and returns Set-Cookie: VTEXSC. With the Cloudflare behavior described in this PR, that landing response still bypasses the CDN, so the claimed non-default-channel cacheability fix is incomplete.

Prompt for AI agents
Check if this issue is valid — if so, understand the root cause and fix it. At vtex/utils/segment.ts, line 267:

<comment>This gate removes only `vtex_segment`; a non-default `?sc=...` request still executes the preceding `setCookie(... name: SALES_CHANNEL_COOKIE)` branch and returns `Set-Cookie: VTEXSC`. With the Cloudflare behavior described in this PR, that landing response still bypasses the CDN, so the claimed non-default-channel cacheability fix is incomplete.</comment>

<file context>
@@ -260,9 +260,11 @@ export const setSegmentBag = (
+  // UTM-only and non-default sales channel) stay Set-Cookie-free and CDN-
+  // cacheable. Mirrors the middleware's cacheability check (isCacheableSegment)
+  // so the cookie gate and the Cache-Control decision never disagree.
+  if (vtex_segment !== token && !isCacheableSegment(ctx)) {
     setCookie(ctx.response.headers, {
       value: token,
</file context>

Comment thread vtex/utils/segment.ts Outdated
// UTM-only and non-default sales channel) stay Set-Cookie-free and CDN-
// cacheable. Mirrors the middleware's cacheability check (isCacheableSegment)
// so the cookie gate and the Cache-Control decision never disagree.
if (vtex_segment !== token && !isCacheableSegment(ctx)) {

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

P2: Logged-in UTM requests now skip vtex_segment even though middleware marks them no-store: this gate checks isCacheableSegment but not the same auth-cookie predicate used by middleware. The prior !isAnonymous path persisted this changed segment, so the gate should also retain the cookie for logged-in requests.

Prompt for AI agents
Check if this issue is valid — if so, understand the root cause and fix it. At vtex/utils/segment.ts, line 267:

<comment>Logged-in UTM requests now skip `vtex_segment` even though middleware marks them `no-store`: this gate checks `isCacheableSegment` but not the same auth-cookie predicate used by middleware. The prior `!isAnonymous` path persisted this changed segment, so the gate should also retain the cookie for logged-in requests.</comment>

<file context>
@@ -260,9 +260,11 @@ export const setSegmentBag = (
+  // UTM-only and non-default sales channel) stay Set-Cookie-free and CDN-
+  // cacheable. Mirrors the middleware's cacheability check (isCacheableSegment)
+  // so the cookie gate and the Cache-Control decision never disagree.
+  if (vtex_segment !== token && !isCacheableSegment(ctx)) {
     setCookie(ctx.response.headers, {
       value: token,
</file context>
Suggested change
if (vtex_segment !== token && !isCacheableSegment(ctx)) {
if (
vtex_segment !== token &&
(!isCacheableSegment(ctx) ||
Boolean(
cookies["VtexIdclientAutCookie"] ||
cookies[`VtexIdclientAutCookie_${ctx.account}`],
))
) {

igoramf and others added 4 commits July 15, 2026 16:50
UTM can trigger VTEX promotions and change price, so treating UTM-only
requests as cacheable is only safe when the store has opted into
removeUTMFromCacheKey — the same flag the product loaders already use to
strip UTM from their cache key. Without it, dropping the vtex_segment
cookie would let the CDN cache one UTM variant's HTML and serve it
generically, breaking per-UTM pricing.

With the flag on, gate the cookie on isCacheableSegment (ignores UTM);
otherwise keep the previous isAnonymous behavior so UTM-carrying requests
stay non-cacheable and cookie-bearing.

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
Align the middleware's Cache-Control decision with the cookie gate and the
loader cache-key decision: a UTM-carrying request is only treated as
cacheable when the store opts into removeUTMFromCacheKey. Otherwise UTM
disqualifies caching, since UTM can trigger VTEX promotions and change
price. Channel stays cacheable (CDN varies by VTEXSC), unchanged.

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
The middleware change was unnecessary. With removeUTMFromCacheKey off, a
UTM request is already handled: the vtex_segment cookie blocks CDN caching,
and the product loaders keep UTM in their cache key so deco's page cache is
keyed per-UTM (correct pricing). Forcing no-store instead only threw away
that per-UTM caching, contradicting the flag's design. Middleware reverts to
main; the cookie gate in segment.ts is the whole fix.

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
With the flag on, the page is cached with UTM stripped from the cache key,
so a UTM-triggered VTEX promotion computed in the simulation would be cached
and served generically. Skip feeding UTM into marketingData in that mode to
keep the simulation consistent with the caching contract. Campaigns,
priceTables, regionId and channel are still simulated.

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>

@coderabbitai coderabbitai Bot left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

🧹 Nitpick comments (1)
vtex/utils/extensions/simulation.ts (1)

41-44: 📐 Maintainability & Code Quality | 🔵 Trivial | 💤 Low value

Prefer if statements over && chaining for side effects.

Using logical AND (&&) for side effects without assignment is generally considered a code smell. Using explicit if blocks improves readability and avoids potential linter warnings for unused expressions.

♻️ Proposed refactor
-  utmInKey && utm_campaign && md.set("utmCampaign", utm_campaign);
-  utmInKey && utm_source && md.set("utmSource", utm_source);
-  utmInKey && utmi_campaign && md.set("utmiCampaign", utmi_campaign);
-  campaigns && md.set("campaigns", [{ id: campaigns }]);
+  if (utmInKey) {
+    if (utm_campaign) md.set("utmCampaign", utm_campaign);
+    if (utm_source) md.set("utmSource", utm_source);
+    if (utmi_campaign) md.set("utmiCampaign", utmi_campaign);
+  }
+  if (campaigns) md.set("campaigns", [{ id: campaigns }]);
🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@vtex/utils/extensions/simulation.ts` around lines 41 - 44, In the metadata
population logic, replace the side-effect-only && expressions for
utmInKey/utm_campaign, utm_source, utmi_campaign, and campaigns with explicit if
statements. Preserve each existing condition and corresponding md.set call
without changing the stored values.
🤖 Prompt for all review comments with AI agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

Nitpick comments:
In `@vtex/utils/extensions/simulation.ts`:
- Around line 41-44: In the metadata population logic, replace the
side-effect-only && expressions for utmInKey/utm_campaign, utm_source,
utmi_campaign, and campaigns with explicit if statements. Preserve each existing
condition and corresponding md.set call without changing the stored values.

ℹ️ Review info
⚙️ Run configuration

Configuration used: defaults

Review profile: CHILL

Plan: Pro

Run ID: 257cd8b9-9190-477b-85fa-e15046bf794a

📥 Commits

Reviewing files that changed from the base of the PR and between 5f827eb and 4842ac6.

📒 Files selected for processing (2)
  • vtex/utils/extensions/simulation.ts
  • vtex/utils/segment.ts

@cubic-dev-ai cubic-dev-ai Bot left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

1 issue found across 1 file (changes from recent commits).

Prompt for AI agents (unresolved issues)

Check if these issues are valid — if so, understand the root cause of each and fix them. If appropriate, use sub-agents to investigate and fix each issue separately.


<file name="vtex/utils/extensions/simulation.ts">

<violation number="1" location="vtex/utils/extensions/simulation.ts:38">
P2: Enabling `removeUTMFromCacheKey` now drops UTM from every simulation request, so UTM-dependent pricing is ignored even for non-cacheable requests that still call this extension. Preserving UTM for non-cacheable segments and stripping it only for cacheable responses would avoid regressing the existing simulation behavior for no-store responses.</violation>
</file>

Tip: Review your code locally with the cubic CLI to iterate faster.

Re-trigger cubic

// cache key. Feeding UTM into the simulation here would let a UTM-triggered
// promotion change the price and get cached generically, so skip it to keep
// the simulation consistent with the caching contract.
const utmInKey = !ctx.advancedConfigs?.removeUTMFromCacheKey;

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

P2: Enabling removeUTMFromCacheKey now drops UTM from every simulation request, so UTM-dependent pricing is ignored even for non-cacheable requests that still call this extension. Preserving UTM for non-cacheable segments and stripping it only for cacheable responses would avoid regressing the existing simulation behavior for no-store responses.

Prompt for AI agents
Check if this issue is valid — if so, understand the root cause and fix it. At vtex/utils/extensions/simulation.ts, line 38:

<comment>Enabling `removeUTMFromCacheKey` now drops UTM from every simulation request, so UTM-dependent pricing is ignored even for non-cacheable requests that still call this extension. Preserving UTM for non-cacheable segments and stripping it only for cacheable responses would avoid regressing the existing simulation behavior for no-store responses.</comment>

<file context>
@@ -30,10 +30,17 @@ const doSimulate = (items: {
+  // cache key. Feeding UTM into the simulation here would let a UTM-triggered
+  // promotion change the price and get cached generically, so skip it to keep
+  // the simulation consistent with the caching contract.
+  const utmInKey = !ctx.advancedConfigs?.removeUTMFromCacheKey;
+
   const md = new Map<string, unknown>();
</file context>

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant