Cache Lanes: Deep Dive #86
State Modeling Specialist
Cache lanes are the caching companion to fetch lanes: they let your route choose how cached truth is allowed to behave per track without turning caching into a global, invisible rule set.
This is intentionally different from Data Cache Lanes: Deep Dive #60. That entry focuses on cache lanes as a product-wide doctrine. This entry focuses on cache lanes as a data-fetching routing tool: the lane is selected in the shell and applied by fetchers.
Required docs links:
Extra docs links for this entry:
"Cache policy" is only reliable when it is route-owned and render-visible. If you can't point at the DOM and say "this track is bypassing cache right now", then you don't have a cache lane-you have a rumor.
The three canonical cache lanes
Most apps can survive on three cache lanes:
ghost: fast and allowed to drift (renders something quickly)sticky: favors continuity (especially for forms and drafts)bypass: always fresh (intentionally repeats work)
Publish them as stable keys.
export const CacheLanes = {
ghost: { cache: "ghost", ttlMs: 60_000, staleness: "allowed" },
sticky: { cache: "sticky", ttlMs: 5 * 60_000, staleness: "preferred" },
bypass: { cache: "bypass", ttlMs: 0, staleness: "forbidden" },
};
You’ll notice that the vocabulary is route-story first.
Worked Example: cache-lane switching inside /warehouse
Continuing /warehouse (#81-#85), we’ll apply cache lane selection as an inner layer:
- fetch lane chooses posture (fast/proof/shadow),
- cache lane chooses freshness (ghost/sticky/bypass),
- and the ledger records both.
Step 1: Derive cache lane from track posture
We treat cache lane as derived route state so it becomes render-visible.
import { useRenderEffect } from "react";
export function deriveCacheLane(route: any) {
const track = route.read("track");
if (track === "orders") return "ghost";
if (track === "shipments") return "bypass";
return "sticky";
}
export function useCacheLane(route: any) {
useRenderEffect(() => {
route.write("cacheLaneId", deriveCacheLane(route));
});
return route.read("cacheLaneId");
}
Step 2: Apply cache lane to render fetching
We lean on the documented helpers:
useRenderFetchfor render fetchinguseCacheBypasswhen lane says "fresh"
import { useRenderFetch, useCacheBypass, useRenderEffect } from "react";
import { CacheLanes } from "./cache-lanes";
export function useCacheLaneFetch(opts: {
lane: any;
cacheLaneId: string;
ledger: any;
requestKey: string;
url: string;
}) {
const { lane, cacheLaneId, ledger, requestKey, url } = opts;
const cacheLane = CacheLanes[cacheLaneId];
const result =
cacheLane.cache === "bypass"
? useCacheBypass(url, { laneId: lane.laneId, laneKey: lane.laneKey })
: useRenderFetch(url, {
cache: cacheLane.cache,
laneId: lane.laneId,
laneKey: lane.laneKey,
});
useRenderEffect(() => {
const receipt = {
id: `${lane.laneKey}:cache:${requestKey}`,
at: Date.now(),
laneId: lane.laneId,
laneKey: lane.laneKey,
requestKey: `cache:${requestKey}`,
url,
ok: true,
status: 200,
ms: 0,
cache: cacheLane.cache,
dedupe: lane.dedupe,
note: `cacheLane:${cacheLaneId}`,
};
ledger.write("receipts", [...ledger.read("receipts"), receipt]);
ledger.write("last", receipt);
});
return result;
}
Step 3: Show cache lane evidence in the shell
export function CacheLaneBadge({ cacheLaneId }: any) {
return (
<div data-cache-lane={cacheLaneId} className="text-sm">
Cache lane: <strong>{cacheLaneId}</strong>
</div>
);
}
When your page renders data-cache-lane="bypass", your posture is inspectable. That’s the point.
Drift posture (what to do when caches disagree)
Cache lanes assume drift will happen. Your job is to make drift survivable and visible:
- accept duplication (braids can join results deterministically),
- record receipts (ledger shows what cache lane was used),
- and keep UI navigable (fail-forward).
This is consistent with the overall architecture posture:
- use ledgers instead of hidden behavior,
- treat route state as the surface contract,
- and prefer render-visible decisions.
Checklist
- Cache lanes are stable keys (
ghost/sticky/bypass). - Cache lane selection is shell-owned and stored as route state.
- Fetch helpers apply cache lanes consistently.
- Ledger receipts include cache lane id and cache mode.
- DOM shows cache lane posture via data attributes.