From bd633ce187ecade0c55ad07b2e76189d099473a9 Mon Sep 17 00:00:00 2001 From: leesb971204 Date: Tue, 6 Jan 2026 11:40:12 +0900 Subject: [PATCH 1/2] fix(router-core): handle AbortError in router execution flow Signed-off-by: leesb971204 --- packages/react-router/tests/loaders.test.tsx | 29 +++++++++++++++++++- packages/router-core/src/load-matches.ts | 10 +++++++ packages/solid-router/tests/loaders.test.tsx | 27 ++++++++++++++++++ 3 files changed, 65 insertions(+), 1 deletion(-) diff --git a/packages/react-router/tests/loaders.test.tsx b/packages/react-router/tests/loaders.test.tsx index 8d8ea54e738..fc9e87b0771 100644 --- a/packages/react-router/tests/loaders.test.tsx +++ b/packages/react-router/tests/loaders.test.tsx @@ -430,7 +430,7 @@ test('reproducer #4546', async () => { component: () => { return ( <> -
+
{ expect(fooPendingComponentOnMountMock).not.toHaveBeenCalled() }) +test('throw abortError from loader upon initial load with basepath', async () => { + window.history.replaceState(null, 'root', '/app') + const rootRoute = createRootRoute({}) + + const indexRoute = createRoute({ + getParentRoute: () => rootRoute, + path: '/', + loader: async () => { + return Promise.reject(new DOMException('Aborted', 'AbortError')) + }, + component: () =>
Index route content
, + errorComponent: () => ( +
indexErrorComponent
+ ), + }) + + const routeTree = rootRoute.addChildren([indexRoute]) + const router = createRouter({ routeTree, history, basepath: '/app' }) + + render() + + const indexElement = await screen.findByText('Index route content') + expect(indexElement).toBeInTheDocument() + expect(screen.queryByTestId('index-error')).not.toBeInTheDocument() + expect(window.location.pathname.startsWith('/app')).toBe(true) +}) + test('cancelMatches after pending timeout', async () => { function getPendingComponent(onMount: () => void) { const PendingComponent = () => { diff --git a/packages/router-core/src/load-matches.ts b/packages/router-core/src/load-matches.ts index ec72cbfe441..9d3a4b0aeba 100644 --- a/packages/router-core/src/load-matches.ts +++ b/packages/router-core/src/load-matches.ts @@ -699,6 +699,16 @@ const runLoader = async ( } catch (e) { let error = e + if (error instanceof DOMException && error.name === 'AbortError') { + const head = await executeHead(inner, matchId, route) + inner.updateMatch(matchId, (prev) => ({ + ...prev, + status: prev.status === 'pending' ? 'success' : prev.status, + ...head, + })) + return + } + const pendingPromise = match._nonReactive.minPendingPromise if (pendingPromise) await pendingPromise diff --git a/packages/solid-router/tests/loaders.test.tsx b/packages/solid-router/tests/loaders.test.tsx index c0e52532191..09f1676bd80 100644 --- a/packages/solid-router/tests/loaders.test.tsx +++ b/packages/solid-router/tests/loaders.test.tsx @@ -320,6 +320,33 @@ test('throw error from beforeLoad when navigating to route', async () => { expect(indexElement).toBeInTheDocument() }) +test('throw abortError from loader upon initial load with basepath', async () => { + window.history.replaceState(null, 'root', '/app') + const rootRoute = createRootRoute({}) + + const indexRoute = createRoute({ + getParentRoute: () => rootRoute, + path: '/', + loader: async () => { + return Promise.reject(new DOMException('Aborted', 'AbortError')) + }, + component: () =>
Index route content
, + errorComponent: () => ( +
indexErrorComponent
+ ), + }) + + const routeTree = rootRoute.addChildren([indexRoute]) + const router = createRouter({ routeTree, basepath: '/app' }) + + render(() => ) + + const indexElement = await screen.findByText('Index route content') + expect(indexElement).toBeInTheDocument() + expect(screen.queryByTestId('index-error')).not.toBeInTheDocument() + expect(window.location.pathname.startsWith('/app')).toBe(true) +}) + test('reproducer #4245', async () => { const LOADER_WAIT_TIME = 500 const rootRoute = createRootRoute({}) From 7d62f47ed1635acb85f99a63cf27167d154eaaee Mon Sep 17 00:00:00 2001 From: leesb971204 Date: Wed, 7 Jan 2026 09:40:09 +0900 Subject: [PATCH 2/2] fix(router-core): fix handle AbortError logic Signed-off-by: leesb971204 --- packages/router-core/src/load-matches.ts | 5 ++--- 1 file changed, 2 insertions(+), 3 deletions(-) diff --git a/packages/router-core/src/load-matches.ts b/packages/router-core/src/load-matches.ts index 9d3a4b0aeba..87cb9ce34a9 100644 --- a/packages/router-core/src/load-matches.ts +++ b/packages/router-core/src/load-matches.ts @@ -699,12 +699,11 @@ const runLoader = async ( } catch (e) { let error = e - if (error instanceof DOMException && error.name === 'AbortError') { - const head = await executeHead(inner, matchId, route) + if ((error as any)?.name === 'AbortError') { inner.updateMatch(matchId, (prev) => ({ ...prev, status: prev.status === 'pending' ? 'success' : prev.status, - ...head, + isFetching: false, })) return }