feat(vercel): add Vary: accept so CDN serves markdown to agents#265
feat(vercel): add Vary: accept so CDN serves markdown to agents#265sriramveeraghanta wants to merge 1 commit intomasterfrom
Conversation
The existing Accept: text/markdown rewrite was being masked by Vercel's CDN caching the first variant (HTML) and serving it for all subsequent requests regardless of Accept header. Advertising Vary: accept lets the CDN cache HTML and markdown as separate entries so agents actually get markdown back.
|
The latest updates on your projects. Learn more about Vercel for GitHub.
|
📝 WalkthroughWalkthroughThe pull request adds a Changes
Estimated code review effort🎯 1 (Trivial) | ⏱️ ~3 minutes Poem
🚥 Pre-merge checks | ✅ 5✅ Passed checks (5 passed)
✏️ Tip: You can configure your own custom pre-merge checks in the settings. ✨ Finishing Touches🧪 Generate unit tests (beta)
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. Comment |
There was a problem hiding this comment.
Actionable comments posted: 1
🤖 Prompt for all review comments with AI agents
Verify each finding against the current code and only fix it if needed.
Inline comments:
In `@vercel.json`:
- Around line 11-14: Replace the Vary header value "accept" with "Accept" to
match HTTP header casing (change the value for the object with "key": "Vary"),
then deploy and monitor CDN cache hit-rate; if cache fragmentation increases,
implement a more targeted negotiation: add a custom middleware header (e.g.,
"X-Preferred-Format" or similar) that maps incoming Accept strings to a small
set like "markdown" or "html", set that header on responses and change Vary to
that custom header instead to avoid high cardinality.
🪄 Autofix (Beta)
Fix all unresolved CodeRabbit comments on this PR:
- Push a commit to this branch (recommended)
- Create a new PR with the fixes
| { | ||
| "key": "Vary", | ||
| "value": "accept" | ||
| } |
There was a problem hiding this comment.
🧩 Analysis chain
🌐 Web query:
How does Vercel's edge CDN handle Vary: Accept — does it normalize the Accept header into buckets or key the cache on the raw value?
💡 Result:
Vercel's Edge CDN keys the cache on the raw value of the Accept header rather than normalizing it into buckets. The Accept header is included in the cache key by default, even without an explicit Vary: Accept response header. When a Vary: Accept header is set (along with a caching Cache-Control like s-maxage), the CDN creates unique cache entries for each distinct combination of the base cache key and the raw Accept header value from the request. This standard HTTP Vary behavior ensures correct content negotiation (e.g., serving WebP vs JPEG based on browser Accept headers) but can lead to high cache fragmentation if Accept values vary widely across clients, as no normalization or bucketing is mentioned or implied in official docs.
Citations:
- 1: https://vercel.com/docs/functions/edge-functions/edge-caching
- 2: https://vercel.com/docs/caching/cdn-cache
- 3: https://vercel.com/docs/edge-network/caching
- 4: https://vercel.com/changelog/serve-personalized-content-faster-with-vary-support
- 5: Vercel CDN cache and `Vary: Accept` header. vercel/vercel#7612
Change "accept" to "Accept" to match HTTP header naming conventions (line 13).
This is functionally correct and solves the stale-HTML-for-agents problem. However, Vary: Accept creates unique cache entries for each raw Accept header value on Vercel's CDN (no normalization is performed). Since browsers send highly variable Accept headers (e.g. text/html,application/xhtml+xml,application/xml;q=0.9,image/webp,*/*;q=0.8 with differences across Chrome/Firefox/Safari/versions), this significantly fragments the cache by keying on the full raw header string.
Monitor hit-rate impact after rollout. If cache efficiency regresses, consider a more targeted approach using custom middleware headers that only distinguish markdown vs. html, then Vary on that instead—this gives the same content negotiation without the cardinality cost.
🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.
In `@vercel.json` around lines 11 - 14, Replace the Vary header value "accept"
with "Accept" to match HTTP header casing (change the value for the object with
"key": "Vary"), then deploy and monitor CDN cache hit-rate; if cache
fragmentation increases, implement a more targeted negotiation: add a custom
middleware header (e.g., "X-Preferred-Format" or similar) that maps incoming
Accept strings to a small set like "markdown" or "html", set that header on
responses and change Vary to that custom header instead to avoid high
cardinality.
There was a problem hiding this comment.
Pull request overview
This PR updates the Vercel deployment configuration to ensure Accept-based markdown content negotiation works correctly when responses are cached at the CDN edge.
Changes:
- Add a global
Vary: acceptresponse header so Vercel’s CDN caches HTML and markdown variants separately.
💡 Add Copilot custom instructions for smarter, more guided reviews. Learn how to get started.
| }, | ||
| { | ||
| "key": "Vary", | ||
| "value": "accept" |
There was a problem hiding this comment.
Consider using the canonical header name casing in the Vary value (i.e., Accept instead of accept) for consistency with HTTP specs and the external references mentioned in the PR description, and to reduce the chance of any intermediary treating the token case-sensitively.
| "value": "accept" | |
| "value": "Accept" |
Summary
Vary: acceptto the global response headers invercel.jsonAccept: text/markdownfrom receivingcontent-type: text/markdownWhy
PR #263 wired up
buildEndto copy.mdintodist/and added a rewrite rule that routesAccept: text/markdownto/:path*.md. In practice, the rewrite only wins on a cold cache — once HTML is cached for a path, Vercel's CDN serves that HTML for all subsequent requests regardless of Accept header, because responses don't advertise that they vary on it.Advertising
Vary: acceptlets the CDN split the cache so HTML and markdown coexist as separate entries. Cloudflare's Markdown for Agents reference and the markdown-negotiation skill both specify this header.Test plan
After the Vercel preview deploys:
curl -I -H "Accept: text/markdown" <preview>/api-reference/introductionreturnscontent-type: text/markdown; charset=utf-8curl -I <preview>/api-reference/introduction(no Accept) returnscontent-type: text/html; charset=utf-8vary: accept.mdfetch still works:curl -I <preview>/api-reference/introduction.mdhttps://isitagentready.com—checks.contentAccessibility.markdownNegotiation.statusreturnspassSummary by CodeRabbit