Which project does this relate to?
Router
Describe the bug
Starting in @tanstack/router-plugin (@tanstack/router-generator) 1.145.1, if you have a pathless layout() route defined in a virtualRouteConfig whose filename contains a dot (e.g. pathless.layout.tsx) the generator now interprets the dot in the filename as a path separator, so child routes are mounted under an unexpected path.
Given this config:
// src/routes.ts
export const routes = rootRoute('root.route.tsx', [
index('index.route.tsx'),
layout('pathless.layout.tsx', [
route('subpath', 'subpath.route.tsx'),
]),
])
- 1.144.0 (working):
subpath is reachable at /subpath.
- 1.145.1 and later (broken):
subpath now resolves to /layout/subpath instead of /subpath.
Complete minimal reproducer
https://github.com/paulgaspardo/tsr-virtual-layout-dot-bug
Steps to Reproduce the Bug
- git clone, npm install, npm run dev
- Open
http://localhost:5173/subpath
- Open
http://localhost:5173/layout/subpath
There are 3 commits/tags on the repository with working and non-working versions
- First commit,
working-1.144.0 - with the last version of @tanstack/router-plugin having the expected behavior in package.json
first-broken-1.145.1 - with the first version where broken behavior is observed
- Last commit,
still-broken-1.168.19 - with the current version of @tanstack/router-plugin, where it's still broken
Expected behavior
- When opening
http://localhost:5173/subpath I expect to see the text "subpath.route.tsx"
- When opening
http://localhost:5173/layout/subpath I expect to see a Not Found error
Screenshots or Videos
No response
Platform
- Router Version: 1.170.17
- OS: macOS
- Browser: Firefox
- Browser Version: 152.0.5
- Bundler: vite
- Bundler Version: 8.1.3
Additional context
Claude points to src/filesystem/virtual/getRouteNodes.ts, the case "layout": branch of
getRouteNodesRecursive as the source of the bug:
Between 1.144.0 and 1.145.1 the layout branch was rewritten to run the layout id through determineInitialRoutePath():
// 1.144.0 (correct)
const lastSegment = node.id; // e.g. "_pathless.layout"
const routePath = `${parentRoutePath}/${removeLeadingSlash(lastSegment)}`;
// => "/_pathless.layout" (one segment, dot kept literal)
// 1.145.1+ (buggy)
const lastSegment = node.id; // "_pathless.layout"
const { routePath: escapedSegment, originalRoutePath: originalSegment } =
determineInitialRoutePath(removeLeadingSlash(lastSegment));
const routePath = `${parentRoutePath}${escapedSegment}`;
// => "/_pathless/layout" (two segments!)
determineInitialRoutePath (in src/utils.ts) splits on SPLIT_REGEX = /(?<!\[)\.(?!\])/g and re-joins parts with /. That regex exists to turn the physical file-based convention posts.index.tsx into nested path segments posts/index. But a virtual layout() id legitimately contains a dot as part of the name.layout naming convention, so the same split corrupts it.
Which project does this relate to?
Router
Describe the bug
Starting in
@tanstack/router-plugin(@tanstack/router-generator) 1.145.1, if you have a pathlesslayout()route defined in avirtualRouteConfigwhose filename contains a dot (e.g.pathless.layout.tsx) the generator now interprets the dot in the filename as a path separator, so child routes are mounted under an unexpected path.Given this config:
subpathis reachable at/subpath.subpathnow resolves to/layout/subpathinstead of/subpath.Complete minimal reproducer
https://github.com/paulgaspardo/tsr-virtual-layout-dot-bug
Steps to Reproduce the Bug
http://localhost:5173/subpathhttp://localhost:5173/layout/subpathThere are 3 commits/tags on the repository with working and non-working versions
working-1.144.0- with the last version of@tanstack/router-pluginhaving the expected behavior in package.jsonfirst-broken-1.145.1- with the first version where broken behavior is observedstill-broken-1.168.19- with the current version of@tanstack/router-plugin, where it's still brokenExpected behavior
http://localhost:5173/subpathI expect to see the text "subpath.route.tsx"http://localhost:5173/layout/subpathI expect to see a Not Found errorScreenshots or Videos
No response
Platform
Additional context
Claude points to
src/filesystem/virtual/getRouteNodes.ts, thecase "layout":branch ofgetRouteNodesRecursiveas the source of the bug:Between 1.144.0 and 1.145.1 the layout branch was rewritten to run the layout id through
determineInitialRoutePath():determineInitialRoutePath(insrc/utils.ts) splits onSPLIT_REGEX = /(?<!\[)\.(?!\])/gand re-joins parts with/. That regex exists to turn the physical file-based conventionposts.index.tsxinto nested path segmentsposts/index. But a virtuallayout()id legitimately contains a dot as part of thename.layoutnaming convention, so the same split corrupts it.