Skip to content

fix(cloudflare): classify CDN cacheability per concrete route - #3115

Open
james-elicx wants to merge 22 commits into
codex/cacheability-route-handlersfrom
codex/cacheability-probe-scalability
Open

fix(cloudflare): classify CDN cacheability per concrete route#3115
james-elicx wants to merge 22 commits into
codex/cacheability-route-handlersfrom
codex/cacheability-probe-scalability

Conversation

@james-elicx

@james-elicx james-elicx commented Aug 27, 2026

Copy link
Copy Markdown
Member

Summary

Addresses the large-app two-stage CDN cacheability feedback above #3113.

  • discovers route ownership once and sends at most one staged render probe per concrete route pathname, rather than probing HTML/RSC or HTML/Pages-data identities separately
  • records the exact result of every probed concrete path so parameter-dependent dynamic API usage cannot classify a sibling
  • lets paired RSC, loading-shell, and Pages-data identities perform their own completed-render admission during the final warm without spending another classification probe
  • keeps a paired representation becoming private as a valid optional skip; a required primary fill still blocks promotion
  • permits unknown fallbacks only when every discovered path was genuinely static, preserving Next.js static-to-dynamic errors; mixed-pattern unknown paths remain private
  • retains Pages getStaticPaths() routes with fallback: true or fallback: "blocking" even when they return zero paths
  • retains dynamic App Route Handlers with empty generateStaticParams, or explicit dynamic = "force-static" / "error", as one pattern-only candidate
  • preserves Next.js Cache Components validation by rejecting empty generateStaticParams for App Pages while continuing to allow it for App Route Handlers
  • treats App Route Handler eligibility independently from page layouts and parallel slots, matching Next.js route-module behavior
  • merges fallback eligibility with exact concrete-path observations so an observed private path cannot erase static eligibility for an unknown fallback path
  • prunes remaining siblings only after a pattern-wide force-dynamic / effective revalidate = 0 result and only when config cache policies cannot vary for a known path, an unlisted path, or a has/missing condition
  • maps default-locale Pages data URLs back to the configured HTML pathname, including basePath, so HTML and data share one probe
  • compacts literal and dynamic route proofs to keep large manifests small
  • preserves custom application, middleware, and config Vary fields when the active CDN adapter guarantees verbatim header-keyed variants
  • accepts extra Vary fields during warm validation while still requiring every vinext RSC selector
  • removes the staged probe body-size cutoff; authenticated probes drain and discard chunks without retaining the body
  • retains a separate completed-response memory envelope for final admission, because ordinary public cache misses must retain the body until clean EOF
  • uses a build-secret-authenticated internal readiness endpoint for both staged versions, validating CF_VERSION_METADATA without rendering an application route
  • keeps six consecutive version-overridden readiness checks while bounding the phase to the explicit timeout (120 seconds by default)
  • retains the no-progress watchdog, terminal progress display, and --verbose gating for raw Wrangler output
  • writes the classified manifest atomically into dist/server before final upload and retains the exact deployable artifact locally

The readiness endpoint is deployment-internal and runs before middleware, routing, cacheability admission, and rendering. Its reserved pathname returns a private 404 whenever the capability, method, or expected-version predicate fails, so it can never fall through to userland. An authorized request returns an exact 204 with Cache-Control: no-store and 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 generateStaticParams results independently: one parameter can prerender while a sibling using cookies(), 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 empty generateStaticParams result 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() with fallback: true or "blocking", and App Route Handlers with an empty generateStaticParams or 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 Vary capability 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/:slug case.

Validation

  • vp check
  • vp 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#build and vp run @vinext/cloudflare#build
  • vp 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)
  • deployed isolated Worker: 4,194,305-byte Route Handler response returned in full on both requests and transitioned from CF-Cache-Status: MISS to HIT
  • workerd regressions cover an over-4-MiB authenticated probe, completed-response admission, on-demand Pages blocking fallback, an empty-generateStaticParams App Route Handler, and a cacheable custom-Vary Route Handler
  • regressions cover completion timeout/cancellation, aggregate capture memory, mixed static/dynamic siblings, all-static fallback static-to-dynamic behavior, representation-specific policy, loading shells, conditional config, and default-locale Pages deduplication

Stack

Layer 10, based on #3113.

Stack managed with GitHub Stacks CLI

@james-elicx james-elicx changed the title codex/cacheability probe scalability fix(cloudflare): scale cacheability probing Aug 27, 2026
@github-actions

Copy link
Copy Markdown
Contributor
Example Preview Production Original
app-router-cloudflare preview production
pages-router-cloudflare preview production
app-router-playground preview production original
realworld-api-rest preview production
nextra-docs-template preview production
benchmarks preview production
hackernews preview production original
workers-cache preview production
static-export preview production
web preview production

@github-actions

github-actions Bot commented Aug 27, 2026

Copy link
Copy Markdown
Contributor

Performance benchmarks

Compared 0bc7f63 against base 85ab957 using alternating same-runner rounds. Next.js was unchanged and skipped.

0 improved · 0 regressed · 6 within ±1.5%

Scenario Framework Baseline Current Change
Client bundle size (gzip) vinext 142.3 KB 142.3 KB ⚫ +0.0%
Client entry size (gzip) vinext 129.6 KB 129.6 KB ⚫ +0.0%
Dev server cold start vinext 3.18 s 3.17 s ⚫ -0.0%
Production build time vinext 3.51 s 3.52 s ⚫ +0.3%
RSC entry closure size (gzip) vinext 119.1 KB 119.2 KB ⚫ +0.1%
Server bundle size (gzip) vinext 202.5 KB 202.8 KB ⚫ +0.1%

View detailed results and traces

🟢 improvement · 🔴 regression · ⚫ change below 1.5% · paired base/head

@james-elicx

Copy link
Copy Markdown
Member Author

/bigbonk review for issues

@pkg-pr-new

pkg-pr-new Bot commented Aug 27, 2026

Copy link
Copy Markdown

Open in StackBlitz

npm i https://pkg.pr.new/@vinext/cloudflare@3115
npm i https://pkg.pr.new/create-vinext-app@3115
npm i https://pkg.pr.new/@vinext/types@3115
npm i https://pkg.pr.new/vinext@3115

commit: 0bc7f63

@james-elicx

Copy link
Copy Markdown
Member Author

/bigbonk review for issues

@james-elicx

Copy link
Copy Markdown
Member Author

/bigbonk review for issues

@james-elicx

Copy link
Copy Markdown
Member Author

/bigbonk review for issues

@ask-bonk

ask-bonk Bot commented Aug 27, 2026

Copy link
Copy Markdown
Contributor

@james-elicx Bonk workflow failed. Check the logs for details.

View workflow run · To retry, trigger Bonk again.

@james-elicx

Copy link
Copy Markdown
Member Author

/bigbonk review for issues; hard time limit: return the best complete findings within 8 minutes

@james-elicx
james-elicx marked this pull request as ready for review August 27, 2026 10:11
@james-elicx
james-elicx marked this pull request as draft August 27, 2026 10:18
@james-elicx james-elicx changed the title fix(cloudflare): scale cacheability probing fix(cloudflare): classify CDN cacheability per concrete route Aug 27, 2026
@james-elicx
james-elicx force-pushed the codex/cacheability-probe-scalability branch 2 times, most recently from 45c25bd to 1a5c473 Compare August 27, 2026 11:51
@james-elicx
james-elicx force-pushed the codex/cacheability-probe-scalability branch from 1a5c473 to 83394fd Compare August 27, 2026 12:11
@james-elicx

Copy link
Copy Markdown
Member Author

/bigbonk review for issues; hard time limit: 20 minutes. Review exact head 83394fd.

@ask-bonk

ask-bonk Bot commented Aug 27, 2026

Copy link
Copy Markdown
Contributor

error: Your local changes to the following files would be overwritten by checkout:
pnpm-lock.yaml
Please commit your changes or stash them before you switch branches.
Aborting

github run

@james-elicx

Copy link
Copy Markdown
Member Author

/bigbonk review for issues; hard time limit: return your complete verdict within 10 minutes. Review exact head 83394fd.

@ask-bonk

ask-bonk Bot commented Aug 27, 2026

Copy link
Copy Markdown
Contributor

error: Your local changes to the following files would be overwritten by checkout:
pnpm-lock.yaml
Please commit your changes or stash them before you switch branches.
Aborting

github run

@james-elicx
james-elicx marked this pull request as ready for review August 27, 2026 12:20
@james-elicx

Copy link
Copy Markdown
Member Author

/bigbonk review for issues. Hard time limit: 10 minutes; stop and report any completed findings when the limit is reached.

@ask-bonk

ask-bonk Bot commented Aug 27, 2026

Copy link
Copy Markdown
Contributor

error: Your local changes to the following files would be overwritten by checkout:
pnpm-lock.yaml
Please commit your changes or stash them before you switch branches.
Aborting

github run

@james-elicx

Copy link
Copy Markdown
Member Author

/bigbonk review for issues

@ask-bonk

ask-bonk Bot commented Aug 27, 2026

Copy link
Copy Markdown
Contributor

error: Your local changes to the following files would be overwritten by checkout:
pnpm-lock.yaml
Please commit your changes or stash them before you switch branches.
Aborting

github run

@james-elicx

Copy link
Copy Markdown
Member Author

/bigbonk review for issues. Hard time limit: 10 minutes; review exact head 9c660ab and return the completed verdict within that limit.

@ask-bonk

ask-bonk Bot commented Aug 27, 2026

Copy link
Copy Markdown
Contributor

error: Your local changes to the following files would be overwritten by checkout:
pnpm-lock.yaml
Please commit your changes or stash them before you switch branches.
Aborting

github run

@james-elicx

Copy link
Copy Markdown
Member Author

/bigbonk review for issues. Hard time limit: 10 minutes; review exact head 0bc7f63 and return the completed verdict within that limit.

@ask-bonk

ask-bonk Bot commented Aug 27, 2026

Copy link
Copy Markdown
Contributor

error: Your local changes to the following files would be overwritten by checkout:
pnpm-lock.yaml
Please commit your changes or stash them before you switch branches.
Aborting

github run

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