Skip to content

route.tsx and index.tsx conflict when using virtual routing and physical() #5421

@adharris

Description

@adharris

Which project does this relate to?

Router

Describe the bug

tldr: When mixing virtual routing and file based routing using the physical virtual route type, the root of the mounted folder cannot have both an index.tsx and a route.tsx. This setup is supported in file-based routing, and is useful for adding layout or context to all child routes.


I'm working on a project with many mostly independent features, each of with has its own set of routes. In my repository, I'd like to have the routes for each feature isolated into the subfolder scoped to to that feature. For example:

/src
├── app/
│   └── routes/  # global routes not related to a feature
└── features
   ├── feature-a
   │     └── routes/  # routes for feature-a
   └── feature-b
         └── routes/  # routes for feature-b

Virtual file routes seem like a great fit for this:

export default rootRoute('app/routes/root.tsx', [
   // ... global routes
    physical('feature-a', 'features/feature-a/routes'),
    physical('feature-b', 'features/feature-b/routes'),
]);

However, when i do this, i can no longer have both a route.tsx and an index.tsx in the feature/*/routes folders. When I do, i get an error like:

Error: Conflicting configuration paths were found for the following routes: "feature", "feature".
Please ensure each Route has a unique full path.
Conflicting files: 
 /home/projects/tanstack-router-xuwk6tn2/src/routes/feature/index.tsx
 /home/projects/tanstack-router-xuwk6tn2/src/routes/feature/route.tsx

This feels broken, when the exact same folder structure works with file based routing.

As a workaround, I can use a pathless route instead of a route.tsx. ie, _layout.tsx and _layout.index.tsx. But this feels like extra verbosity that shouldn't be needed. Adding a shared layout, loader, or context to all children seems like it should be easier.

Your Example Website or App

https://stackblitz.com/edit/tanstack-router-xuwk6tn2?file=src%2Froutes.ts

Steps to Reproduce the Bug or Issue

The attached stackblitz can be swapped between virtual file routes and file based routing by commenting out the virtualRouteConfig option in vite.config.js, and then restarting vite. Vite will automatically scroll the terminal past the router plugin errors, so it will look successful, but if you scroll up yo can see the conflicting path error.

In src/routes.ts, you can swap between mounting the feature folder using physical() and mounting the feature routes directly using route() and feature().

Note that when disabling file based routing, routeTree.gen.ts is identical (ignoring casing) to the generated version when using route()/index(). Also note that when using physical() and getting the error, routeTree.gen.ts is not actually updated, so the app still works (confusing).

Expected behavior

When mounting a directory into a virtual route tree using physical('path', 'folder'), and folder contains both an index.tsx and a route.tsx, the route tree should still generate correctly, with the route.tsx being treated as the parent route for index.tsx, and all other routes in the folder.

Screenshots or Videos

No response

Platform

  • @tanstack/react-router: 1.132.47
  • @tanstack/router-plugin: 1.132.51
  • @tanstack/virtual-file-routes: 1.131.2
  • OS: OSX 15.5 ARM
  • Browser: Chrome
  • Browser Version: 140.0.7339.209 ARM
  • Bundler: Vite
  • Bundler Version: 7.1.7

Additional context

No response

Metadata

Metadata

Assignees

No one assigned

    Labels

    Type

    No type

    Projects

    No projects

    Milestone

    No milestone

    Relationships

    None yet

    Development

    No branches or pull requests

    Issue actions