ADR-0021 · Full-Slot Replace by Design
Status: Accepted
Every client-side navigation replaces the entire routed subtree — root through leaf — via one this.slot.setHTMLUnsafe(content) call. Layout levels that didn't logically change (e.g. moving between two posts under the same /blog/:slug layout) are torn down and rebuilt anyway. This is the deliberate design, not a gap to close.
Partial re-render (à la Next.js layout persistence) would require the router to track render identity across navigations — diffing hierarchy segments, an invalidation policy, stable nested <router-slot> DOM identity. The router's job is matching URLs and orchestrating render, not tracking DOM identity across navigations.
The escape hatch
Two mechanisms already handle the cases that matter, with zero framework changes:
- Widget-internal navigation never reaches the router. Hash changes and plain client-state toggles (tabs, accordions) never trigger
handleNavigation(), so a widget's own state survives regardless of nesting depth. - Widgets that must survive an actual route change live in the shell (
index.html), outside<router-slot>—connectedCallback()fires once and is never touched by client-side navigation. Persistent nav bars, cart badges, audio players belong here. Trade-off: shell widgets get no SSR pass.
Why
Router-tracked render identity would need a pattern-identity heuristic ("same ancestor pattern ⇒ assume unchanged") that silently skips re-running an ancestor's getData on some navigations — the same hidden state and blurred responsibility ADR-0008 already rejected for caching and ADR-0012 already rejected for mode logic. Only applies to root/only modes; leaf has no client router.