How it works
The page you just left is kept alive and animated out while the new one mounts in place.
SSGOI never reads your router. It watches for elements marked with data-ssgoi-transitionappearing and disappearing from the DOM. A navigation changes a boundary's key, and SSGOI reacts to the framework destroying and rebuilding the routed node.

- Navigation changes the boundary key, so the framework unmounts the old page.
- SSGOI keeps that same node — never a clone — and puts it back with
position: absolute, offset so it does not visually jump. - The new page mounts in its normal place.
- The chosen transition animates both, either at once or one after the other.
- When the animation ends, the old node is removed for good.
<Activity> or Next's cacheComponents, the old page is hidden with display: none instead of being removed. SSGOI watches for that too: it reveals the same node in place, animates it, then hides it again — and the page keeps its state, so scroll position, inputs and media survive.Everything runs through the Web Animations API: spring motion is simulated up front, turned into keyframes and handed to the browser, so there is no per-frame JavaScript once a transition is playing.
Why the wrapper needs relative and z-0
Both classes exist because of that temporarily reinserted page.
relative is the load-bearing one. An absolutely positioned element is placed against its nearest positioned ancestor; if your shell has none, the leaving page is measured against the document and lands in the wrong spot.
z-0 is about containment. Transitions stack their layers at z-index 0, 1 and 2 — never negative, so the leaving page cannot slip behind your background — but a few expressive effects go much higher. A stacking context on the wrapper keeps all of that inside your shell instead of over a fixed header.
If a transition looks broken, check the layout shell before anything else.
Which boundary owns the transition
If an outer boundary and one nested inside it both change in the same update, the outer one owns the transition. A child owns it only while its parent stays mounted.
The reason is mechanical: the outer node is the one the framework removes, and the inner boundary goes with it. SSGOI still cleans the inner one up, but there is no separately removed node left to animate, so the outer boundary's transition is the only one that runs.
tab → tab
app-shell stays ── main-content changes ── BottomNav stays
tab → detail
app-shell changes ── nested change is absorbed ── BottomNav leaves
project child → child
project key stays ── no marked child leaves ── content swaps immediatelyOne consequence worth knowing: changing only data-ssgoi-transition on a node that stays mounted animates nothing. The key has to change. Persistent layouts has the full route tree, dynamic keys, and intercepted modals.