Repository: TanStack/router (ts)

Issue:
Issue with `useBlocker` when navigating to route with `beforeLoad` and `redirect`
### Describe the bug

There is an issue when using `useBlocker` when the user tries to `navigate` to route with `beforeLoad` with `redirect`. The user will either see `Blocker` twice or have the tab unresponding depending on the type of `Blocker` used.

### Your Example Website or App

https://stackblitz.com/edit/tanstack-router-2j52rq?file=src%2Fmain.tsx

### Steps to Reproduce the Bug or Issue

Native blocker
1. Go to `Editor 1` page
2. Type anything into the input
3. uncheck `Use custom blocker`
4. Try to navigate to `Editor 2` page
5. Click `ok` when the blocker appears

`Blocker` will appear twice in this case

Custom blocker
1. Go to `Editor 1` page
2. Type anything into the input
3. check `Use custom blocker`
4. Try to navigate to `Editor 2` page
5. Click `yes` when the blocker appears

the browser will crash in this case

### Expected behavior

As a user, the `Blocker` should only appear once and not crash the browser

### Screenshots or Videos

_No response_

### Platform

- OS: Windows 11
- Browser: Arc Browser
- Version: 1.3.2


### Additional context

_No response_
Router is stuck in displaying the loading component when an error is thrown from `beforeLoad`
### Describe the bug

When an error is thrown from the `beforeLoad` option of a route and the router is configured with pending component, it seems that the router remains stuck in displaying the pending component.

If I remove the pending component it doesn't display anything.

### Your Example Website or App

https://stackblitz.com/edit/tanstack-router-g8hajv?file=src%2Fmain.tsx

### Steps to Reproduce the Bug or Issue

1. Check the demo from the link
2. See how the pending component is displayed indefinitely
3. You can toggle the defaultPendingComponent to test without the pending mode

### Expected behavior

I was expecting to see the error component in both cases: with and without the pending component.

### Screenshots or Videos

_No response_

### Platform

- OS: macOS
- Browser: Chrome
- @tanstack/react-router: 1.51.0

### Additional context

_No response_

Pull request:
fix(react-router): resolve loadPromise when beforeLoad throws
fixes #2198
fixes #1688

Patch:
diff --git a/examples/react/start-convex-trellaux/.env.local.example b/examples/react/start-convex-trellaux/.env.local.example
new file mode 100644
index 000000000..315dd107e
--- /dev/null
+++ b/examples/react/start-convex-trellaux/.env.local.example
@@ -0,0 +1,3 @@
+# This is a shared default backend you can't modify the code for.
+# In order to create your own backend, run `npx convex dev`.
+VITE_CONVEX_URL=https://laudable-anaconda-558.convex.cloud
diff --git a/examples/react/start-convex-trellaux/.stackblitzrc b/examples/react/start-convex-trellaux/.stackblitzrc
new file mode 100644
index 000000000..38b03d072
--- /dev/null
+++ b/examples/react/start-convex-trellaux/.stackblitzrc
@@ -0,0 +1,3 @@
+{
+  "startCommand": "cp .env.local.example .env.local && npx vinxi dev",
+}
diff --git a/examples/react/start-convex-trellaux/app/routeTree.gen.ts b/examples/react/start-convex-trellaux/app/routeTree.gen.ts
index 61956de61..05b9df826 100644
--- a/examples/react/start-convex-trellaux/app/routeTree.gen.ts
+++ b/examples/react/start-convex-trellaux/app/routeTree.gen.ts
@@ -17,13 +17,11 @@ import { Route as BoardsBoardIdImport } from './routes/boards.$boardId'
 // Create/Update Routes
 
 const IndexRoute = IndexImport.update({
-  id: '/',
   path: '/',
   getParentRoute: () => rootRoute,
 } as any)
 
 const BoardsBoardIdRoute = BoardsBoardIdImport.update({
-  id: '/boards/$boardId',
   path: '/boards/$boardId',
   getParentRoute: () => rootRoute,
 } as any)
diff --git a/examples/react/start-convex-trellaux/convex/crons.ts b/examples/react/start-convex-trellaux/convex/crons.ts
new file mode 100644
index 000000000..a11889d5b
--- /dev/null
+++ b/examples/react/start-convex-trellaux/convex/crons.ts
@@ -0,0 +1,8 @@
+import { cronJobs } from 'convex/server'
+import { internal } from './_generated/api'
+
+const crons = cronJobs()
+
+crons.cron('clear messages table', '0,20,40 * * * *', internal.board.clear)
+
+export default crons
diff --git a/packages/react-router/src/router.ts b/packages/react-router/src/router.ts
index 7d82ca071..4d8f1b7cc 100644
--- a/packages/react-router/src/router.ts
+++ b/packages/react-router/src/router.ts
@@ -2088,6 +2088,7 @@ export class Router<
 
               updateMatch(matchId, (prev) => {
                 prev.beforeLoadPromise?.resolve()
+                prev.loadPromise?.resolve()
 
                 return {
                   ...prev,


Test patch:
diff --git a/packages/react-router/tests/loaders.test.tsx b/packages/react-router/tests/loaders.test.tsx
index 0ca852044..5f25c8a89 100644
--- a/packages/react-router/tests/loaders.test.tsx
+++ b/packages/react-router/tests/loaders.test.tsx
@@ -224,3 +224,101 @@ test('reproducer for #2053', async () => {
   const fooElement = await screen.findByText('fooId: 3ΚΑΠΠΑ')
   expect(fooElement).toBeInTheDocument()
 })
+
+test('reproducer for #2198 - throw error from beforeLoad upon initial load', async () => {
+  const rootRoute = createRootRoute({})
+
+  const indexRoute = createRoute({
+    getParentRoute: () => rootRoute,
+    path: '/',
+    component: () => <div>Index page</div>,
+    beforeLoad: () => {
+      throw new Error('Test!')
+    },
+    errorComponent: () => <div>indexErrorComponent</div>,
+  })
+
+  const routeTree = rootRoute.addChildren([indexRoute])
+  const router = createRouter({
+    routeTree,
+    defaultErrorComponent: () => {
+      return <div>defaultErrorComponent</div>
+    },
+  })
+
+  render(<RouterProvider router={router} />)
+
+  const errorElement = await screen.findByText('indexErrorComponent')
+  expect(errorElement).toBeInTheDocument()
+})
+
+test('throw error from loader upon initial load', async () => {
+  const rootRoute = createRootRoute({})
+
+  const indexRoute = createRoute({
+    getParentRoute: () => rootRoute,
+    path: '/',
+    component: () => <div>Index page</div>,
+    loader: () => {
+      throw new Error('Test!')
+    },
+    errorComponent: () => <div>indexErrorComponent</div>,
+  })
+
+  const routeTree = rootRoute.addChildren([indexRoute])
+  const router = createRouter({
+    routeTree,
+    defaultErrorComponent: () => {
+      return <div>defaultErrorComponent</div>
+    },
+  })
+
+  render(<RouterProvider router={router} />)
+
+  const errorElement = await screen.findByText('indexErrorComponent')
+  expect(errorElement).toBeInTheDocument()
+})
+
+test('throw error from beforeLoad when navigating to route', async () => {
+  const rootRoute = createRootRoute({})
+
+  const indexRoute = createRoute({
+    getParentRoute: () => rootRoute,
+    path: '/',
+    component: () => (
+      <div>
+        <h1>Index page</h1> <Link to="/foo">link to foo</Link>
+      </div>
+    ),
+    errorComponent: () => <div>indexErrorComponent</div>,
+  })
+
+  const fooRoute = createRoute({
+    getParentRoute: () => rootRoute,
+    path: '/foo',
+    component: () => <div>Foo page</div>,
+    beforeLoad: () => {
+      throw new Error('Test!')
+    },
+    errorComponent: () => <div>fooErrorComponent</div>,
+  })
+
+  const routeTree = rootRoute.addChildren([indexRoute, fooRoute])
+  const router = createRouter({
+    routeTree,
+    defaultErrorComponent: () => {
+      return <div>defaultErrorComponent</div>
+    },
+  })
+
+  render(<RouterProvider router={router} />)
+
+  const linkToFoo = await screen.findByRole('link', { name: 'link to foo' })
+
+  expect(linkToFoo).toBeInTheDocument()
+
+  fireEvent.click(linkToFoo)
+
+  const indexElement = await screen.findByText('fooErrorComponent')
+  expect(indexElement).toBeInTheDocument()
+})