mirror of
https://github.com/cloudflare/vinext.git
synced 2026-05-09 08:25:34 +02:00
[PR #359] [CLOSED] fix(app-router): implement ISR caching gated behind production NODE_ENV #509
Labels
No labels
enhancement
enhancement
good first issue
help wanted
nextjs-tracking
nextjs-tracking
pull-request
No milestone
No project
No assignees
1 participant
Notifications
Due date
No due date set.
Dependencies
No dependencies set.
Reference
starred/vinext#509
Loading…
Add table
Add a link
Reference in a new issue
No description provided.
Delete branch "%!s()"
Deleting a branch is permanent. Although the deleted branch may continue to exist for a short time before it actually gets removed, it CANNOT be undone in most cases. Continue?
📋 Pull Request Information
Original PR: https://github.com/cloudflare/vinext/pull/359
Author: @james-elicx
Created: 3/8/2026
Status: ❌ Closed
Base:
main← Head:fix/app-router-isr-production-guard📝 Commits (10+)
8b076c9fix(app-router): implement ISR caching gated behind production NODE_ENV919591eMerge remote-tracking branch 'origin/main' into fix/app-router-isr-production-guard38d7495test: update entry template snapshots for App Router ISR changesaee9cc0fix(isr): complete RSC caching, fix nav context, add X-Vinext-Cache: MISS5be8559test(e2e): add app-router-prod Playwright project with production ISR lifecycle tests40fee93ci: add app-router-prod to E2E matrix40054a7fix(isr): thread fetch tags into isrSet so revalidateTag invalidates page cache2df32daperf(isr): move cache check before render, stream MISS response, fix CodeQL ReDoS9138a1cfix(isr): add path tags to isrSet, cache RSC MISS, fix STALE regen font timingd7714a2perf(dev): cache loadModule() result to eliminate per-request Vite IPC overhead📊 Changes
10 files changed (+3303 additions, -582 deletions)
View changed files
📝
.github/workflows/ci.yml(+1 -0)📝
packages/vinext/src/cloudflare/kv-cache-handler.ts(+27 -3)📝
packages/vinext/src/index.ts(+1 -0)📝
packages/vinext/src/server/app-dev-server.ts(+438 -82)📝
packages/vinext/src/server/app-router-entry.ts(+2 -2)📝
packages/vinext/src/server/isr-cache.ts(+16 -0)📝
packages/vinext/src/shims/cache.ts(+17 -8)📝
playwright.config.ts(+12 -0)📝
tests/__snapshots__/entry-templates.test.ts.snap(+2548 -487)➕
tests/e2e/app-router-prod/isr.spec.ts(+241 -0)📄 Description
Summary
export const revalidate = Nread from/write to the pluggable cache handler (set viasetCacheHandler), serving HIT/STALE/MISS responses — matching Pages Router behaviorrscDatacache entry fieldX-Vinext-Cacheon every ISR response: HIT, STALE, and MISS all emit the header for observability paritys-maxage=N, stale-while-revalidateon ISR pages; only the cache handler interaction is gated behindNODE_ENV === "production"activeHandlermoved toglobalThis:shims/cache.tsnow stores the active cache handler onglobalThis[Symbol.for("vinext.activeHandler")]so all module instances in the same process (host + Vite RSC environment) share the same referenceHow it works
The generated RSC entry gains full ISR logic wrapped in
if (process.env.NODE_ENV === "production"). Vite'sdefineconfig statically replacesprocess.env.NODE_ENVat transform time ("development"or"test"in non-production), so the cache handler interaction is dead-code-eliminated in dev/test — every request re-renders fresh.HTML requests:
X-Vinext-Cache: HITX-Vinext-Cache: STALE+ triggers background regeneration (uses_frozenNavContextcaptured before request context is cleared, sousePathname()etc. work correctly in background renders)rscStreambeforehandleSsr, then collects HTML and RSC flight bytes concurrently (concurrent collection avoids a backpressure deadlock — SSR produces HTML by consuming the RSC stream, so draining them in series could stall), stores both in cache, returns withX-Vinext-Cache: MISShtmlStreamresponse withCache-Controlheader still setRSC requests (client-side navigation):
rscDataFixes addressed from review
renderFreshAndCachecalled_getNavigationContext()after context was already nulled_frozenNavContextbefore nulling; use it in all background regen lambdasX-Vinext-Cache: MISSon cache misstriggerBackgroundRegenerationtype mismatch (Promise<string>vs() => Promise<void>)async () => { await renderFreshAndCache(); }rscStreamteed on HTML MISS to populaterscDatacollectStreamAsArrayBufferused but not definedreturn new Response(cachedRscStream, ...)🔄 This issue represents a GitHub Pull Request. It cannot be merged through Gitea due to API limitations.