Skip to content

Conversation

@davidclimbing
Copy link

@davidclimbing davidclimbing commented Jan 18, 2026

What Changed

Refactored new-process-route-tree.ts to ensure pathless routes (e.g., _authenticated) are correctly processed during fuzzy matching, allowing their notFoundComponent to trigger.

Files Changed:

  • packages/router-core/src/new-process-route-tree.ts
    • Removed skipOnParamError guard for pathless node creation.
    • Added explicit node.route assignment to pathless nodes.

Why

**Fixes #6351 **

Pathless layout routes were being skipped during fuzzy matching when a child route failed to match. This prevented their notFoundComponent from rendering, causing the error to bubble up to the root incorrectly.

This change ensures pathless nodes are properly flagged in the segment tree so they are considered valid candidates for handling notFound errors.

How Tested

New Tests:

  • Added packages/router-core/tests/pathless_fuzzy_matching.test.ts to verify:
    • Exact matching works for children.
    • Fuzzy matching correctly finds the pathless layout for missing paths.

Results:

  • ✅ All new tests passed.
  • ✅ All 846 regression tests in router-core passed.

**Closes #6351 **

Summary by CodeRabbit

  • Bug Fixes

    • Not-found handling now searches up the route hierarchy to render the nearest ancestor's not-found component when a child lacks one, improving fallback behavior for nested routes.
  • Tests

    • Added test confirming pathless layout routes inherit and use a parent's not-found component instead of the global default.

✏️ Tip: You can customize this high-level summary in your review settings.

@coderabbitai
Copy link
Contributor

coderabbitai bot commented Jan 18, 2026

📝 Walkthrough

Walkthrough

This PR changes not-found resolution to traverse up the route hierarchy to find the nearest route with a notFoundComponent, preferring that ancestor component, then the router's defaultNotFoundComponent, and finally the global default. The renderRouteNotFound signature now receives router as the first parameter. A test was added validating a pathless layout's notFound is used for children.

Changes

Cohort / File(s) Summary
Not-Found Resolution Logic
packages/react-router/src/renderRouteNotFound.tsx
Signature changed to renderRouteNotFound(router, route, data). Adds traversal (routeWithNotFound + while loop) to locate nearest ancestor with notFoundComponent. Renders ancestor's notFoundComponent if found; otherwise falls back to router.defaultNotFoundComponent or DefaultGlobalNotFound. Removes immediate dev warning and silences previous warning behavior.
Test Coverage
packages/react-router/tests/not-found.test.tsx
New test: "pathless layout route notFoundComponent is used when child has none (issue #6351)". Builds nested routes (root layout → authenticated layout with notFoundComponent → agents children), navigates to a missing child path, and asserts the authenticated layout's notFound component is rendered instead of router default.

Sequence Diagram(s)

mermaid
sequenceDiagram
participant Client as Client
participant Router as Router
participant RenderFn as renderRouteNotFound
participant RouteChain as Route Hierarchy
participant DefaultGlobal as DefaultGlobalNotFound
Client->>Router: navigate to /agents/non-existent
Router->>RenderFn: renderRouteNotFound(router, route, data)
RenderFn->>RouteChain: traverse ancestors for notFoundComponent
alt ancestor with notFoundComponent found
RouteChain->>RenderFn: return ancestor.notFoundComponent
RenderFn->>Client: render ancestor.notFoundComponent
else no ancestor notFoundComponent
RenderFn->>Router: check router.defaultNotFoundComponent
alt router.defaultFound present
Router->>RenderFn: return router.defaultNotFoundComponent
RenderFn->>Client: render router.defaultNotFoundComponent
else
RenderFn->>DefaultGlobal: use DefaultGlobalNotFound
RenderFn->>Client: render DefaultGlobalNotFound
end
end

Estimated code review effort

🎯 3 (Moderate) | ⏱️ ~20 minutes

Possibly related PRs

Suggested reviewers

  • schiller-manuel
  • Sheraff

Poem

🐰 I hopped the route tree, nose in the wind,
Found parent notFound where child had thinned,
No panic, no default pulled from afar—
Ancestors now answer when routes stray or spar. 🥕✨

🚥 Pre-merge checks | ✅ 5
✅ Passed checks (5 passed)
Check name Status Explanation
Description Check ✅ Passed Check skipped - CodeRabbit’s high-level summary is enabled.
Title check ✅ Passed Title accurately describes the main change: refactoring renderRouteNotFound to traverse the parent route chain for notFoundComponent lookup instead of checking only the current route.
Linked Issues check ✅ Passed The PR successfully addresses issue #6351 by modifying renderRouteNotFound to search up the route hierarchy for notFoundComponent, enabling pathless layout routes to have their notFoundComponent rendered before falling back to the global default.
Out of Scope Changes check ✅ Passed All changes are directly related to the stated objective: modifications to renderRouteNotFound signature and implementation in react-router package, and a new regression test validating the fix.
Docstring Coverage ✅ Passed Docstring coverage is 100.00% which is sufficient. The required threshold is 80.00%.

✏️ Tip: You can configure your own custom pre-merge checks in the settings.

✨ Finishing touches
  • 📝 Generate docstrings

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 Jan 18, 2026

🤖 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 14bfa97

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

☁️ Nx Cloud last updated this comment at 2026-01-22 17:20:51 UTC

@pkg-pr-new
Copy link

pkg-pr-new bot commented Jan 18, 2026

More templates

@tanstack/arktype-adapter

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

@tanstack/eslint-plugin-router

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

@tanstack/history

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

@tanstack/nitro-v2-vite-plugin

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

@tanstack/react-router

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

@tanstack/react-router-devtools

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

@tanstack/react-router-ssr-query

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

@tanstack/react-start

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

@tanstack/react-start-client

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

@tanstack/react-start-server

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

@tanstack/router-cli

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

@tanstack/router-core

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

@tanstack/router-devtools

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

@tanstack/router-devtools-core

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

@tanstack/router-generator

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

@tanstack/router-plugin

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

@tanstack/router-ssr-query-core

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

@tanstack/router-utils

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

@tanstack/router-vite-plugin

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

@tanstack/solid-router

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

@tanstack/solid-router-devtools

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

@tanstack/solid-router-ssr-query

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

@tanstack/solid-start

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

@tanstack/solid-start-client

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

@tanstack/solid-start-server

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

@tanstack/start-client-core

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

@tanstack/start-fn-stubs

npm i https://pkg.pr.new/TanStack/router/@tanstack/start-fn-stubs@6409

@tanstack/start-plugin-core

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

@tanstack/start-server-core

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

@tanstack/start-static-server-functions

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

@tanstack/start-storage-context

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

@tanstack/valibot-adapter

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

@tanstack/virtual-file-routes

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

@tanstack/vue-router

npm i https://pkg.pr.new/TanStack/router/@tanstack/vue-router@6409

@tanstack/vue-router-devtools

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

@tanstack/vue-router-ssr-query

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

@tanstack/vue-start

npm i https://pkg.pr.new/TanStack/router/@tanstack/vue-start@6409

@tanstack/vue-start-client

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

@tanstack/vue-start-server

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

@tanstack/zod-adapter

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

commit: 14bfa97

@Sheraff
Copy link
Contributor

Sheraff commented Jan 18, 2026

Thank your for your PR, the issue is clear and based on the video (#6351 (comment)) it does seem like a real bug to me.

However:

  1. in the test you are proposing in this PR, I would disagree that _layout should be fuzzy matched

In the video example we have these routes

__root__
  _authenticated
    agents.route
      skill-agent.index

So when the path to match is /agents/foo, then skill-agent.index is not matched, but agents.route should be (so the notFoundComponent in _authenticated should be used).

But in your case

__root__
  _layout
    child

matching /missing does not match child at all, so I don't see why it should include the _layout in its matched routes.

  1. following this explanation, I'm not entirely sure the fix you are proposing is correct. It might be. But skipping some nodes in the tree is good for performance, so ideally I'd like to understand why getNodeMatch does not fuzzy match the agents.route in the video case. I haven't tried, but it feels like it should pass this condition

// In fuzzy mode, track the best partial match we've found so far
if (
fuzzy &&
node.route &&
node.kind !== SEGMENT_TYPE_INDEX &&
isFrameMoreSpecific(bestFuzzy, frame)
) {
bestFuzzy = frame
}

@schiller-manuel
Copy link
Contributor

this PR breaks a lot of tests. check the CI output

@davidclimbing davidclimbing force-pushed the fix/pathless-route-notfound-component branch from 8204a4f to 963198c Compare January 22, 2026 15:06
@davidclimbing
Copy link
Author

  1. following this explanation, I'm not entirely sure the fix you are proposing is correct. It might be. But skipping some nodes in the tree is good for performance, so ideally I'd like to understand why getNodeMatch does not fuzzy match the agents.route in the video case. I haven't tried, but it feels like it should pass this condition

Thank you @Sheraff for your patient feedback!

What was wrong with my previous approach

I realize now that my previous test case was incorrect, and I am sorry for any confusion this caused. As @Sheraff pointed out, the test was flawed because the /missing path was structured in a way that it didn't actually fall under the _layout (pathless route) hierarchy. This led me to mistakenly investigate new-process-route-tree.ts, which was not the source of the problem. Your feedback helped me realize that the issue was specifically about how the rendering layer identifies the correct notFoundComponent within a nested, pathless route structure.

Root cause analysis (Issue #6351)

The problem exists in the renderRouteNotFound() function. When a "Not Found" state occurs (e.g., navigating to /agents/non-existent):

  1. The router correctly performs a fuzzy-match to the agents route.
  2. The agents route itself does not have a notFoundComponent.
  3. The Bug: Instead of traversing up to the immediate parent (the pathless _authenticated route), the code was skipping the parent hierarchy and falling back directly to the defaultNotFoundComponent defined in the root.
  4. Expected: Even for pathless routes, the router should check the parent chain recursively to find the nearest available notFoundComponent.

The fix

I have updated renderRouteNotFound() to correctly walk up the parent route chain. This ensures that pathless layout routes (like _authenticated) are properly checked for their notFoundComponent before reaching the global default.

// Recursively walk up the parent route chain to find the nearest notFoundComponent
let routeWithNotFound: AnyRoute | undefined = route
while (routeWithNotFound && !routeWithNotFound.options.notFoundComponent) {
  routeWithNotFound = routeWithNotFound.parentRoute
}

Test case

I added a robust test case in packages/react-router/tests/not-found.test.tsx that mirrors the structure reported in issue #6351:

  • Structure: __root___authenticated (pathless) → agentsskill-agent
  • Scenario: Navigate to /agents/non-existent.
  • Verification: Confirms that _authenticated's notFoundComponent is rendered, ensuring pathless parents are no longer ignored.

Files changed

@davidclimbing davidclimbing changed the title fix(router-core): ensure pathless routes are matched for notFoundComponents fix(react-router): walk parent route chain for notFoundComponent lookup Jan 22, 2026
@davidclimbing davidclimbing force-pushed the fix/pathless-route-notfound-component branch from 14bfa97 to af3f4da Compare January 23, 2026 00:34
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.

main.tsx defaultNotFound component is being rendered instead a root pathless route notFoundComponent

3 participants