Repository navigation
Replies: 1 comment
|
I can see why this matters for collaborative apps. A reload that normally fixes a stale client could actually make things worse if the user has unsaved changes or the app needs to flush some telemetry first. I like the idea of an optional callback, especially if the existing behavior stays exactly the same when no callback is registered. One thing I'd be careful about is what happens if the callback throws, rejects, or never finishes. It probably shouldn't be possible for a failed callback to leave the app stuck with a stale client forever. Maybe the default reload could still happen after a bounded timeout, unless the app explicitly takes over recovery. I'd also want the loop-guard behavior to be clearly defined when the app chooses to handle recovery itself. Otherwise, repeated mismatches could end up leaving requests hanging. That seems like the main edge case to settle before deciding on the exact API shape. |
Uh oh!
There was an error while loading. Please reload this page.
We've adopted the 8.3
clientVersionstale-client detection across four RSC Framework Mode apps (custom entries) and it works well. This proposal is about the one place it currently limits us: the recovery reload is unconditional and invisible to the app, and for a collaborative app that combination is actively harmful. Following the contributing guide, here's how we're limited by the current API first; a candidate shape is at the end.The limitation
On a detected mismatch the runtime does
window.location.href = errorReloadPath(guarded by sessionStorage) with no way for the app to know it's happening. Two real consequences we hit:1.
beforeunloadguards fire on the framework's own recoveryCollaborative editors guard against losing unsynced changes:
When the skew reload fires mid-session, the browser shows the native Leave site? dialog in the middle of the framework's automatic recovery. If the user cancels it:
2. Session telemetry dies with the page
Error/analytics buffers flush on a best-effort
pagehide, but a hardlocation.hrefassignment mid-fetch loses whatever context explains why the reload happened. We'd like to record "reloaded due to version skew, from version X" — today the only trace is a console warning.The workaround we currently maintain
Wrapping the
fetchpassed to the RSC entry points and re-implementing detection + bounded recovery end-to-end (~400 lines per app: version header, mismatch classification, history-state loop guard, a window flag our collab provider'sbeforeunloadguard checks, telemetry flush). It predates 8.3 — but 8.3's built-in can't replace it solely because there's no seam at the moment of recovery. We'd much rather delete our pipeline and keep the framework's.Candidate shape (one option — genuinely open to different ones)
return false= the app owns recovery: the runtime still refuses to apply the stale payload, but skips its ownlocation.href. In that case the sessionStorage guard should arguably not be armed — otherwise the second mismatch for the same version short-circuits before the hook and later fetches hang silently. Pinning that interaction down is part of the design.Open design points we'd like maintainer input on
RSCHydratedRouterprop cannot reach thecreateCallServerpath (it's wired viasetServerCallbackindependently), so a prop-based API can't service server-action mismatches. A registration on the shared surface next to the existing router globals would cover all call sites — including, if desired, the non-RSC fog-of-war path. Alternatively RSC-scope it and drop"server-action".clientVersionAPI to stabilize, and if so, what interim escape hatch you'd suggest for thebeforeunloadcollision — that one bites today.Related: while evaluating this we found that
createCallServerapplies action rerender payloads without a version check — filed separately as a straightforward coverage fix, since it adds no API surface: #15342Written on behalf of agcty.
All reactions