fix(cloudflare): classify CDN cacheability per concrete route - #3115
fix(cloudflare): classify CDN cacheability per concrete route#3115james-elicx wants to merge 22 commits into
Conversation
|
Performance benchmarksCompared 0 improved · 0 regressed · 6 within ±1.5%
View detailed results and traces 🟢 improvement · 🔴 regression · ⚫ change below 1.5% · paired base/head |
|
/bigbonk review for issues |
commit: |
|
/bigbonk review for issues |
|
/bigbonk review for issues |
|
/bigbonk review for issues |
|
@james-elicx Bonk workflow failed. Check the logs for details. View workflow run · To retry, trigger Bonk again. |
|
/bigbonk review for issues; hard time limit: return the best complete findings within 8 minutes |
45c25bd to
1a5c473
Compare
1a5c473 to
83394fd
Compare
|
/bigbonk review for issues; hard time limit: 20 minutes. Review exact head 83394fd. |
|
error: Your local changes to the following files would be overwritten by checkout: |
|
/bigbonk review for issues; hard time limit: return your complete verdict within 10 minutes. Review exact head 83394fd. |
|
error: Your local changes to the following files would be overwritten by checkout: |
|
/bigbonk review for issues. Hard time limit: 10 minutes; stop and report any completed findings when the limit is reached. |
|
error: Your local changes to the following files would be overwritten by checkout: |
|
/bigbonk review for issues |
|
error: Your local changes to the following files would be overwritten by checkout: |
|
/bigbonk review for issues. Hard time limit: 10 minutes; review exact head 9c660ab and return the completed verdict within that limit. |
|
error: Your local changes to the following files would be overwritten by checkout: |
|
/bigbonk review for issues. Hard time limit: 10 minutes; review exact head 0bc7f63 and return the completed verdict within that limit. |
|
error: Your local changes to the following files would be overwritten by checkout: |
Summary
Addresses the large-app two-stage CDN cacheability feedback above #3113.
getStaticPaths()routes withfallback: trueorfallback: "blocking"even when they return zero pathsgenerateStaticParams, or explicitdynamic = "force-static"/"error", as one pattern-only candidategenerateStaticParamsfor App Pages while continuing to allow it for App Route Handlersforce-dynamic/ effectiverevalidate = 0result and only when config cache policies cannot vary for a known path, an unlisted path, or ahas/missingconditionbasePath, so HTML and data share one probeVaryfields when the active CDN adapter guarantees verbatim header-keyed variantsVaryfields during warm validation while still requiring every vinext RSC selectorCF_VERSION_METADATAwithout rendering an application route--verbosegating for raw Wrangler outputdist/serverbefore final upload and retains the exact deployable artifact locallyThe readiness endpoint is deployment-internal and runs before middleware, routing, cacheability admission, and rendering. Its reserved pathname returns a private
404whenever the capability, method, or expected-version predicate fails, so it can never fall through to userland. An authorized request returns an exact204withCache-Control: no-storeand a framework-only readiness marker; the deploy client requires all three plus the matching build identity. The normal outer response boundary stamps the build identity. No ordinary application route or cache-eligibility behavior changes.The cacheability manifest remains a generated JavaScript asset embedded in the final Worker upload. It is not a binding and contains no response bodies. After classification, deployment atomically replaces the placeholder module in dist/server and uploads that exact generated artifact. The manifest remains in dist after success or failure, so the user-visible build output is the deployable source of truth.
Next.js parity and safety
Next.js classifies concrete
generateStaticParamsresults independently: one parameter can prerender while a sibling usingcookies(),headers(), request-time search params, or uncached data remains dynamic. This layer therefore never uses one concrete path as proof for another unless the staged Worker reports a genuinely pattern-wide route-config bailout.With
cacheComponents: true, an emptygenerateStaticParamsresult fails discovery for an App Page because no concrete path exists for dynamic-access validation. App Route Handlers continue to allow an empty result, matching Next.js route-module behavior. With Cache Components disabled, zero-path static page fallbacks also remain eligible.Pages
getStaticPaths()withfallback: trueor"blocking", and App Route Handlers with an emptygenerateStaticParamsor an explicit static contract, remain eligible for on-demand static generation even when no concrete path exists to probe. They are represented by one compact pattern record with zero speculative probes. Each actual response is still buffered to clean EOF and re-evaluated for request APIs, preview/cookies, response policy, unsupported variance, errors, and incomplete bodies before CDN admission. A concrete dynamic result overrides only that exact path; it does not erase the framework's fallback eligibility for unobserved paths.Authenticated staged probes have no byte-size limit and discard chunks as they drain. Final CDN admission is different: it must retain a public request's response until clean EOF, so it uses a 16 MiB per-response and 32 MiB per-isolate capture envelope to avoid exhausting Cloudflare's 128 MiB isolate memory. Crossing that envelope preserves and privately streams every byte; it does not fail or truncate the response, but that response is not admitted to the CDN. A response that misses the completion deadline likewise falls back to private streaming. Cancellation is best-effort so a hostile non-settling user stream cannot hold request context indefinitely.
For a known static HTML path, RSC is authorized only for a completed runtime check; it does not inherit HTML's static-to-dynamic failure state. Public outcomes supplied only by next.config remain runtime checks unless the renderer itself was static. Unknown fallbacks under an all-static pattern retain Next.js static eligibility and static-to-dynamic errors, while unknown paths under a mixed pattern cannot inherit a static sibling proof.
Workers Cache declares the same verbatim
Varycapability at runtime and build time. Admission carries that guarantee through both App and Pages request contexts.Vary: *remains unconditionally private.Relevant Next.js references:
Scale
The nodejs.org discovery artifact contains 2,272 concrete paths across seven patterns. Prefix compaction reduces its exact-path array payload from 68,822 bytes to an estimated 40,951 bytes while retaining one exact membership token per concrete path. A 2,272-path regression verifies 2,272 probes—not 4,544 HTML/RSC probes—and keeps the generated manifest below 20 KiB for the synthetic
/docs/:slugcase.Validation
vp checkvp test run tests/worker-prerender-discovery.test.ts tests/app-router-worker-entry.test.ts tests/pages-router-worker-entry.test.ts tests/cloudflare-cdn-warm.test.ts tests/cloudflare-cdn-warm-deploy.test.ts tests/cache-adapters-config.test.ts(152/152)vp test run tests/deploy.test.ts tests/cloudflare-cdn-cache.test.ts tests/request-pipeline.test.ts(482/482)vp run vinext#buildandvp run @vinext/cloudflare#buildvp test run tests/cacheability-admission.test.ts tests/app-route-handler-execution.test.ts(83/83)vp test run tests/cloudflare-cdn-warm-deploy.test.ts tests/cloudflare-cdn-warm.test.ts tests/deploy.test.ts(453/453)PLAYWRIGHT_PROJECT=ppr-impact-demo pnpm run test:e2e(9/9 workerd tests)CF-Cache-Status: MISStoHITgenerateStaticParamsApp Route Handler, and a cacheable custom-VaryRoute HandlerStack
Layer 10, based on #3113.
Stack managed with GitHub Stacks CLI