Route boundaries
Mark each page so SSGOI can tell which one just left and which one arrived.
A route boundary is the real DOM element that owns a changing page. The framework creates and removes it; SSGOI observes that lifetime and animates the region. Router helpers are optional conveniences around this pattern, so a router does not need a dedicated SSGOI package.
// React: use the committed pathname from your router.
<div key={pathname} data-ssgoi-transition={pathname}>
{children}
</div>Place the boundary inside one provider. Get the pathname from the router in the same render as its children; a mount-time window.location read or an effect-delayed key can miss the outgoing page. Ready-made helpers are listed within each framework guide.
What each value decides
They start out identical, but they answer different questions and are read by different things.
| Value | What it decides | Who reads it |
|---|---|---|
key | Whether this DOM node survives the navigation. SSGOI reacts to the framework destroying and rebuilding the routed node. | Your framework |
data-ssgoi-transition | Which logical route the node represents, so the leaving page can be paired with the arriving one and matched against your rules. | SSGOI config |
Build a boundary for your router
The React wrapper only needs the route id and the lifetime of its DOM root.
import type { HTMLAttributes, ReactNode } from "react";
type Props = HTMLAttributes<HTMLDivElement> & {
children: ReactNode;
pathname: string;
routeKey?: string | number;
};
export function RouteBoundary({ children, pathname, routeKey, ...props }: Props) {
return (
<div {...props} key={routeKey ?? pathname} data-ssgoi-transition={pathname}>
{children}
</div>
);
}Feed this wrapper the active route's committed pathname and its children. A stable routeKey preserves a shell; changing page content still needs its own boundary. Do not put a browser-URL key around a background slot that an intercepted route should preserve.
Other frameworks use their own lifetime primitives: Vue uses keyed elements, Solid uses a keyed Show, and SvelteKit detaches the outgoing snippet in onNavigate before its content changes. Qwik keeps the marker and key on the page's own root; Angular can recreate an embedded template. The framework guides show those connections and their limits.
When they stop being the same
A persistent layout gives several routes one shared mount while their route ids keep changing.
A bottom-nav app is the usual case. Tab → tab should remount only the page content; tab → detail should remount the whole shell and take the nav with it. That needs an outer shell boundary and an inner content boundary.
When both change in the same navigation, the outer one owns the transition. The outer node is the one the framework removes, so the inner boundary is torn down inside it — there is no separate node left for it to animate.
<Ssgoi> provider. Add boundaries to express ownership; never nest providers.