Skip to content

server crashes when unhandled exception in server function or middleware #5266

@kkane

Description

@kkane

Which project does this relate to?

Start

Describe the bug

Server crashes when there are exceptions in server functions in loader, either middleware or handler itself.

Your Example Website or App

https://github.com/TanStack/router/tree/main/examples/react/start-basic

Steps to Reproduce the Bug or Issue

Add middleware.ts:

import { createMiddleware, json } from "@tanstack/react-start";

export const authMiddleware = createMiddleware({ type: "function" }).server(
  async ({ next, context }) => {
    throw json({ message: "Unauthorized: missing token" }, { status: 401 });
    return next();
  }
);

Replace deferred.tsx with following:

import { Await, createFileRoute } from "@tanstack/react-router";
import { createServerFn } from "@tanstack/react-start";
import { Suspense, useState } from "react";
import { authMiddleware } from "~/middleware";

const personServerFn = createServerFn({ method: "GET" })
  .middleware([authMiddleware])
  .inputValidator((d: string) => d)
  .handler(({ data: name }) => {
    return { name, randomNumber: Math.floor(Math.random() * 100) };
  });

const slowServerFn = createServerFn({ method: "GET" })
  .inputValidator((d: string) => d)
  .handler(async ({ data: name }) => {
    await new Promise((r) => setTimeout(r, 1000));
    return { name, randomNumber: Math.floor(Math.random() * 100) };
  });

export const Route = createFileRoute("/deferred")({
  loader: async () => {
    return {
      deferredStuff: new Promise<string>((r) =>
        setTimeout(() => r("Hello deferred!"), 2000)
      ),
      deferredPerson: slowServerFn({ data: "Tanner Linsley" }),
      person: await personServerFn({ data: "John Doe" }),
    };
  },
  component: Deferred,
});

function Deferred() {
  const [count, setCount] = useState(0);
  const { deferredStuff, deferredPerson, person } = Route.useLoaderData();

  return (
    <div className="p-2">
      <div data-testid="regular-person">
        {person.name} - {person.randomNumber}
      </div>
      <Suspense fallback={<div>Loading person...</div>}>
        <Await
          promise={deferredPerson}
          children={(data) => (
            <div data-testid="deferred-person">
              {data.name} - {data.randomNumber}
            </div>
          )}
        />
      </Suspense>
      <Suspense fallback={<div>Loading stuff...</div>}>
        <Await
          promise={deferredStuff}
          children={(data) => <h3 data-testid="deferred-stuff">{data}</h3>}
        />
      </Suspense>
      <div>Count: {count}</div>
      <div>
        <button onClick={() => setCount(count + 1)}>Increment</button>
      </div>
    </div>
  );
}

then go directly to http://localhost:3000/deferred (page needs to be initially loaded from server at that path).
Server crashes with:

Error reading routerStream: g [Error]: The value [object Response] of type "object" cannot be parsed/serialized.

Another way to make it crash:

const personServerFn = createServerFn({ method: "GET" })
  .inputValidator((d: string) => d)
  .handler(({ data: name }) => {
    throw new Error("test");
    return { name, randomNumber: Math.floor(Math.random() * 100) };
  });

and then remove await in loader:

person: personServerFn({ data: "John Doe" }),

Server crashes with exception:

    throw new Error("test");
          ^
Error: test

Expected behavior

Expect server to never crash due to user's code.

Platform

  • Router / Start Version:
    "@tanstack/react-start": "^1.132.13",
    "@tanstack/react-router": "^1.132.7",

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