-
-
Notifications
You must be signed in to change notification settings - Fork 1.6k
Solid: ssr='data-only' + pendingComponent element causes hydration mismatch / template is not a function #7085
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/solid-start@1.166.18@tanstack/solid-router@1.167.5
Broken:
@tanstack/solid-start@1.167.0@tanstack/solid-router@1.168.0
Still broken on latest tested:
@tanstack/solid-start@1.167.15@tanstack/solid-router@1.168.9
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/solid-router@1.167.5), 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/solid-router@1.168.0, 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.