The home page of this site has a strip of live numbers: whether the sites we run are up and how fast the fastest one answers, deploys this week, the air in Sarajevo from BosniaAir, the price of Bitcoin. They are small, but they make a claim: this is happening now. A number that says "now" and is hours old is a lie, and a strip that is blank half the time looks broken.
I wanted a page that is still static and fast, with numbers I would not be embarrassed by. This is how it ended up working, and the rules are the useful part.
The problem
The numbers come from several places. Some come from public APIs, one from Vercel's REST API with a read-only token, one from a plain GET of each site we run. Any of them can be slow, down or rate limited at the moment a visitor arrives.
Meanwhile the pages sit behind a CDN. A cached page is fast because it was built earlier. That means the numbers baked into it were true at build time, and a page served after a quiet spell can carry values that are hours old.
So there are two ways to be wrong. A source fails, or the page is old. Fixing one does not fix the other.
What I did
One endpoint, `/api/signals`, collects everything at request time. It is a dynamic route, never a snapshot. Each source gets a deadline of 8 seconds. A source that is late counts as failed for that round, and the answer goes out without it instead of waiting for it.
A failed source serves its last good value. The server keeps the last good value of each signal in memory, and it starts from a small JSON file committed to the repo, so a cold instance still has something. A signal is only replaced when a fresh one arrives.
The CDN gets honest cache headers. An answer where every source worked is cached for 60 seconds, with 300 seconds of stale-while-revalidate. An answer where a source failed is cached for 15 seconds only, so the next visitor triggers another try soon. An answer with nothing current in it is never cached at all.
The page renders the layout's cached value, and the browser corrects it. The server renders the strip from a value cached for 5 minutes, which is part of the static shell. Then the browser asks /api/signals on load and every 60 seconds and merges the answer in, one signal at a time. If the answer fails or is older than 15 minutes, it asks once more past the CDN with a throwaway query string.
The rule that matters
All of that machinery serves one decision. Every signal carries the time it was last checked, and the reader sorts it into three states:
/** Younger than this, a signal is "fresh". */
export const FRESH_MS = 15 * 60_000;
/** Older than this, a signal is hidden. Until then it is shown as "stale". */
export const STALE_MS = 24 * 60 * 60_000;- Under 15 minutes old: fresh, shown as it is.
- Between 15 minutes and 24 hours: stale. It is still shown, with its time ("updated 16 Oct, 22:24") in the strip's head, so nobody mistakes it for live.
- Older than 24 hours: hidden. Nothing is better than something that old pretending to be current.
Only when every signal is gone, after the browser has asked, does the strip turn into one muted "offline" line.
A value that changes pulses for 700 ms, so you can see that something actually moved.
The part that is easy to get wrong
The tempting version trusts whatever the layout rendered. The home page is static, and a cached shell is the thing most likely to be old, so the strip would be wrong in exactly the place where people look first. The browser refresh and the stale label exist for that. The page stays static and fast, and the numbers are corrected within a moment of loading.
The same goes for failure. When a source fails, the easy move is to drop its number. Then the strip jumps around between visits, with items appearing and disappearing. Serving the last good value, clearly marked once it is old, is calmer than hiding it and still honest.
The lesson
Caching is a promise about how wrong something is allowed to be. Decide that number first. Here it is 15 minutes to count as fresh and a day before a number goes away. Then make the interface say which case you are in, because a stale value shown as live is the one outcome the visitor cannot spot from the outside.
The collection logic and the reader that sorts values by age are both unit tested, with fetch passed in so no test needs the network. That keeps the three states from drifting apart when someone, me included, edits one of them later.