Transitions run off route meta and come in three tiers, site, layout, and page. Each tier is just a boolean load-state that flips false while its slice of the view is changing and back to true once the new content paints. A tier only reacts when the thing it tracks actually changes, so navigating between two pages under the same layout transitions the page and leaves the layout sitting still.
Site is the top tier and maps to booting the app. It stays down until the critical startup work resolves, the auth check, the settings load, and the first locale files, and it is what the loading cover sits behind. Once site is up it normally never comes back down for the rest of the session.
Layout is for the big structural swaps, moving from the auth screens into the logged-in shell, changing which asides are present, toggling header and footer. Page is the lowest tier and flips on every route name change, so once a layout is settled you are just paging through content inside it.
Layout and page are mechanically identical. Both are toggle switches watching route meta, and the only difference is what they watch. Layout compares a content.layout value pulled from meta, page compares the route name. So you reach for layout when a navigation should hold the outer chrome and swap only the inner region, and page for the normal case where the content area is what changes.
On each navigation the content guard walks the matched records and collects their content meta into a single site/layout/page shape, once for the route you are leaving and once for the one you are entering. A tier is marked loaded only when its value is unchanged across the two. Anything that changed flips to false so its transition can play, and an afterEach flips everything back to true a paint tick later once the new view is up.
That one tick delay is deliberate. It lets the transition start from the loading state instead of snapping straight to the loaded content.
SiteTransition, LayoutTransition, and PageTransition are thin wrappers over those load-states. Drop one around a region and it gates its reveal on its tier, holding the loading state until ready and fading the content in after. They are conveniences. The state is the real system and the components just read it, so nothing stops you reading a tier directly when a screen needs something the wrappers do not cover.
Layout and page each also take an isLoading prop. That is the hook for a specific layout or page to fold its own async work, a query, a preloaded model, whatever the screen needs, on top of the built-in tier. While isLoading is true the region stays in its loading state even though the route itself has already settled.
Most screens need a page transition and nothing else, a plain wrapper that flips as you move between routes. Add a layout transition one level up when a section swap should hold the chrome, an aside change, a different header or footer, moving between whole sections like auth and the user area. When a page has tabs or a breadcrumb or a title that should persist while the body drills down, transition just the page region and update those outer parts directly.
The payoff is that only the region actually changing animates. No full-page flashes, no per-page spinners, just the content that moved fading in cleanly.
Cover Loading for the site-tier cover and the pre-mount shell it hands off from. Internationalization for how locale files feed the same load-states.