Loading states
A page arriving shows its own shape, never the coin. The chrome renders immediately; only data regions get a silhouette.
The rule
Left: what a page load must look like — real title, real filters, a silhouette only where data will land. Right: the coin, which shares no geometry with the page behind it and makes the arrival read as a restart.
Chrome never waits
Title, tabs and filters are knowable from the URL before any request resolves. They render for real and stay mounted, so you can start filtering before the results arrive — and nothing shifts when they do.
One wait, not two
The route fallback covers the server navigation; the page then mounts and its query starts. Both render the same silhouette component, so the hand-off is invisible — the swap to real content is the only visual transition.
SkeletonReveal — nothing flashes
Held invisible for 150ms, then faded in over 200ms. A navigation that resolves faster than the delay never shows a silhouette at all; the global top progress bar already covers those. Pure CSS, so it works in a server-rendered loading.tsx. Reduced motion renders it immediately.
Where the coin still belongs
Operations the user asked for and is waiting on — a generation, an extraction, a competitor scan. There is no silhouette to draw because the shape of the result isn't known yet, and the wait is long enough that copy helps.
Skeleton to content: a crossfade
Once the page has mounted, every later load (a filter change, a refetch) hands off through Crossfade: the silhouette dissolves into the rows over 150 ms instead of being swapped. The route loading.tsx is a separate tree, so the first arrival still uses RouteLoading.
Poolside 1:1
$1,240 · 96 conv.
15 s testimonial
$980 · 71 conv.
Three colours
$610 · 40 conv.
