Repository navigation
pendingComponent animation restarts during hydration of an ssr: false route
#8482
Replies: 1 comment
|
Your reading of Here's the chain for an Hydration matches, so node A survives. Then the store snapshot flips, So the transition is structural, and nothing in the router tries to preserve the DOM instance across it — with pending UI living in two tree positions, React couldn't reuse the node anyway. Both instances are intended; that the second one is a fresh mount is a consequence nobody has designed around. Worth knowing: this area is actively being reworked. Issue #8180 (a Suspense boundary invalidated before it finishes hydrating, React error #421) has an open PR, #8185, moving the match-store subscription below the What you can do without building a loading-state coordinator: The cleanest option uses a hook the router already exports. function AnimatedSpinner() {
const hydrated = useHydrated()
if (!hydrated) return <SpinnerFrame0 /> // same SVG, no animation — goes into server HTML
return <SpinnerAnimated />
}The animated element then mounts exactly once (as node B), so there is nothing running to restart — the placeholder just starts moving. I ran this variant through the same harness: static markup in the server HTML, the animated instance visible after the flip. If you want motion from first paint, make the restart invisible instead: keep a module-scope start timestamp and give each mount a negative offset, Things that won't help, some of them tempting:
If you want the router itself to preserve the instance across the swap, that's a structural change (one pending slot instead of two, or a boundary arrangement where the fallback survives) — #8185 is a reasonable place to raise it, or a fresh issue, since nothing tracks it today. |
Uh oh!
There was an error while loading. Please reload this page.
Is it expected for an
ssr: falseroute's pending UI to switch to a different DOM instance during initial hydration, while the same loader is still pending?I have an SSR-enabled parent layout and a child route whose loader runs on the client. The child awaits its data and uses an animated SVG as its
pendingComponent. On a direct page load, the indicator appears in the server HTML, then its animation restarts during hydration, before the data arrives.The relevant configuration, simplified from the application, is:
AnimatedSpinneruses an ordinary repeating CSS animation. The intended behavior is one continuous pending animation until the loader resolves.In a production-build browser capture, with the data request delayed, DOM-identity tracing showed:
There was one data request. The SSR parent remained visible. More precisely, the first indicator was hidden when the second appeared; it was not immediately removed from the DOM.
Looking at
MatchView, the same pending element is supplied to both the outer Suspense boundary and the innerClientOnlyfallback. My understanding is that hydration switchesClientOnlytoMatchInner, which suspends on the pending loader, causing the outer boundary to display a new fallback instance. This seems consistent with the observed animation restart.Is that transition intentional? Could the router preserve the visible pending instance across it, or is there a supported configuration for keeping the animation continuous without implementing a separate loading-state coordinator in the application?
Observed with:
@tanstack/react-router: 1.170.34@tanstack/react-start: 1.168.51The source link above is a current upstream snapshot; the runtime observation is from the versions listed. I have not published a standalone reproduction yet, so I am starting this as a discussion about the intended behavior.
All reactions