mirror of
https://github.com/cloudflare/vinext.git
synced 2026-09-14 19:04:59 +08:00
vinext@0.1.4
6 Commits
| Author | SHA1 | Message | Date | |
|---|---|---|---|---|
|
|
5ac620c2d0 |
fix(cache): honor fetch opt-outs and force-dynamic revalidate parity (#1907)
* fix(cache): honor fetch opt-outs in app page output
App Router fetches that explicitly opt out of caching could still leave the rendered page eligible for static output, while dynamic = "force-dynamic" was lowered to fetchCache = "force-no-store" and overrode explicit per-fetch cache/revalidate options.
The cache decision was conflating route-level dynamic rendering with a hard segment fetchCache mode. Next.js treats force-dynamic as a default no-store mode only for fetches without explicit cache or revalidate config, and marks explicit uncached fetches as dynamic for the surrounding render.
Mark explicit uncached fetch decisions through the existing dynamic usage signal, keep force-dynamic as a separate per-request fetch default, and preserve Response.url when reconstructing cached fetch responses.
* fix(cache): correct revalidate:false and Response.url semantics
- Split revalidate: false out of the no-store path; it now caches
indefinitely (INFINITE_CACHE) matching upstream patch-fetch.ts.
- Store actual response.url (freshResp.url / response.url) in cached
fetch entries instead of the input request URL.
- Remove markUncachedFetchForPageOutput() from the auth-header fallback
so it matches upstream's autoNoCache path and does not mark pages
dynamic.
- Update tests to reflect correct revalidate: false caching behavior
and add a test for redirect-style response URLs.
* fix(app-router): resolve target route dynamic config during intercepts and revalidation
Prevents the outer route's force-dynamic default from leaking into
interception source routes and ISR revalidation targets.
- app-rsc-entry.ts: expose __resolveRouteDynamicConfig
- app-page-dispatch.ts: pass resolveRouteDynamicConfig to dispatch,
sync currentForceDynamicFetchDefault when the render target changes
- app-page-dispatch.test.ts: 4 tests covering intercept + revalidation
paths for both lost and leaked defaults
* fix(app-router): sync fetchCache and force-dynamic default during action redirects/re-renders
* fix(cache): treat revalidate:false as non-explicit for force-dynamic parity
Upstream patch-fetch.ts computes noFetchConfigAndForceDynamic using
!currentFetchRevalidate (truthiness), so revalidate: false is treated
as 'no fetch revalidate config' and force-dynamic wins. vinext's
hasExplicitRevalidateValue() previously returned true for false,
blocking the force-dynamic no-store default and causing the fetch to
cache for 1 year instead.
Update hasExplicitRevalidateValue to exclude false (and 0) from the
explicit set, aligning with upstream's truthiness check. Add a parity
test covering force-dynamic + next.revalidate: false.
* fix(cache): split revalidate predicates for force-dynamic vs segment defaults
The previous fix to hasExplicitRevalidateValue() treated false and 0 as
non-explicit, which matched upstream's force-dynamic truthiness check
but broke segment cache defaults: default-cache would override
revalidate: 0 to force-cache, and default-no-store would override
revalidate: false to no-store.
Split into two helpers:
- hasExplicitRevalidateValue(): any defined value is explicit (for
segment defaults where 0 and false are explicit opt-outs)
- isFalsyRevalidate(): false and 0 are falsy (for force-dynamic's
noFetchConfigAndForceDynamic parity check)
Add parity tests for default-cache + revalidate: 0 and
default-no-store + revalidate: false.
* refactor(cache): extract ONE_YEAR_SECONDS and cover only-no-store revalidate:false
* fix(cache): keep dynamic fetch observations for auth bypass and cache-key fallback
Restore recordDynamicFetchObservation in the auth-header safety bypass so
auth-keyed fetches still downgrade the page output to fresh render, and
revert the cache-key-generation fallback to a plain observation so an
internal limitation (oversized/unserializable body) does not mark the
whole page dynamic. Lock in force-dynamic vs explicit segment fetchCache
precedence with a test.
* docs(cache): note deliberate no-cache dynamic-marking divergence from upstream
* fix(route-handler): apply segment fetchCache and force-dynamic fetch default to route handler dispatch
Route handlers never called setCurrentFetchCacheMode /
setCurrentForceDynamicFetchDefault, so a force-dynamic route handler did
not get the per-request no-store fetch default that page dispatch applies,
and an explicit fetchCache export on a route handler module was ignored.
Upstream's app-route module copies userland.fetchCache into the work store
and sets workStore.forceDynamic for dynamic = "force-dynamic", which
patch-fetch turns into a no-store default for fetches without explicit
cache config — for route handlers as well as pages. Mirror that in
dispatchAppRouteHandler and re-apply the state inside the background
regeneration request context.
* fix(fetch-cache): tolerate cached entries missing url and document revalidate:false cacheability
* fix(fetch-cache): fall back to the request URL for cached entries missing url
Reconstructing Response.url from the fetch input preserves the
pre-existing guarantee for legacy/foreign cache entries instead of
degrading to "". Also document why the intercept dispatch deliberately
does not save/restore fetch defaults around buildPageElement.
* docs(fetch-cache): note cached responses now join body-stream cleanup
* docs(fetch-cache): note explicit segment fetchCache beats force-dynamic default
* test(fetch-cache): pin tags-only fetch to no-store under force-dynamic
* docs(fetch-cache): document auth-bypass precedence and test no-store + auth
- Note at the auth-safety bypass that explicit no-store/no-cache/revalidate: 0
takes the stronger branch first and fully marks the page dynamic; the bypass
only covers implicitly-cacheable auth-keyed fetches.
- Add a test locking in that explicit no-store with auth headers routes through
markUncachedFetchForPageOutput rather than the softer auth bypass.
- Clarify in dispatchAppRouteHandler that the fetch-cache setters are new
wiring for route handlers, not a mirror of a pre-existing pattern.
* test(route-handler): lock no-store fetch bailing ISR for revalidating handlers
A route handler with revalidate = 60 that performs
fetch(url, { cache: "no-store" }) must skip its ISR cache write and be
marked known-dynamic, now that the patched fetch's explicit no-store
branch calls markDynamicUsage() (upstream patch-fetch parity). Exercises
the real fetch-cache shim and the real headers-shim dynamic-usage pair,
matching the app-route-handler-dispatch wiring.
---------
Co-authored-by: James <james@eli.cx>
|
||
|
|
8498e4a37e |
fix(app-router): emit per-page dynamic stale time metadata (#1711)
* fix(app-router): emit per-page dynamic stale time metadata App Router pages that export unstable_dynamicStaleTime currently behave as if no page-level stale window exists. The server segment config resolver discards the export, so RSC responses cannot carry the per-page metadata that the client cache needs for independently configured pages. The violated invariant is that route segment config resolution must preserve page-only stale-time metadata across active parallel pages before response construction. Parse the page export, reduce active page slots by shortest stale time, thread the value through generated App page dispatch, and emit it on RSC responses for the client cache to consume. * fix(app-router): gate dynamic stale time header to dynamic-only renders Only emit X-Vinext-Dynamic-Stale-Time when the render is truly dynamic (not prerender or force-static), matching Next.js behavior where the response `d` field is only set for non-static-generation renders. The client treats the headers presence as authoritative; leaking it onto static/prerendered RSC responses would cause the client cache to apply dynamic stale time semantics to a statically-cached entry. * test(app-router): add behavioral tests for dynamic stale time gating * docs(app-page-render): add line-numbered link to Next.js source * refactor(app-router): drop redundant Number.isFinite check Number.isInteger already returns false for Infinity/NaN, so the preceding Number.isFinite guard is redundant in both the dynamic stale-time header check and resolveDynamicStaleTimeSeconds. * fix(app-router): gate dynamic stale time on cache-capture predicate The previous gating (isPrerender || isForceStatic) was too narrow for production: isPrerender is only true during the build-time prerender pass, so a production ISR render (revalidate > 0) still emitted X-Vinext-Dynamic-Stale-Time even though that render is captured into the ISR cache. Next.js gates on !workStore.isStaticGeneration; the runtime analog here is shouldCaptureRscForCacheMetadata, so add it to the guard. Adds production ISR coverage to lock down the gating. * docs(app-router): document dynamic stale time over-gating boundary Add a comment explaining that shouldCaptureRscForCacheMetadata is computed from config before the stream runs, so a production default-config route that turns dynamic via a late dynamic API loses the advisory stale-time hint even though Next.js (!isStaticGeneration) would emit it. The value is unknowable until after headers are built. Adds a complementary test pinning down this boundary behavior. --------- Co-authored-by: James <james@eli.cx> |
||
|
|
8ae5abd56c | fix(app-router): preserve forwarded action redirect wrappers (#1582) | ||
|
|
d0839903e8 |
fix(app-router): emit vary marker on edge RSC route response (#1565)
* fix(app-router): emit x-edge-runtime marker on edge RSC route response Closes #1531 The Next.js edge-runtime app template (edge-ssr-app.ts) sets `x-edge-runtime: 1` on every App Router response. vinext serves App Router responses from a Cloudflare Worker (an edge-equivalent runtime) but was not emitting this marker, so Next.js' upstream test suite (`test/e2e/app-dir/app/index.test.ts`) saw `null` for the header. Add the header to both the RSC and HTML response builders so the assertion passes for any App Router page, regardless of which payload shape the client requests. * fix(app-router): gate x-edge-runtime marker on configured runtime Only emit `x-edge-runtime: 1` when the route's resolved segment config opts into the edge runtime (`export const runtime = "edge"` or `"experimental-edge"`). Plumb the resolved runtime from the segment config through dispatch and the render lifecycle so both RSC and HTML response builders see it. Without the gate, nodejs-runtime routes also advertise the marker, which hides the configured runtime from downstream consumers. |
||
|
|
6b7e7557a9 |
fix: support revalidate = false for App route segment config (#1046)
* fix: support revalidate = false for App route segment config (#1041) - Build-time: extractExportConstNumber now matches false and returns Infinity - Classifiers: classifyLayoutSegmentConfig and classifyAppRoute correctly classify revalidate = false as static (indefinite caching) - Runtime: getAppRouteHandlerRevalidateSeconds returns Infinity for false, enabling ISR cache gates that emit STATIC cache-control instead of SWR - Template: RSC entry resolves revalidate = false from page exports as Infinity - Cache-Control: buildRouteHandlerCacheControl emits STATIC cache-control when revalidateSeconds is Infinity, matching the page-path behavior Ported from Next.js segment config validation: https://github.com/vercel/next.js/blob/canary/packages/next/src/build/segment-config/app/app-segment-config.ts * fix: exclude Infinity from route handler ISR read/write gates * Update packages/vinext/src/build/report.ts Co-authored-by: ask-bonk[bot] <249159057+ask-bonk[bot]@users.noreply.github.com> --------- Co-authored-by: ask-bonk[bot] <249159057+ask-bonk[bot]@users.noreply.github.com> |
||
|
|
54a1286839 |
fix(app): apply layout segment config to pages (#993)
* fix(app): apply layout segment config to pages * fix(app): address segment config review nits |