[Deep Dive #60] Data Cache Lanes - discussion
Deep Dive #60 argues that caching scales when you treat it as lanes with explicit rules (allowed wrongness, duplication rules, shell chooses lane per track). The vocabulary (lane:fast, lane:proof, lane:form) and the idea that the shell chooses a lane and the ledger records the choice feels like a way to stop caching from becoming accidental behavior nobody can explain.
Do you actually choose cache lanes at the shell (per track), or do you let each fetch decide its own caching behavior? How do you decide what "allowed to be wrong" means for a given track? Have you implemented a form lane (never lose input) and how do you keep it from showing dangerously stale data? What evidence do you render so users/support can tell which lane they're on (and why)?
Comments (24)
Back to latestShell chooses lane per track.
If fetch decides ad hoc, you get inconsistent UX.
We log lane selection and render it as evidence. It's the difference between "cache bug" and "policy choice":
txt
[tips] laneSelect track=checkout.shipping lane=form reason=form:first
[tips] laneSelect track=checkout.payment lane=proof reason=audit:first
[tips] laneSelect track=checkout.review lane=fast reason=responsive
Then the UI prints data-lane=form and support stops guessing.
The form lane clicked for me as a contract: it prioritizes not losing user input, even if data is stale.
We also render explicit evidence when form lane is using cached server data (so users aren't surprised).
Concrete alternative: keep one cache and just tune TTLs.
We tried it. It works until one surface needs audit-trail correctness and another needs responsiveness. Lanes make the tradeoffs explicit instead of hidden in random TTL numbers.
Exactly. The post's point is policy visibility.
If you're going to have different behaviors, you want them named and reviewable.
Long-form: lane choice is a product promise.
If a user is in a proof lane, they're expecting correctness and auditability. If they're in a fast lane, they're expecting responsiveness. If you mix them accidentally, you break trust.
We had to attach lane choice to track identity (stable) and we required lane evidence in the DOM. That made it possible to see when a surface was accidentally using the wrong lane.
Docs tie-in: the link to forms is key. The form lane is really a forms contract disguised as a caching decision.
Related: Forms and Validation and Data Fetching and Caching.
Short take: if the lane isn't visible, it doesn't exist.
We implemented lanes by making the request wrapper accept lane policy input from the shell.
The wrapper didn't decide lane; the shell did. That split kept policy reviewable.
The dangerous part of form lane is stale correctness.
We solved it by rendering an evidence token when form lane data is older than some threshold, and by making proof lane the default for confirmation/submit screens.
A small lane policy snippet that matched the post's vocabulary:
ts
type Lane = 'fast' | 'proof' | 'form';
type Policy = { cache: 'ghost' | 'network' | 'sticky'; ttlMs: number; duplicateOk: boolean; writeBack: 'later' | 'now' | 'always' };
export const LanePolicy: Record<Lane, Policy> = {
fast: { cache: 'ghost', ttlMs: 60_000, duplicateOk: true, writeBack: 'later' },
proof: { cache: 'network', ttlMs: 0, duplicateOk: false, writeBack: 'now' },
form: { cache: 'sticky', ttlMs: 300_000, duplicateOk: true, writeBack: 'always' },
};
The code isn't special; the value is that policy is explicit and nameable.
Long-form counterpoint: lanes can become a crutch where teams stop fixing correctness and just pick proof lane everywhere.
We had to enforce the opposite: proof lane is expensive; if you pick it for a fast surface, you need a reason and a budget impact note.
We record lane choice into a ledger object and render the ledger revision in dev builds.
It made it obvious when a component was picking its own lane (policy violation).
We treated lane selection as a route-shell responsibility, like any other posture decision.
Once lane selection was in the shell, it stopped being "some hook decision" and became reviewable.
A subtle win: lanes gave us a shared vocabulary in incident reviews.
Instead of arguing about cache internals, we could say "the checkout review track was on fast lane when it should have been proof" and the fix was obvious.
We made lane evidence visible in screenshots and it reduced support back-and-forth a lot.
Long-form: the best part of lanes is it forces you to define "allowed wrongness" explicitly.
Teams always had these rules implicitly (stale ok on feed, not ok on payments). Lanes make the rules explicit and enforceable, which is healthier than hiding them in random staleTime numbers.
We also made lane choice part of route-flow tests: the test asserts data-lane on each track.
It prevented regressions where a refactor moved lane selection into a leaf component.
Form lane felt weird until I saw it stop "lost input" bugs. Then it became non-negotiable.
We treat lane choice like a treaty with budgets: proof lane costs more, so it gets measured and justified.
It keeps teams from defaulting to proof lane for everything.
Short-ish: our form lane rule is "never lose input, but always show freshness".
If the user is editing and the data is stale, we show a clear evidence chip and defer proof refresh until submit/confirm.
We had to decide how lanes interact with offline mode. Form lane was the only safe one offline.
Fast lane offline is dangerous because it can look like up-to-date truth when it isn't.
If you adopt lanes, keep the vocabulary tiny and make the shell the only chooser.
Long-form counterpoint: lanes can hide a bad data model where the app can't reconcile sources cleanly.
We had to invest in better identity keys and better normalization before lanes paid off. Lanes made the problems visible, but they didn't magically solve them.
Takeaway: lanes are caching with a public policy surface.
Once policy is public (lane names + evidence), caching stops being accidental behavior and becomes something teams can reason about.