Skip to content

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

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

Activity

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

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