diff --git a/e2e/react-start/server-functions/src/routeTree.gen.ts b/e2e/react-start/server-functions/src/routeTree.gen.ts index f4c74f657b7..c8fa692dcb7 100644 --- a/e2e/react-start/server-functions/src/routeTree.gen.ts +++ b/e2e/react-start/server-functions/src/routeTree.gen.ts @@ -44,6 +44,7 @@ import { Route as MiddlewareRequestMiddlewareRouteImport } from './routes/middle import { Route as MiddlewareMiddlewareFactoryRouteImport } from './routes/middleware/middleware-factory' import { Route as MiddlewareFunctionMetadataRouteImport } from './routes/middleware/function-metadata' import { Route as MiddlewareClientMiddlewareRouterRouteImport } from './routes/middleware/client-middleware-router' +import { Route as MiddlewareCatchHandlerErrorRouteImport } from './routes/middleware/catch-handler-error' import { Route as CookiesSetRouteImport } from './routes/cookies/set' import { Route as AbortSignalMethodRouteImport } from './routes/abort-signal/$method' import { Route as MiddlewareRedirectWithMiddlewareIndexRouteImport } from './routes/middleware/redirect-with-middleware/index' @@ -231,6 +232,12 @@ const MiddlewareClientMiddlewareRouterRoute = path: '/middleware/client-middleware-router', getParentRoute: () => rootRouteImport, } as any) +const MiddlewareCatchHandlerErrorRoute = + MiddlewareCatchHandlerErrorRouteImport.update({ + id: '/middleware/catch-handler-error', + path: '/middleware/catch-handler-error', + getParentRoute: () => rootRouteImport, + } as any) const CookiesSetRoute = CookiesSetRouteImport.update({ id: '/cookies/set', path: '/cookies/set', @@ -279,6 +286,7 @@ export interface FileRoutesByFullPath { '/submit-post-formdata': typeof SubmitPostFormdataRoute '/abort-signal/$method': typeof AbortSignalMethodRoute '/cookies/set': typeof CookiesSetRoute + '/middleware/catch-handler-error': typeof MiddlewareCatchHandlerErrorRoute '/middleware/client-middleware-router': typeof MiddlewareClientMiddlewareRouterRoute '/middleware/function-metadata': typeof MiddlewareFunctionMetadataRoute '/middleware/middleware-factory': typeof MiddlewareMiddlewareFactoryRoute @@ -321,6 +329,7 @@ export interface FileRoutesByTo { '/submit-post-formdata': typeof SubmitPostFormdataRoute '/abort-signal/$method': typeof AbortSignalMethodRoute '/cookies/set': typeof CookiesSetRoute + '/middleware/catch-handler-error': typeof MiddlewareCatchHandlerErrorRoute '/middleware/client-middleware-router': typeof MiddlewareClientMiddlewareRouterRoute '/middleware/function-metadata': typeof MiddlewareFunctionMetadataRoute '/middleware/middleware-factory': typeof MiddlewareMiddlewareFactoryRoute @@ -364,6 +373,7 @@ export interface FileRoutesById { '/submit-post-formdata': typeof SubmitPostFormdataRoute '/abort-signal/$method': typeof AbortSignalMethodRoute '/cookies/set': typeof CookiesSetRoute + '/middleware/catch-handler-error': typeof MiddlewareCatchHandlerErrorRoute '/middleware/client-middleware-router': typeof MiddlewareClientMiddlewareRouterRoute '/middleware/function-metadata': typeof MiddlewareFunctionMetadataRoute '/middleware/middleware-factory': typeof MiddlewareMiddlewareFactoryRoute @@ -408,6 +418,7 @@ export interface FileRouteTypes { | '/submit-post-formdata' | '/abort-signal/$method' | '/cookies/set' + | '/middleware/catch-handler-error' | '/middleware/client-middleware-router' | '/middleware/function-metadata' | '/middleware/middleware-factory' @@ -450,6 +461,7 @@ export interface FileRouteTypes { | '/submit-post-formdata' | '/abort-signal/$method' | '/cookies/set' + | '/middleware/catch-handler-error' | '/middleware/client-middleware-router' | '/middleware/function-metadata' | '/middleware/middleware-factory' @@ -492,6 +504,7 @@ export interface FileRouteTypes { | '/submit-post-formdata' | '/abort-signal/$method' | '/cookies/set' + | '/middleware/catch-handler-error' | '/middleware/client-middleware-router' | '/middleware/function-metadata' | '/middleware/middleware-factory' @@ -535,6 +548,7 @@ export interface RootRouteChildren { SubmitPostFormdataRoute: typeof SubmitPostFormdataRoute AbortSignalMethodRoute: typeof AbortSignalMethodRoute CookiesSetRoute: typeof CookiesSetRoute + MiddlewareCatchHandlerErrorRoute: typeof MiddlewareCatchHandlerErrorRoute MiddlewareClientMiddlewareRouterRoute: typeof MiddlewareClientMiddlewareRouterRoute MiddlewareFunctionMetadataRoute: typeof MiddlewareFunctionMetadataRoute MiddlewareMiddlewareFactoryRoute: typeof MiddlewareMiddlewareFactoryRoute @@ -806,6 +820,13 @@ declare module '@tanstack/react-router' { preLoaderRoute: typeof MiddlewareClientMiddlewareRouterRouteImport parentRoute: typeof rootRouteImport } + '/middleware/catch-handler-error': { + id: '/middleware/catch-handler-error' + path: '/middleware/catch-handler-error' + fullPath: '/middleware/catch-handler-error' + preLoaderRoute: typeof MiddlewareCatchHandlerErrorRouteImport + parentRoute: typeof rootRouteImport + } '/cookies/set': { id: '/cookies/set' path: '/cookies/set' @@ -863,6 +884,7 @@ const rootRouteChildren: RootRouteChildren = { SubmitPostFormdataRoute: SubmitPostFormdataRoute, AbortSignalMethodRoute: AbortSignalMethodRoute, CookiesSetRoute: CookiesSetRoute, + MiddlewareCatchHandlerErrorRoute: MiddlewareCatchHandlerErrorRoute, MiddlewareClientMiddlewareRouterRoute: MiddlewareClientMiddlewareRouterRoute, MiddlewareFunctionMetadataRoute: MiddlewareFunctionMetadataRoute, MiddlewareMiddlewareFactoryRoute: MiddlewareMiddlewareFactoryRoute, diff --git a/e2e/react-start/server-functions/src/routes/middleware/catch-handler-error.tsx b/e2e/react-start/server-functions/src/routes/middleware/catch-handler-error.tsx new file mode 100644 index 00000000000..44c054d941b --- /dev/null +++ b/e2e/react-start/server-functions/src/routes/middleware/catch-handler-error.tsx @@ -0,0 +1,67 @@ +import { createFileRoute } from '@tanstack/react-router' +import { createMiddleware, createServerFn } from '@tanstack/react-start' +import { useState } from 'react' + +const errorCatchingMiddleware = createMiddleware({ type: 'function' }).server( + async ({ next }) => { + try { + return await next() + } catch (error) { + const originalMessage = + error instanceof Error ? error.message : 'Unknown error' + throw new Error(`Middleware caught and transformed: ${originalMessage}`) + } + }, +) + +const $serverFnThatThrows = createServerFn({ method: 'GET' }) + .middleware([errorCatchingMiddleware]) + .handler(() => { + throw new Error('This error should be caught by middleware') + }) + +export const Route = createFileRoute('/middleware/catch-handler-error')({ + component: RouteComponent, +}) + +function RouteComponent() { + const [error, setError] = useState(null) + const [loading, setLoading] = useState(false) + + const handleClick = async () => { + setLoading(true) + setError(null) + try { + await $serverFnThatThrows() + } catch (e) { + setError(e instanceof Error ? e.message : String(e)) + } finally { + setLoading(false) + } + } + + return ( +
+

+ Middleware Error Catching Test +

+

+ This tests that middleware can catch and transform errors thrown by + server function handlers +

+ + {error && ( +
+ Error: {error} +
+ )} +
+ ) +} diff --git a/e2e/react-start/server-functions/tests/server-functions.spec.ts b/e2e/react-start/server-functions/tests/server-functions.spec.ts index 2bc4873d1b6..62d6a0df94f 100644 --- a/e2e/react-start/server-functions/tests/server-functions.spec.ts +++ b/e2e/react-start/server-functions/tests/server-functions.spec.ts @@ -1021,3 +1021,25 @@ test('server function receives serverFnMeta in options', async ({ page }) => { expect(nestingPostMetadata.inner.post.name.length).toBeGreaterThan(0) expect(nestingPostMetadata.inner.post.filename.length).toBeGreaterThan(0) }) + +test('middleware can catch errors thrown by server function handlers', async ({ + page, +}) => { + await page.goto('/middleware/catch-handler-error') + + await page.waitForLoadState('networkidle') + + await expect(page.getByTestId('catch-handler-error-title')).toBeVisible() + + await page.getByTestId('trigger-error-btn').click() + + await expect(page.getByTestId('transformed-error')).toBeVisible() + + await expect(page.getByTestId('transformed-error')).toContainText( + 'Middleware caught and transformed', + ) + + await expect(page.getByTestId('transformed-error')).toContainText( + 'This error should be caught by middleware', + ) +}) diff --git a/packages/start-client-core/src/createServerFn.ts b/packages/start-client-core/src/createServerFn.ts index 45a9b45163f..b2944e8b533 100644 --- a/packages/start-client-core/src/createServerFn.ts +++ b/packages/start-client-core/src/createServerFn.ts @@ -266,14 +266,13 @@ export async function executeMiddleware( error: userCtx.error ?? (ctx as any).error, } - try { - return await callNextMiddleware(nextCtx) - } catch (error: any) { - return { - ...nextCtx, - error, - } + const result = await callNextMiddleware(nextCtx) + + if (result.error) { + throw result.error } + + return result } // Execute the middleware