IDB Engine & TTL
How Flux decides what to keep on disk, for how long, and what actually happens the instant that time runs out. This is Layer 1 of the resilience stack — the reason a returning visitor's app paints instantly instead of waiting on the network.
A TTL in Flux is not an expiry timer that clears the cache when it fires. It's a freshness marker. When a piece of cached data outlives its TTL, Flux still serves it to your component immediately — instantly, from disk. What happens next is not automatic — it depends on whether this channel has a live realtime connection, which is the part most caching systems get wrong. Read on.
Every registered channel gets a TTL
ttl is a required field on every register() call, and it only ever takes one of three values: 'short', 'medium', or 'long'. You define what those three actually mean, in milliseconds, once — in your createFlux() config — and every registration in your app just picks the bucket that fits.short from 5 minutes to 1, say — is a one-line change in one place, not a search-and-replace across every store you've registered.short for things that change often and where staleness is visible to the user (live metrics, a queue count). medium for moderate churn (a notifications list, a team roster). long for data that rarely changes at all (pricing tables, site configuration, terms pages).TTL expiry does not always mean a refetch
If realtime is connected for this channel, Flux does not fire a background refetch at all — even though the TTL has technically expired. A live connection means any change to this data would already have arrived as a realtime event and been applied. Refetching over HTTP on top of that would just be asking the server the same question realtime has already been answering continuously. Instead, Flux simply refreshes the data's freshness clock, so the TTL countdown restarts without a single network request being made.
If realtime is not connected — no adapter configured, or the connection is still reconnecting — that's when a real HTTP refetch happens, and only for the specific channels that are actually stale, not a full re-fetch of everything.
Without this check, an app with a healthy, active realtime connection would still fire a redundant HTTP request every time a channel's TTL lapsed — even though the cache was already provably correct. Flux treats "realtime is connected and current" as its own form of freshness, separate from the TTL clock, so your backend never gets asked a question it already answered a second ago.
What happens when your app reconnects
When the realtime connection re-establishes, Flux tells it exactly what each channel last knew, so the very first thing that happens on reconnect is a targeted catch-up — not a blind refetch of everything, and not silence either. If anything actually changed while your app was disconnected, those changes stream in and get applied immediately, and the freshness clock updates as part of that. If nothing changed at all, Flux still refreshes the freshness clock to right now, because realtime just positively confirmed the cached data is current — there's no reason to treat it as stale a moment later.
Only if realtime does not come back — no connection at all, or it's still reconnecting after a short grace period — does Flux fall back to a selective HTTP refetch, and even then, only for the channels that are genuinely still stale once that grace period has passed. A channel that reconnects and catches up successfully during that window never triggers a refetch at all.
What happens on every cold read
Fresh. Data is within its TTL. It's dispatched to your store immediately. Nothing else happens.
Stale. Data exists but has outlived its TTL. It's still dispatched to your store immediately — so the UI is never empty — and your registration's onStale callback fires, if you provided one. What happens after that follows the realtime-aware behavior described above, not an automatic refetch.
Missing. Nothing cached yet for this channel. Your store's initial/loading state is left as-is, and the UI waits for the network — same as any app without a cache layer, just for the very first visit only.
Two data shapes, two caching strategies
trackAccessTime: true means an entry the user is actively reading gets its "last accessed" clock refreshed, so it isn't evicted out from under them mid-read even if it's technically the oldest entry by write time.An LRU channel with no maxBytes configured falls back to a sane 10MB default. Whatever you configure — or whatever the default resolves to — is automatically capped at a hard ceiling, so a misconfigured budget can never balloon into a real problem on a visitor's device. Mobile browsers, Safari especially, are noticeably less forgiving about large origin storage usage than desktop, so keeping LRU budgets modest is worth doing deliberately rather than leaving to chance.
Every app's cache is isolated
storagePrefix once, in your engine config, and every IDB entry Flux writes for this app is namespaced under it. This is what lets two independent Flux-powered surfaces — say, a public marketing site and an authenticated dashboard — share a browser origin without their caches ever colliding or overwriting each other.Images are handled separately from data
A single object or bounded array (homepage content, pricing, a team list) → ingestionType: 'SNAPSHOT' or 'COLLECTION_ALL', no cacheStrategy needed.
An unbounded, growing collection (articles, products, user-generated content) → ingestionType: 'LRU' with an explicit maxBytes.
Data that should never trigger a redundant refetch → pair a shorter ttl with a realtime adapter (see core-concepts/sync-anchors) rather than lengthening the TTL — a connected realtime channel already avoids the refetch entirely, regardless of how short its TTL is.
See core-concepts/sync-anchors for how reconnect catch-up decides what changed while you were away, core-concepts/activity-bus for how Flux avoids racing a refetch against an active realtime stream, core-concepts/data-ingestion-strategies for the full breakdown of all four ingestion types, and core-concepts/offline-queue for how writes — not just reads — are made resilient on top of this same local cache.