Skip to content

fix: draftMode() must mark dynamic usage to prevent caching#369

Merged
james-elicx merged 1 commit intocloudflare:mainfrom
NathanDrake2406:fix/draft-mode-dynamic-usage
Mar 9, 2026
Merged

fix: draftMode() must mark dynamic usage to prevent caching#369
james-elicx merged 1 commit intocloudflare:mainfrom
NathanDrake2406:fix/draft-mode-dynamic-usage

Conversation

@NathanDrake2406
Copy link
Copy Markdown
Contributor

Summary

  • draftMode() reads the __prerender_bypass cookie (request-specific data) but did not call markDynamicUsage(), unlike headers() and cookies()
  • This means a Server Component branching on draftMode().isEnabled could have its output cached by ISR, serving draft content to regular users or stale content to draft users
  • Added the missing markDynamicUsage() call and a test verifying the behavior

Test plan

  • New test: draftMode() marks dynamic usage so the render is uncacheable — calls draftMode() within a headers context and asserts consumeDynamicUsage() returns true
  • All existing draft-mode tests pass (8/8)

draftMode() reads the __prerender_bypass cookie, which is
request-specific data. Without calling markDynamicUsage(), a Server
Component branching on draftMode().isEnabled could have its output
cached by ISR — serving draft content to regular users or stale
content to draft users.

headers() and cookies() already call markDynamicUsage(); draftMode()
was the only request-reading API that didn't.
@pkg-pr-new
Copy link
Copy Markdown

pkg-pr-new Bot commented Mar 9, 2026

Open in StackBlitz

npm i https://pkg.pr.new/vinext@369

commit: 0c120ec

@james-elicx
Copy link
Copy Markdown
Collaborator

/bigbonk review this pr

Copy link
Copy Markdown
Contributor

@ask-bonk ask-bonk Bot left a comment

Choose a reason for hiding this comment

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

Clean, correct fix. draftMode() reads the __prerender_bypass cookie (request-specific data) and was the only dynamic request API in the headers shim that didn't call markDynamicUsage(). Without this, ISR could cache a response that branched on draftMode().isEnabled, serving draft content to regular users or vice versa.

Verified that the fix is consistent with the existing pattern:

  • headers() — calls markDynamicUsage() (line 276)
  • cookies() — calls markDynamicUsage() (line 294)
  • connection() in server.ts — calls markDynamicUsage() (line 459)
  • noStore() in cache.ts — calls _markDynamic() (line 365)
  • draftMode() — now calls markDynamicUsage() (line 362) ✓

Placement is correct — it's called before _getState() reads the cookie, matching the throwIfInsideCacheScopemarkDynamicUsage → read-state ordering used by headers() and cookies().

Test is well-structured: sets up ALS context, resets prior dynamic usage, calls draftMode(), and asserts the flag is set. Good use of the existing runWithHeadersContext / consumeDynamicUsage test utilities.

@ask-bonk
Copy link
Copy Markdown
Contributor

ask-bonk Bot commented Mar 9, 2026

Approved. This is a correct one-line fix that closes a real caching bug. draftMode() was the only dynamic request API that didn't call markDynamicUsage(), meaning ISR could incorrectly cache responses that branched on draft mode state. The fix is consistent with how headers(), cookies(), connection(), and noStore() all mark dynamic usage, and the test properly verifies the behavior.

github run

@james-elicx james-elicx merged commit 7405d88 into cloudflare:main Mar 9, 2026
16 checks passed
@NathanDrake2406 NathanDrake2406 deleted the fix/draft-mode-dynamic-usage branch March 18, 2026 09:50
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.

2 participants