Repository navigation
Description
Description
A hydration regression seems to have been introduced for Solid when using ssr: 'data-only' together with a pendingComponent that returns any DOM element.
I reduced this to a very small MRE. The route shape is essentially:
export const Route = createFileRoute('/mre-data-only')({
ssr: 'data-only',
loader: async () => {
await new Promise((r) => setTimeout(r, 1500))
return 'OK'
},
pendingComponent: () => <div />,
component: () => <div>{Route.useLoaderData()}</div>,
})On initial load / hard refresh of this route:
pendingComponent: () => <div />failspendingComponent: () => <div>PENDING</div>failspendingComponent: () => <p>PENDING</p>failspendingComponent: () => 'PENDING'workspendingComponent: () => nullworks
So the minimal failing case appears to be: pendingComponent returns a single DOM element.
Actual behavior
On initial load or refresh, the app hits hydration/runtime errors such as:
Hydration Mismatch. Unable to find DOM nodes for hydration keyTypeError: template is not a function
and the page can remain stuck in the pending state.
Expected behavior
For ssr: 'data-only', the route should hydrate cleanly and transition from pendingComponent to the loaded route component.
Version boundary
Working:
@tanstack/[email protected]@tanstack/[email protected]
Broken:
@tanstack/[email protected]@tanstack/[email protected]
Still broken on latest tested:
@tanstack/[email protected]@tanstack/[email protected]
Suspected regression
The behavior change appears to line up with the Match.tsx refactor in commit 0545239 (refactor: signal based reactivity).
In the previously working version (@tanstack/[email protected]), the outer suspense fallback was effectively disabled for resolvedNoSsr routes on both server and client:
(isServer ?? router.isServer) || resolvedNoSsr
? undefined
: <Dynamic component={resolvePendingComponent()} />In @tanstack/[email protected], this became:
(isServer ?? router.isServer) && resolvedNoSsr
? undefined
: <Dynamic component={resolvePendingComponent()} />For ssr: 'data-only', resolvedNoSsr is true, so this changes client behavior:
- old behavior: outer suspense fallback is also disabled on the client
- new behavior: outer suspense fallback is enabled on the client, but still disabled on the server
That seems to create the hydration mismatch.
Extra verification
I also monkey-patched the latest installed @tanstack/solid-router locally and changed only that condition back from && to ||.
With that one-line change:
- hydration succeeded
template is not a functiondisappearedpendingComponent: () => <div />worked again- route completed from pending to
OK
Related
This may be related to #6824, but this MRE seems narrower: ssr: 'data-only' + pendingComponent returning any DOM element is enough to trigger it.
Activity
ljho01 commentedon Apr 1, 2026
Attached a regression test PR for this: #7086
The added test passes on current
main.If the
packages/solid-router/src/Match.tsxfallback condition is changed from(isServer ?? router.isServer) || resolvedNoSsrback to(isServer ?? router.isServer) && resolvedNoSsr, the same dev-mode test fails and the route never reaches the loaded UI.