Skip to content

Throwing error in route loader with SSR streaming crashes dev server #6200

@yanghuidong

Description

@yanghuidong

Which project does this relate to?

Start

Describe the bug

When streaming loader data during SSR, which is activated by neither awaiting nor returning the fetched data, an error thrown by the fetcher (e.g. Zod validation error) will crash the Vite dev server (with Nitro v3 plugin, Cloudflare Workers preset). Now if we don't enable streaming, then the error is properly caught by the default error component of the router, and the dev server doesn't crash.

Your Example Website or App

Not available yet, but very minimal code to reproduce locally, see below

Steps to Reproduce the Bug or Issue

Here's a minimal example to reproduce the scenario:

const ARTICLES_PER_PAGE = 0; // Invalid input according to Zod schema

export const Route = createFileRoute('/')({
	loader: async ({ context }) => {
		const data = context.queryClient.ensureQueryData(
			articlesQueryOptions(0, ARTICLES_PER_PAGE),
		);
		// return data; // not returning gives us streaming behavior according docs
	},
	component: HomePage,
});

When we don't return (nor await) the data, opening the page immediately crashed the dev server. The server console prints the following:

[4:39:14 PM]  ERROR  Worker error: [
  {
    "origin": "number",
    "code": "too_small",
    "minimum": 1,
    "inclusive": true,
    "path": [
      "limit"
    ],
    "message": "Too small: expected number to be >=1"
  }
]

    at execValidator (node_modules/.pnpm/@[email protected]/node_modules/@tanstack/start-client-core/src/createServerFn.ts:673:13)
    at next (node_modules/.pnpm/@[email protected]/node_modules/@tanstack/start-client-core/src/createServerFn.ts:196:24)
    at executeMiddleware (node_modules/.pnpm/@[email protected]/node_modules/@tanstack/start-client-core/src/createServerFn.ts:233:10)
    at AsyncFunction.listPublicArticlesFn.__executeServer (node_modules/.pnpm/@[email protected]/node_modules/@tanstack/start-client-core/src/createServerFn.ts:147:20)
    at src/server/query.ts:238:10
    at Object.fn [as extractedFn] (node_modules/.pnpm/@[email protected][email protected][email protected]_/node_modules/@tanstack/start-server-core/src/createSsrRpc.ts:15:12)
    at client (node_modules/.pnpm/@[email protected]/node_modules/@tanstack/start-client-core/src/createServerFn.ts:705:21)
    at queryFn (src/tsq.ts:36:11)

node:internal/deps/undici/undici:13510
      Error.captureStackTrace(err);
            ^

TypeError: fetch failed
    at node:internal/deps/undici/undici:13510:13
    at async nitroDevMiddleware (node_modules/.pnpm/[email protected]_@[email protected][email protected]_@[email protected]_ky_639768f3d07319a3d1fe53facc673f3e/node_modules/nitro/dist/_build/vite.plugin.mjs:348:18) {
  [cause]: SocketError: other side closed
      at Socket.onHttpSocketEnd (node_modules/.pnpm/[email protected]/node_modules/undici/lib/dispatcher/client-h1.js:904:22)
      at Socket.emit (node:events:530:35)
      at endReadableNT (node:internal/streams/readable:1698:12)
      at process.processTicksAndRejections (node:internal/process/task_queues:90:21) {
    code: 'UND_ERR_SOCKET',
    socket: {
      localAddress: undefined,
      localPort: undefined,
      remoteAddress: undefined,
      remotePort: undefined,
      remoteFamily: undefined,
      timeout: undefined,
      bytesWritten: 791,
      bytesRead: 0
    }
  }
}

Node.js v22.18.0
 ELIFECYCLE  Command failed with exit code 1.

Now, as soon as we switch to returning the data, the dev server crash stops, and the browser renders the default ErrorComponent defined on the router.

Expected behavior

Ideally, turning on SSR streaming wouldn't make the (dev) server less robust than in non-streaming mode.

Screenshots or Videos

No response

Platform

  • Router / Start Version: 1.142.13
  • OS: macOS
  • Browser: Chrome
  • Browser Version: 144.0
  • Bundler: Vite
  • Bundler Version: 7.3.0

Additional context

Using "nitro": "3.0.1-alpha.1" with preset: 'cloudflare_module'

Relevant quote from docs:

You can also prefetch with fetchQuery or ensureQueryData in a loader without consuming the data in a component. If you return the promise directly from the loader, it will be awaited and thus block the SSR request until the query finishes. If you don't await the promise nor return it, the query will be started on the server and will be streamed to the client without blocking the SSR request.

Metadata

Metadata

Assignees

No one assigned

    Labels

    Type

    No type

    Projects

    No projects

    Milestone

    No milestone

    Relationships

    None yet

    Development

    No branches or pull requests

    Issue actions