Skip to content

Conversation

leesb971204
Copy link
Contributor

@leesb971204 leesb971204 commented Jul 3, 2025

fixes #4517

Summary by CodeRabbit

  • Bug Fixes

    • Prevents error screens when a loader is aborted (AbortError). The app now continues rendering the intended content, applies head updates, and respects base paths on initial load.
    • Reduces unnecessary error flashes during canceled navigations.
  • Tests

    • Added tests validating AbortError handling on initial load with a base path for React and Solid routers.
    • Minor test update adjusting className order to stabilize assertions.

Copy link

nx-cloud bot commented Jul 3, 2025

🤖 Nx Cloud AI Fix Eligible

An automatically generated fix could have helped fix failing tasks for this run, but Self-healing CI is disabled for this workspace. Visit workspace settings to enable it and get automatic fixes in future runs.

To disable these notifications, a workspace admin can disable them in workspace settings.


View your CI Pipeline Execution ↗ for commit d39722e

Command Status Duration Result
nx affected --targets=test:eslint,test:unit,tes... ❌ Failed 4m 12s View ↗
nx run-many --target=build --exclude=examples/*... ✅ Succeeded 1m 22s View ↗

☁️ Nx Cloud last updated this comment at 2025-09-24 02:28:15 UTC

Copy link

pkg-pr-new bot commented Jul 3, 2025

More templates

@tanstack/arktype-adapter

npm i https://pkg.pr.new/TanStack/router/@tanstack/arktype-adapter@4570

@tanstack/directive-functions-plugin

npm i https://pkg.pr.new/TanStack/router/@tanstack/directive-functions-plugin@4570

@tanstack/eslint-plugin-router

npm i https://pkg.pr.new/TanStack/router/@tanstack/eslint-plugin-router@4570

@tanstack/history

npm i https://pkg.pr.new/TanStack/router/@tanstack/history@4570

@tanstack/nitro-v2-vite-plugin

npm i https://pkg.pr.new/TanStack/router/@tanstack/nitro-v2-vite-plugin@4570

@tanstack/react-router

npm i https://pkg.pr.new/TanStack/router/@tanstack/react-router@4570

@tanstack/react-router-devtools

npm i https://pkg.pr.new/TanStack/router/@tanstack/react-router-devtools@4570

@tanstack/react-router-ssr-query

npm i https://pkg.pr.new/TanStack/router/@tanstack/react-router-ssr-query@4570

@tanstack/react-start

npm i https://pkg.pr.new/TanStack/router/@tanstack/react-start@4570

@tanstack/react-start-client

npm i https://pkg.pr.new/TanStack/router/@tanstack/react-start-client@4570

@tanstack/react-start-server

npm i https://pkg.pr.new/TanStack/router/@tanstack/react-start-server@4570

@tanstack/router-cli

npm i https://pkg.pr.new/TanStack/router/@tanstack/router-cli@4570

@tanstack/router-core

npm i https://pkg.pr.new/TanStack/router/@tanstack/router-core@4570

@tanstack/router-devtools

npm i https://pkg.pr.new/TanStack/router/@tanstack/router-devtools@4570

@tanstack/router-devtools-core

npm i https://pkg.pr.new/TanStack/router/@tanstack/router-devtools-core@4570

@tanstack/router-generator

npm i https://pkg.pr.new/TanStack/router/@tanstack/router-generator@4570

@tanstack/router-plugin

npm i https://pkg.pr.new/TanStack/router/@tanstack/router-plugin@4570

@tanstack/router-ssr-query-core

npm i https://pkg.pr.new/TanStack/router/@tanstack/router-ssr-query-core@4570

@tanstack/router-utils

npm i https://pkg.pr.new/TanStack/router/@tanstack/router-utils@4570

@tanstack/router-vite-plugin

npm i https://pkg.pr.new/TanStack/router/@tanstack/router-vite-plugin@4570

@tanstack/server-functions-plugin

npm i https://pkg.pr.new/TanStack/router/@tanstack/server-functions-plugin@4570

@tanstack/solid-router

npm i https://pkg.pr.new/TanStack/router/@tanstack/solid-router@4570

@tanstack/solid-router-devtools

npm i https://pkg.pr.new/TanStack/router/@tanstack/solid-router-devtools@4570

@tanstack/solid-start

npm i https://pkg.pr.new/TanStack/router/@tanstack/solid-start@4570

@tanstack/solid-start-client

npm i https://pkg.pr.new/TanStack/router/@tanstack/solid-start-client@4570

@tanstack/solid-start-server

npm i https://pkg.pr.new/TanStack/router/@tanstack/solid-start-server@4570

@tanstack/start-client-core

npm i https://pkg.pr.new/TanStack/router/@tanstack/start-client-core@4570

@tanstack/start-plugin-core

npm i https://pkg.pr.new/TanStack/router/@tanstack/start-plugin-core@4570

@tanstack/start-server-core

npm i https://pkg.pr.new/TanStack/router/@tanstack/start-server-core@4570

@tanstack/start-static-server-functions

npm i https://pkg.pr.new/TanStack/router/@tanstack/start-static-server-functions@4570

@tanstack/start-storage-context

npm i https://pkg.pr.new/TanStack/router/@tanstack/start-storage-context@4570

@tanstack/valibot-adapter

npm i https://pkg.pr.new/TanStack/router/@tanstack/valibot-adapter@4570

@tanstack/virtual-file-routes

npm i https://pkg.pr.new/TanStack/router/@tanstack/virtual-file-routes@4570

@tanstack/zod-adapter

npm i https://pkg.pr.new/TanStack/router/@tanstack/zod-adapter@4570

commit: d39722e

Comment on lines 2514 to 2529
if (
e instanceof DOMException &&
e.name === 'AbortError'
) {
const head = await executeHead()
updateMatch(matchId, (prev) => ({
...prev,
status:
prev.status === 'pending'
? 'success'
: prev.status,
...head,
}))
return
}

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

According to the documentation, the abortController.abort() method throws an Error object of type DOMException with the name AbortError.
When this error is thrown during route loading, it is treated as a normal control flow and the route is updated to a valid state accordingly.

Copy link
Contributor Author

@leesb971204 leesb971204 Jul 3, 2025

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

@SeanCassiere

I noticed you’ve explored similar concerns before(#4531), so I think it would be great to discuss this approach together.

@leesb971204
Copy link
Contributor Author

@SeanCassiere

Would there be a chance for us to have a conversation about this?

Copy link
Member

@SeanCassiere SeanCassiere left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Sorry about that!

@schiller-manuel there were recent changes this flow if I'm not mistaken with the selective SSR stuff. Would this be an issue?

@leesb971204 provided Manuel's fine with these changes, I don't see a reason not to get this pushed in.

Edit: Also, please make sure that your test are reflected in @tanstack/solid-router as well.

@leesb971204
Copy link
Contributor Author

Sorry about that!

@schiller-manuel there were recent changes this flow if I'm not mistaken with the selective SSR stuff. Would this be an issue?

@leesb971204 provided Manuel's fine with these changes, I don't see a reason not to get this pushed in.

Edit: Also, please make sure that your test are reflected in @tanstack/solid-router as well.

Thanks for the comment!
I’ve added the same test to solid-router as well.

@leesb971204
Copy link
Contributor Author

Sorry about that!

@schiller-manuel there were recent changes this flow if I'm not mistaken with the selective SSR stuff. Would this be an issue?

@leesb971204 provided Manuel's fine with these changes, I don't see a reason not to get this pushed in.

Edit: Also, please make sure that your test are reflected in @tanstack/solid-router as well.

Are there any other tasks that need to be completed before this work can be approved?

@SeanCassiere
Copy link
Member

Are there any other tasks that need to be completed before this work can be approved?

Other than the current merge conflict, as mentioned here, I'd like @schiller-manuel to take a look at how this is being handled.

@leesb971204
Copy link
Contributor Author

Is there any particular reason this work hasn’t been reviewed yet?

@schiller-manuel
Copy link
Contributor

no, we are just busy with a lot of stuff, sorry. will get back to you as soon as time permits

Copy link
Contributor

coderabbitai bot commented Sep 24, 2025

Walkthrough

Adds AbortError handling in loader error flow to prevent error propagation on initial load with basepath. Executes head, preserves/updates match status, and returns early. Adds React and Solid tests reproducing AbortError during basepath mount and asserting no error UI and correct path.

Changes

Cohort / File(s) Summary
Core loader error handling
packages/router-core/src/load-matches.ts
Intercepts DOMException with name "AbortError" during loader errors; runs executeHead, merges head result, preserves prior pending status as success, and exits without triggering error handling.
React tests for AbortError + basepath
packages/react-router/tests/loaders.test.tsx
Adds test for loader rejecting with AbortError on initial load with basepath; asserts index renders, error UI absent, and path includes basepath. Minor className order change in a reproducer div.
Solid tests for AbortError + basepath
packages/solid-router/tests/loaders.test.tsx
Adds analogous test verifying AbortError on initial load with basepath does not render error component and basepath is preserved.

Sequence Diagram(s)

sequenceDiagram
  autonumber
  actor User
  participant App
  participant Router
  participant RouteLoader as Loader
  participant Head as executeHead
  participant UI as ErrorBoundary

  User->>App: Open "/"
  App->>Router: Initialize with basepath "/app"
  Router->>Loader: Run loader (initial load)
  note right of Loader: Abort due to basepath redirect
  Loader-->>Router: Throw DOMException(name="AbortError")

  alt AbortError
    Router->>Head: executeHead()
    Head-->>Router: headResult
    note over Router: Merge head, preserve/resolve match status
    Router-->>App: Render route content (no error)
    App-->>User: UI shows index under "/app"
  else Other errors
    Router->>UI: Propagate to error boundary
    UI-->>User: Error UI
  end
Loading

Estimated code review effort

🎯 3 (Moderate) | ⏱️ ~25 minutes

Possibly related PRs

Suggested reviewers

  • schiller-manuel

Poem

A hop, a skip, the loaders race—
Abort! says basepath, shifting place.
No burrowed error, calm instead,
We nibble greens while running head.
The trail now starts at “/app”, hooray—
A rabbit routes the tidy way. 🐇✨

Pre-merge checks and finishing touches

✅ Passed checks (5 passed)
Check name Status Explanation
Description Check ✅ Passed Check skipped - CodeRabbit’s high-level summary is enabled.
Title Check ✅ Passed The title "fix(router-core): handle AbortError in router execution flow" succinctly and accurately describes the primary change (special-casing AbortError handling in router-core) and aligns with the modified files and added tests that target loader abort behavior; it is specific, concise, and relevant to the changeset.
Linked Issues Check ✅ Passed The changes directly address issue #4517 by adding special handling for DOMException 'AbortError' in packages/router-core/src/load-matches.ts to prevent the AbortError from bubbling into the ErrorBoundary, and by adding tests in react-router and solid-router that reproduce the basepath initial-load abort scenario and assert the app renders without showing the error route. These code and test updates match the linked issue's objective to avoid treating an unreasoned abort as an application error.
Out of Scope Changes Check ✅ Passed All modifications are limited to router-core logic and router-specific tests (react-router and solid-router) that validate the abort behavior; there are no changes to public/exported APIs or unrelated packages in the provided summary.
Docstring Coverage ✅ Passed No functions found in the changes. Docstring coverage check skipped.
✨ Finishing touches
  • 📝 Generate Docstrings
🧪 Generate unit tests
  • Create PR with unit tests
  • Post copyable unit tests in a comment

Comment @coderabbitai help to get the list of available commands and usage tips.

Copy link
Contributor

@coderabbitai coderabbitai bot left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Actionable comments posted: 1

📜 Review details

Configuration used: CodeRabbit UI

Review profile: CHILL

Plan: Pro

📥 Commits

Reviewing files that changed from the base of the PR and between c2f732d and d39722e.

📒 Files selected for processing (3)
  • packages/react-router/tests/loaders.test.tsx (2 hunks)
  • packages/router-core/src/load-matches.ts (1 hunks)
  • packages/solid-router/tests/loaders.test.tsx (1 hunks)
🧰 Additional context used
📓 Path-based instructions (3)
**/*.{ts,tsx}

📄 CodeRabbit inference engine (AGENTS.md)

Use TypeScript in strict mode with extensive type safety across the codebase

Files:

  • packages/router-core/src/load-matches.ts
  • packages/solid-router/tests/loaders.test.tsx
  • packages/react-router/tests/loaders.test.tsx
packages/router-core/**

📄 CodeRabbit inference engine (AGENTS.md)

Keep framework-agnostic core router logic in packages/router-core/

Files:

  • packages/router-core/src/load-matches.ts
packages/{react-router,solid-router}/**

📄 CodeRabbit inference engine (AGENTS.md)

Implement React and Solid bindings/components only in packages/react-router/ and packages/solid-router/

Files:

  • packages/solid-router/tests/loaders.test.tsx
  • packages/react-router/tests/loaders.test.tsx
🧬 Code graph analysis (1)
packages/react-router/tests/loaders.test.tsx (1)
packages/react-router/src/router.ts (1)
  • createRouter (80-82)
⏰ Context from checks skipped due to timeout of 90000ms. You can increase the timeout in your CodeRabbit configuration to a maximum of 15 minutes (900000ms). (2)
  • GitHub Check: Preview
  • GitHub Check: Test
🔇 Additional comments (3)
packages/react-router/tests/loaders.test.tsx (2)

433-433: Non-functional className reorder — OK

No behavior change; safe.


733-757: Good coverage for AbortError + basepath

Test correctly asserts no error UI and basepath prefix.

Optionally also assert router.state.location.href.startsWith('/app') for stronger verification.

packages/solid-router/tests/loaders.test.tsx (1)

321-345: Solid test parity — looks good

Mirrors React test; assertions are appropriate.

Optionally assert router.state.location.href.startsWith('/app') for symmetry with the React suite.

Comment on lines +673 to +682
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
}

Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

⚠️ Potential issue

DOMException check can crash in SSR; also ensure pending-min duration and reset isFetching on async path

  • error instanceof DOMException will throw if DOMException is undefined (some SSR/envs).
  • Early return skips awaiting minPendingPromise, potentially violating pendingMinMs guarantees.
  • In background reloads (loaderShouldRunAsync), isFetching can remain 'loader' after abort since only runLoader can flip it to false.

Fix by detecting abort via name === 'AbortError', awaiting min-pending, and explicitly setting isFetching: false.

-      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
-      }
+      // Treat aborted loaders as cancellations, not errors
+      if ((error as any)?.name === 'AbortError') {
+        const head = await executeHead(inner, matchId, route)
+        const pendingPromise = match._nonReactive.minPendingPromise
+        if (pendingPromise) await pendingPromise
+        inner.updateMatch(matchId, (prev) => ({
+          ...prev,
+          status: prev.status === 'pending' ? 'success' : prev.status,
+          isFetching: false,
+          ...head,
+        }))
+        return
+      }
📝 Committable suggestion

‼️ IMPORTANT
Carefully review the code before committing. Ensure that it accurately replaces the highlighted code, contains no missing lines, and has no issues with indentation. Thoroughly test & benchmark the code to ensure it meets the requirements.

Suggested change
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
}
// Treat aborted loaders as cancellations, not errors
if ((error as any)?.name === 'AbortError') {
const head = await executeHead(inner, matchId, route)
const pendingPromise = match._nonReactive.minPendingPromise
if (pendingPromise) await pendingPromise
inner.updateMatch(matchId, (prev) => ({
...prev,
status: prev.status === 'pending' ? 'success' : prev.status,
isFetching: false,
...head,
}))
return
}
🤖 Prompt for AI Agents
In packages/router-core/src/load-matches.ts around lines 673–682, replace the
fragile `error instanceof DOMException && error.name === 'AbortError'` check
with a safe abort detection using `error?.name === 'AbortError'` (or equivalent
null-safe check) so SSR environments without DOMException don't throw; before
returning, await the existing min-pending promise used elsewhere (e.g., `await
minPendingPromise`) to honor pendingMinMs guarantees; and call
`inner.updateMatch(matchId, ...)` to explicitly set `isFetching: false` (and
keep the current status transition logic: set status to 'success' only if
previously 'pending') so background loader aborts don't leave `isFetching` stuck
as 'loader'.

@leesb971204
Copy link
Contributor Author

The tests can only pass after #5202 has been merged.

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

Projects

None yet

Development

Successfully merging this pull request may close these issues.

AbortError: signal is aborted without reason is bubbling up when using loader with basepath

3 participants