Skip to content

Conversation

@DeTuksa
Copy link
Contributor

@DeTuksa DeTuksa commented Nov 17, 2025

Resolves: #5763

  • Updated packages/router-core/src/router.ts to treat 'notFound' like 'error' in invalidate
  • Added a new test in packages/react-router/tests/router.test.tsx that verifies a route returning a 404 notFound

Summary by CodeRabbit

  • New Features

    • Added a public invalidate API to mark routes (optionally via a filter) for reset so their loaders re-run.
  • Bug Fixes

    • Routes stuck in "not found" or error states now reset to pending when invalidated, ensuring loaders re-run and the correct content or not-found UI is shown.
  • Tests

    • Added regression tests covering filtered (HMR-style) and global invalidation to verify loaders re-run and not-found UI remains shown as expected.

@coderabbitai
Copy link
Contributor

coderabbitai bot commented Nov 17, 2025

Walkthrough

Adds a public RouterCore.invalidate(...) method and updates commit/invalidate logic so matches with status 'notFound' are treated like 'error' during invalidation and commit: errors are cleared and matching exits are reset to 'pending' (optionally via forcePending) so loaders re-run. Adds tests in React and Solid verifying loaders that throw or return notFound() re-run and notFoundComponent remains rendered.

Changes

Cohort / File(s) Summary
Router core
packages/router-core/src/router.ts
Added public invalidate(opts?) on RouterCore; commit logic updated to retain/exclude exiting matches considering status === 'error' or 'notFound'; invalidate now clears errors and converts 'notFound'/'error' matches to 'pending' (respecting forcePending and optional filter) so loaders will re-run.
Tests (react)
packages/react-router/tests/router.test.tsx
Added two regression tests under the "invalidate" suite that simulate HMR-filtered and global invalidation when loaders throw or return notFound(), asserting loaders re-run and notFoundComponent remains rendered while original route is removed.
Tests (solid)
packages/solid-router/tests/router.test.tsx
Added parallel regression tests mirroring the React tests: filtered and global invalidation scenarios for loaders throwing/returning notFound(), verifying re-run behavior, rendering of notFoundComponent, and loader invocation counts.

Sequence Diagram(s)

sequenceDiagram
    actor Dev
    participant HMR as HMR System
    participant Router as RouterCore
    participant Loader as Route Loader
    participant UI as Renderer

    Dev->>HMR: Save file (HMR)
    HMR->>Router: invalidate(filter?) 
    activate Router
    Router->>Router: find matches matching filter
    alt match status is 'error' or 'notFound' or opts.forcePending
        Router->>Router: clear error, set status -> 'pending'
    end
    Router->>Loader: invoke loader for pending match
    activate Loader
    Loader-->>Router: returns data OR throws/returns notFound()
    deactivate Loader
    Router->>UI: render route data or notFoundComponent
    deactivate Router
Loading

Estimated code review effort

🎯 4 (Complex) | ⏱️ ~45 minutes

  • Focus review on packages/router-core/src/router.ts (invalidate API surface, status transitions, cachedMatches commit logic).
  • Review test determinism in packages/react-router/tests/router.test.tsx and packages/solid-router/tests/router.test.tsx (HMR filter simulation and loader invocation counts).
  • Verify public typing/signature for the new invalidate method and any exports.

Possibly related PRs

Suggested reviewers

  • schiller-manuel
  • nlynzaad

Poem

🐰 A hop, a patch, a tidy shove,
NotFound stays near, the loaders rove.
HMR taps, the router mends,
Re-runs the loaders, keeps old friends. 🥕

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 accurately reflects the main change: fixing a crash when routes with server functions as loaders that throw notFound() undergo HMR.
Linked Issues check ✅ Passed All coding requirements from issue #5763 are met: the invalidate API treats notFound like error, loaders re-run on invalidation, and notFoundComponent continues rendering after HMR.
Out of Scope Changes check ✅ Passed All changes are directly related to fixing the notFound crash on HMR: router invalidate logic, commit path caching, and comprehensive test coverage for both React and Solid routers.
Docstring Coverage ✅ Passed No functions found in the changed files to evaluate docstring coverage. Skipping docstring coverage check.
✨ Finishing touches
  • 📝 Generate docstrings
🧪 Generate unit tests (beta)
  • Create PR with unit tests
  • Post copyable unit tests in a comment

📜 Recent review details

Configuration used: CodeRabbit UI

Review profile: CHILL

Plan: Pro

📥 Commits

Reviewing files that changed from the base of the PR and between c4f892b and 32a3f09.

📒 Files selected for processing (1)
  • packages/router-core/src/router.ts (3 hunks)
🧰 Additional context used
🧠 Learnings (3)
📓 Common learnings
Learnt from: nlynzaad
Repo: TanStack/router PR: 5402
File: packages/router-generator/tests/generator/no-formatted-route-tree/routeTree.nonnested.snapshot.ts:19-21
Timestamp: 2025-10-08T08:11:47.088Z
Learning: Test snapshot files in the router-generator tests directory (e.g., files matching the pattern `packages/router-generator/tests/generator/**/routeTree*.snapshot.ts` or `routeTree*.snapshot.js`) should not be modified or have issues flagged, as they are fixtures used to verify the generator's output and are intentionally preserved as-is.
📚 Learning: 2025-10-08T08:11:47.088Z
Learnt from: nlynzaad
Repo: TanStack/router PR: 5402
File: packages/router-generator/tests/generator/no-formatted-route-tree/routeTree.nonnested.snapshot.ts:19-21
Timestamp: 2025-10-08T08:11:47.088Z
Learning: Test snapshot files in the router-generator tests directory (e.g., files matching the pattern `packages/router-generator/tests/generator/**/routeTree*.snapshot.ts` or `routeTree*.snapshot.js`) should not be modified or have issues flagged, as they are fixtures used to verify the generator's output and are intentionally preserved as-is.

Applied to files:

  • packages/router-core/src/router.ts
📚 Learning: 2025-10-01T18:30:26.591Z
Learnt from: schiller-manuel
Repo: TanStack/router PR: 5330
File: packages/router-core/src/router.ts:2231-2245
Timestamp: 2025-10-01T18:30:26.591Z
Learning: In `packages/router-core/src/router.ts`, the `resolveRedirect` method intentionally strips the router's origin from redirect URLs when they match (e.g., `https://foo.com/bar` → `/bar` for same-origin redirects) while preserving the full URL for cross-origin redirects. This logic should not be removed or simplified to use `location.publicHref` directly.

Applied to files:

  • packages/router-core/src/router.ts
🔇 Additional comments (2)
packages/router-core/src/router.ts (2)

2132-2143: Excluding error/notFound matches from cachedMatches is consistent and correct

This updated filter and comment make the caching semantics explicit and ensure failed/not-found results are not reused after commit, which aligns with the new invalidation behavior and the HMR scenario described in #5763.


2316-2357: invalidate implementation correctly re-runs loaders for error/notFound matches

The new invalidate API cleanly marks filtered matches as invalid and resets error/notFound (and optionally all via forcePending) back to pending across matches, pendingMatches, and cachedMatches before calling load, which is exactly what’s needed for HMR-driven reloads while keeping the type constraints consistent with ClearCacheFn.


Thanks for using CodeRabbit! It's free for OSS, and your support helps us grow. If you like it, consider giving us a shout-out.

❤️ Share

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

@nx-cloud
Copy link

nx-cloud bot commented Nov 18, 2025

View your CI Pipeline Execution ↗ for commit 32a3f09

Command Status Duration Result
nx affected --targets=test:eslint,test:unit,tes... ✅ Succeeded 9m 53s View ↗
nx run-many --target=build --exclude=examples/*... ✅ Succeeded 1m 25s View ↗

☁️ Nx Cloud last updated this comment at 2025-11-19 16:08:19 UTC

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 3cd2f15 and 68e25a9.

📒 Files selected for processing (2)
  • packages/react-router/tests/router.test.tsx (1 hunks)
  • packages/router-core/src/router.ts (1 hunks)
🚧 Files skipped from review as they are similar to previous changes (1)
  • packages/react-router/tests/router.test.tsx
🧰 Additional context used
🧠 Learnings (3)
📓 Common learnings
Learnt from: nlynzaad
Repo: TanStack/router PR: 5402
File: packages/router-generator/tests/generator/no-formatted-route-tree/routeTree.nonnested.snapshot.ts:19-21
Timestamp: 2025-10-08T08:11:47.088Z
Learning: Test snapshot files in the router-generator tests directory (e.g., files matching the pattern `packages/router-generator/tests/generator/**/routeTree*.snapshot.ts` or `routeTree*.snapshot.js`) should not be modified or have issues flagged, as they are fixtures used to verify the generator's output and are intentionally preserved as-is.
📚 Learning: 2025-10-01T18:30:26.591Z
Learnt from: schiller-manuel
Repo: TanStack/router PR: 5330
File: packages/router-core/src/router.ts:2231-2245
Timestamp: 2025-10-01T18:30:26.591Z
Learning: In `packages/router-core/src/router.ts`, the `resolveRedirect` method intentionally strips the router's origin from redirect URLs when they match (e.g., `https://foo.com/bar` → `/bar` for same-origin redirects) while preserving the full URL for cross-origin redirects. This logic should not be removed or simplified to use `location.publicHref` directly.

Applied to files:

  • packages/router-core/src/router.ts
📚 Learning: 2025-10-08T08:11:47.088Z
Learnt from: nlynzaad
Repo: TanStack/router PR: 5402
File: packages/router-generator/tests/generator/no-formatted-route-tree/routeTree.nonnested.snapshot.ts:19-21
Timestamp: 2025-10-08T08:11:47.088Z
Learning: Test snapshot files in the router-generator tests directory (e.g., files matching the pattern `packages/router-generator/tests/generator/**/routeTree*.snapshot.ts` or `routeTree*.snapshot.js`) should not be modified or have issues flagged, as they are fixtures used to verify the generator's output and are intentionally preserved as-is.

Applied to files:

  • packages/router-core/src/router.ts

@birkskyum
Copy link
Member

@DeTuksa , can you add a similar test in the solid-router?

@DeTuksa
Copy link
Contributor Author

DeTuksa commented Nov 18, 2025

Sure @birkskyum😀

I added these cases:

it('re-runs loaders that throw notFound() when invalidated via HMR filter', async () => {
    const history = createMemoryHistory({
      initialEntries: ['/hmr-not-found'],
    })
    const loader = vi.fn(() => {
      throw notFound()
    })

    const rootRoute = createRootRoute({
      component: () => <Outlet />,
    })

    const hmrRoute = createRoute({
      getParentRoute: () => rootRoute,
      path: '/hmr-not-found',
      loader,
      component: () => <div data-testid="hmr-route">Route</div>,
      notFoundComponent: () => (
        <div data-testid="hmr-route-not-found">Route Not Found</div>
      ),
    })

    const router = createRouter({
      routeTree: rootRoute.addChildren([hmrRoute]),
      history,
    })

    render(() => <RouterProvider router={router} />)
    await router.load()

    await screen.findByTestId('hmr-route-not-found')
    const initialCalls = loader.mock.calls.length
    expect(initialCalls).toBeGreaterThan(0)

    await router.invalidate({
      filter: (match) => match.routeId === hmrRoute.id,
    })

    await waitFor(() => expect(loader).toHaveBeenCalledTimes(initialCalls + 1))
    await screen.findByTestId('hmr-route-not-found')
    expect(screen.queryByTestId('hmr-route')).not.toBeInTheDocument()
  })

  it('keeps rendering a route notFoundComponent when loader returns notFound() after invalidate', async () => {
    const history = createMemoryHistory({
      initialEntries: ['/loader-not-found'],
    })
    const loader = vi.fn(() => notFound())

    const rootRoute = createRootRoute({
      component: () => <Outlet />,
    })

    const loaderRoute = createRoute({
      getParentRoute: () => rootRoute,
      path: '/loader-not-found',
      loader,
      component: () => <div data-testid="loader-route">Route</div>,
      notFoundComponent: () => (
        <div data-testid="loader-not-found-component">Route Not Found</div>
      ),
    })

    const router = createRouter({
      routeTree: rootRoute.addChildren([loaderRoute]),
      history,
    })

    render(() => <RouterProvider router={router} />)
    await router.load()

    await screen.findByTestId('loader-not-found-component')
    const initialCalls = loader.mock.calls.length
    expect(initialCalls).toBeGreaterThan(0)

    await router.invalidate()

    await waitFor(() => expect(loader).toHaveBeenCalledTimes(initialCalls + 1))
    await screen.findByTestId('loader-not-found-component')
    expect(screen.queryByTestId('loader-route')).not.toBeInTheDocument()
  })

@DeTuksa
Copy link
Contributor Author

DeTuksa commented Nov 19, 2025

@coderabbitai review

@coderabbitai
Copy link
Contributor

coderabbitai bot commented Nov 19, 2025

✅ Actions performed

Review triggered.

Note: CodeRabbit is an incremental review system and does not re-review already reviewed commits. This command is applicable only when automatic reviews are paused.

@DeTuksa
Copy link
Contributor Author

DeTuksa commented Nov 19, 2025

@birkskyum I think the checks that failed are related to the cloudfare downtime yesterday😅

@pkg-pr-new
Copy link

pkg-pr-new bot commented Nov 19, 2025

More templates

@tanstack/arktype-adapter

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

@tanstack/directive-functions-plugin

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

@tanstack/eslint-plugin-router

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

@tanstack/history

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

@tanstack/nitro-v2-vite-plugin

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

@tanstack/react-router

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

@tanstack/react-router-devtools

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

@tanstack/react-router-ssr-query

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

@tanstack/react-start

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

@tanstack/react-start-client

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

@tanstack/react-start-server

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

@tanstack/router-cli

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

@tanstack/router-core

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

@tanstack/router-devtools

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

@tanstack/router-devtools-core

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

@tanstack/router-generator

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

@tanstack/router-plugin

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

@tanstack/router-ssr-query-core

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

@tanstack/router-utils

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

@tanstack/router-vite-plugin

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

@tanstack/server-functions-plugin

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

@tanstack/solid-router

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

@tanstack/solid-router-devtools

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

@tanstack/solid-router-ssr-query

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

@tanstack/solid-start

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

@tanstack/solid-start-client

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

@tanstack/solid-start-server

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

@tanstack/start-client-core

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

@tanstack/start-plugin-core

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

@tanstack/start-server-core

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

@tanstack/start-static-server-functions

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

@tanstack/start-storage-context

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

@tanstack/valibot-adapter

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

@tanstack/virtual-file-routes

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

@tanstack/zod-adapter

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

commit: 32a3f09

@birkskyum birkskyum merged commit fb0f069 into TanStack:main Nov 19, 2025
6 checks passed
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.

Route with server function as loader that throws notFound crashes route on HMR

2 participants