Skip to content

[Solid] ssr:false route with pendingComponent fails during dev hydration #7283

@ljho01

Description

@ljho01

Summary

ssr: false routes with a pendingComponent still fail during dev hydration in the Solid Start selective SSR example.

This looks closely related to #7085 / #7266. #7266 fixed the ssr: 'data-only' case, but the same class of failure still reproduces for ssr: false in dev mode.

Reproduction

MRE branch:

https://github.com/ljho01/router/tree/codex/solid-ssr-false-pending-mre

Relevant added route:

https://github.com/ljho01/router/blob/codex/solid-ssr-false-pending-mre/e2e/solid-start/selective-ssr/src/routes/ssr-false-pending-component.tsx

The route is intentionally minimal:

export const Route = createFileRoute('/ssr-false-pending-component')({
  ssr: false,
  loader: async () => {
    await new Promise((resolve) => setTimeout(resolve, 1500))
    return { loadedAt: new Date().toISOString() }
  },
  pendingComponent: () => (
    <div data-testid="ssr-false-pending-component-pending" />
  ),
  component: SsrFalsePendingComponentRoute,
})

I tested against upstream main at d6decca41807e9ca28279e2db6640e7a8bdc1229.

Steps

pnpm install --frozen-lockfile
VITE_SERVER_PORT=57731 pnpm --dir e2e/solid-start/selective-ssr dev:e2e --host localhost --port 57731 --strictPort

Then open:

http://localhost:57731/ssr-false-pending-component

Actual behavior

The route never reaches the loaded component. It stays around the root HTML and the browser console reports:

Warning: The following error wasn't caught by any route! At the very least, consider setting an 'errorComponent' in your RootRoute!
Warning: template is not a function

In my Playwright check, the pending node was present but the ready label timed out after 10s:

{
  "pending": 1,
  "readyText": "TIMEOUT: locator.textContent: Timeout 10000ms exceeded.",
  "bodyText": "Selective SSR E2E Test\nHome\nroot\nssr: \"undefined\"\nexpected data location execution:\nloader: server\ncontext: server"
}

The existing ssr: 'data-only' route in the same example works correctly under the same dev server.

Expected behavior

The ssr: false route should behave like the fixed ssr: 'data-only' case: hydrate without adding a mismatched outer pending fallback and eventually render the loaded component with no browser errors.

Extra notes

The production e2e target passes, so this appears to be dev-mode specific:

CI=1 NX_DAEMON=false pnpm nx run tanstack-solid-start-e2e-selective-ssr:test:e2e --outputStyle=stream --skipRemoteCache -- tests/pending-component-hydration.spec.ts
# 2 passed

The suspicious branch is in packages/solid-router/src/Match.tsx:

const resolvedNoSsr =
  currentMatchState().ssr === false ||
  currentMatchState().ssr === 'data-only'

const shouldSkipSuspenseFallback =
  (isServer ?? router.isServer)
    ? resolvedNoSsr
    : currentMatchState().ssr === 'data-only'

Locally, changing the client-side condition to also use resolvedNoSsr made both the data-only and ssr:false MRE routes pass in dev:

const shouldSkipSuspenseFallback = resolvedNoSsr

I did not include that fix in the MRE branch; the branch is left failing for reproduction.

Environment

  • OS: macOS
  • Node: v22.17.1
  • pnpm: 10.28.0
  • Browser check: Playwright Chromium

Metadata

Metadata

Assignees

No one assigned

    Type

    No type

    Projects

    No projects

    Milestone

    No milestone

    Relationships

    None yet

    Development

    No branches or pull requests

    Issue actions