From bdb2f351273a3bd9f3dcfd4d62a94f8818ab156e Mon Sep 17 00:00:00 2001 From: Dustin Healy <54083382+dustinhealy@users.noreply.github.com> Date: Wed, 22 Jul 2026 13:54:28 -0700 Subject: [PATCH] fix: stop the CDN caching markdown-negotiated docs pages as HTML /docs/* requests with Accept: text/markdown (LLM/agent tooling) get rewritten by proxy.ts to the raw-markdown response, but that response shares its cache key with the HTML document since Cloudflare ignores Vary here. Whichever variant a given edge PoP sees first for a URL gets cached and served to everyone else hitting that PoP, including regular browsers, until the entry expires. This is the same bug class #642 fixed for the RSC flight payload, just never extended to the markdown content negotiation path. Mirror that fix: mark Accept: text/markdown responses private, no-store so the shared CDN never caches them. --- next.config.mjs | 14 ++++++++++++++ 1 file changed, 14 insertions(+) diff --git a/next.config.mjs b/next.config.mjs index 9e4c32e98..90a60e3b3 100644 --- a/next.config.mjs +++ b/next.config.mjs @@ -90,6 +90,15 @@ const OG_VERSION = computeOgVersion() * `private, no-store` so the CDN never caches it and therefore can never serve * one as a document. A cached document occasionally returned to an RSC request * just makes Next fall back to a full navigation, which is harmless. + * + * The same collision applies to the markdown content negotiation in proxy.ts + * (`isMarkdownPreferred`): a `/docs/*` request with `Accept: text/markdown` + * (LLM/agent tooling) gets rewritten to the raw-markdown response, but that + * response shares its cache key with the HTML document since Cloudflare + * ignores `Vary` here too. Whichever one a given edge PoP sees first for a URL + * gets cached for it and served to everyone else hitting that PoP, including + * browsers, until the entry expires. Mark those responses `private, no-store` + * for the same reason as the RSC flight payload. */ const SHARED_CDN_CACHE = 'public, s-maxage=86400, stale-while-revalidate=604800' const cdnCacheHeaders = [ @@ -110,6 +119,11 @@ const cdnCacheHeaders = [ has: [{ type: 'header', key: 'RSC' }], headers: [{ key: 'Cache-Control', value: 'private, no-store' }], }, + { + source, + has: [{ type: 'header', key: 'accept', value: '.*text/markdown.*' }], + headers: [{ key: 'Cache-Control', value: 'private, no-store' }], + }, ]) /** @type {import('next').NextConfig} */