feat(app-router): track dynamic usage in a per-request sticky latch - #3454
james-elicx wants to merge 6 commits into
Conversation
|
/bigbonk review for issues |
@vinext/cloudflare
create-vinext-app
@vinext/types
vinext
@cloudflare/workers-response-store
commit: |
13fc32f to
ced765b
Compare
|
LGTM! |
Performance benchmarksCompared 0 improved · 0 regressed · 6 within ±1.5%
View detailed results and traces 🟢 improvement · 🔴 regression · ⚫ change below 1.5% · paired base/head |
ced765b to
b349c53
Compare
b349c53 to
aca2a84
Compare
aca2a84 to
2d6ab8e
Compare
|
/bigbonk review for issues Please review exact head |
|
LGTM! |
|
2d6ab8e to
cf41f57
Compare
|
/bigbonk review for issues Please review exact head |
|
LGTM! |
cf41f57 to
3220ace
Compare
|
/bigbonk review for issues Please review exact head |
|
LGTM! |
3220ace to
463df6c
Compare
|
/bigbonk review for issues Please review exact head |
|
LGTM! |
463df6c to
4b0f05c
Compare
|
/bigbonk review for issues Please review exact head |
|
LGTM! |
|
/bigbonk review for issues Please review exact head |
|
1403ab2 to
a67b296
Compare
|
Fixed the Low finding ( |
|
/bigbonk review for issues Please review exact head |
|
LGTM! |
a67b296 to
73b1e53
Compare
73b1e53 to
8b378cc
Compare
dbe1edb to
17a0984
Compare
consumeDynamicUsage resets the dynamic flag at the shell, and isolated scopes such as the layout probe don't propagate to it, so code running late in a render can't tell whether the render already used a dynamic API. Add one latch object per request, shared by every child scope, that markDynamicUsage sets and nothing clears, with a way to wait for it.
… and throwing listeners The headers fallback state persists on globalThis across HMR, so one created before the latch existed has none; create it on first access. Listeners are cleared before they run, so a throwing one skipped the rest forever and failed the dynamic API call; notify each one and log its error instead.
…ld scopes A state that predates the latch got one only on first access, so a child scope cloned from it created its own and dynamic usage there never reached the parent. Create the latch on the parent before every clone: in runWithUnifiedStateMutation and in the fallback isolated-usage and connection-probe scopes.
…rded dynamic usage A state created before an HMR update has no latch. Creating it unset lost dynamic usage the state had already recorded, so start it from the state's dynamicUsageDetected flag. The headers shim and unified scopes now share one ensureRenderDynamicLatch helper.
…-populated dynamic usage flag
The default latch was built before `...opts` applied an overridden
dynamicUsageDetected, so createRequestContext({ dynamicUsageDetected: true })
returned an unset latch that ensureRenderDynamicLatch would not reconcile.
Start it from the passed flag; an explicitly passed latch still wins.
17a0984 to
567f094
Compare
Stacked on #3453. First part of plan PR 1 (cache-candidate SSR mode), which fixes a query-bearing miss poisoning the shared ISR key.
Problem
In cache-candidate mode, SSR
useSearchParams()will wait on a per-request gate. The gate opens with real values as soon as the render turns uncacheable, and bails out to client rendering once the render settles. That needs a signal for "this render has used a dynamic API at any point", and the existing flag can't provide it:consumeDynamicUsageresetsdynamicUsageDetectedat the shell, so a gate issued after the shell would miss earlier usage.runWithIsolatedDynamicUsage, and unified-state mutations that reset the flag in a child clone.Change
VinextHeadersShimState.renderDynamicLatchis one object per request. It's created with the request's headers context (runWithHeadersContext,setHeadersContext,createRequestContext) and never replaced. Every child scope shallow-copies state, so they all share the same object; the clone comment now lists it among the fields that must stay shared.markDynamicUsagesets the latch and notifies waiters. Nothing clears it.force-staticnever sets it, becausemarkDynamicUsagereturns early there.isRenderDynamicLatched()andonRenderDynamicLatched(listener)expose it. The gates in the next PR use them.Nothing reads the latch yet, so there's no behaviour change.
Tests
tests/render-dynamic-latch.test.tscovers the plan's latch test:runWithUnifiedStateMutation, the layout probe (runLayoutProbe) and the non-unifiedrunWithIsolatedDynamicUsageis visible later, even after the flag is consumed;force-staticnever latches;