Skip to content

Virtual routes config doesn't respect explicit sibling relationships - auto-nests based on path matching #5822

@palpycus

Description

@palpycus

Which project does this relate to?

Router

Describe the bug

When using virtual routes configuration (routes.config.ts), TanStack Router automatically nests routes based on path matching, even when they are explicitly configured as siblings within the same layout. Current Behavior: Routes like /posts and /posts/$id are automatically nested (detail route becomes child of list route) based on path prefix matching, despite being explicitly defined as siblings in the virtual routes config:

// routes.config.ts
layout('_main', 'domains/main/layout.tsx', [
  route('/posts', 'domains/main/features/posts/pages/PostsPage.tsx'),
  route('/posts/$id', 'domains/main/features/posts/pages/PostDetailPage.tsx'),  // Should be sibling, not child
])
Generated Route Tree:
// routeTree.gen.ts
const domainsMainFeaturesPostsPagesPostDetailPageRoute =
  domainsMainFeaturesPostsPagesPostDetailPageRouteImport.update({
    id: '/$id',
    path: '/$id',
    getParentRoute: () => domainsMainFeaturesPostsPagesPostsPageRoute,  // ❌ Nested under /posts
  } as any)

Expected Behavior: Routes should respect the explicit parent-child relationships defined in the virtual routes config. When two routes are siblings in the config array, they should remain siblings in the generated route tree, regardless of path matching:

// Expected generated code
const domainsMainFeaturesPostsPagesPostDetailPageRoute =
  domainsMainFeaturesPostsPagesPostDetailPageRouteImport.update({
    id: '/posts/$id',
    path: '/posts/$id',
    getParentRoute: () => domainsMainLayoutRoute,  // ✅ Sibling under layout
  } as any)

Use Case: A common pattern is having a layout that wraps multiple related pages:

/posts - List view with table/grid
/posts/$id - Detail view with form/content
/posts/user/$userId - Filtered list by user

Environment:
@tanstack/react-router: ^1.135.2
@tanstack/router-plugin: ^1.135.2

Suggested Fix:
The route generator should track which routes were explicitly provided as children in the virtual config and skip path-based parent inference for those routes. The explicit parent from the config should always take precedence over path-based matching.

Your Example Website or App

no need

Steps to Reproduce the Bug or Issue

export const routes = rootRoute('root.tsx', [
layout('_main', 'domains/main/layout.tsx', [
route('/todos', '...'),
route('/todos/$id', '...'),
]),
])

Expected behavior

The route generator should track which routes were explicitly provided as children in the virtual config and skip path-based parent inference for those routes. The explicit parent from the config should always take precedence over path-based matching.

Screenshots or Videos

No response

Platform

Environment:
@tanstack/react-router: ^1.135.2
@tanstack/router-plugin: ^1.135.2

Additional context

No response

Metadata

Metadata

Assignees

No one assigned

    Labels

    No labels
    No labels

    Type

    No type

    Projects

    No projects

    Milestone

    No milestone

    Relationships

    None yet

    Development

    No branches or pull requests

    Issue actions