Which project does this relate to?
Router
Describe the bug
When a route's path uses the documented [_] escape to keep a literal leading underscore in the URL, the escape is silently ignored if any ancestor is a pathless layout (a route whose path starts with _). The leading underscore is stripped from the generated fullPath.
This reproduces with virtual file routes; I haven't tested file-based routing but the offending code (inferFullPath / removeUnderscoresWithEscape) is shared.
Complete minimal reproducer
https://stackblitz.com/github/dmarcey/tanstack-router-underscore-reproducer
Steps to Reproduce the Bug
// routes.ts
import {layout, rootRoute, route} from '@tanstack/virtual-file-routes'
export const routes = rootRoute('root.tsx', [
// ✅ Works: '/[_]foo' → URL '/_foo'
route('/[_]foo', 'foo.tsx'),
// ❌ Broken: '/[_]bar' under a pathless layout → URL '/bar'
layout('_layout', 'layout.tsx', [
route('/[_]bar', 'bar.tsx'),
]),
])
After running the generator, the routeTree.gen.ts FileRoutesByFullPath includes '/bar' for bar.tsx instead of the expected '/_bar'.
- Configure virtual file routes as above.
- Run the router generator (
@tanstack/router-generator 1.166.41).
- Inspect the generated
fullPath / path entries — the underscore is stripped from any [_]… segment that lives under a pathless layout.
I also reproduced this directly against the published utils:
import * as utils from '@tanstack/router-generator/dist/esm/utils.js'
// Top-level (correct)
utils.inferFullPath({ routePath: '/_foo', originalRoutePath: '/[_]foo' })
// → '/_foo' ✅
// Nested under pathless layout (incorrect)
utils.inferFullPath({ routePath: '/_layout/_foo', originalRoutePath: '/_layout/[_]foo' })
// → '/foo' ❌ (expected '/_foo')
If you're using the app in the stackblitz, you can observe that:
- There is a type error when trying to define a
<Link /> with a to for _nested.
- Hard navigating directly to
/_nested fails.
Expected behavior
'/[_]foo' should produce '/_foo' regardless of whether the route is nested under a pathless layout.
Screenshots or Videos
No response
Platform
@tanstack/router-generator: 1.166.41
@tanstack/virtual-file-routes: 1.161.7
- Node: 24.8.0
- OS: Ubuntu 24.04
Additional context
Here's my best guess at the underlying problem:
In inferFullPath (packages/router-generator/src/utils.ts):
const fullPath = removeGroups(
removeUnderscoresWithEscape(
removeLayoutSegmentsWithEscape(routeNode.routePath, routeNode.originalRoutePath),
routeNode.originalRoutePath, // not adjusted to match
),
)
removeLayoutSegmentsWithEscape filters segments out of routePath, but removeUnderscoresWithEscape is then handed the unmodified originalRoutePath and uses segment array indices to look up the escape state:
return routeSegments.map((segment, i) => {
const originalSegment = originalSegments[i] || ''
const leadingEscaped = hasEscapedLeadingUnderscore(originalSegment)
// …
if (result.startsWith('_') && !leadingEscaped) result = result.slice(1)
})
After the layout segment is dropped from routeSegments, the indexes no longer align with originalSegments, so hasEscapedLeadingUnderscore is checked against the wrong segment and returns false.
A fix would be to either (a) also strip the corresponding original segments inside removeLayoutSegmentsWithEscape and pass the trimmed originalRoutePath along, or (b) align segments by content rather than by index.
Which project does this relate to?
Router
Describe the bug
When a route's path uses the documented
[_]escape to keep a literal leading underscore in the URL, the escape is silently ignored if any ancestor is a pathless layout (a route whose path starts with_). The leading underscore is stripped from the generatedfullPath.This reproduces with virtual file routes; I haven't tested file-based routing but the offending code (
inferFullPath/removeUnderscoresWithEscape) is shared.Complete minimal reproducer
https://stackblitz.com/github/dmarcey/tanstack-router-underscore-reproducer
Steps to Reproduce the Bug
After running the generator, the
routeTree.gen.tsFileRoutesByFullPathincludes'/bar'forbar.tsxinstead of the expected'/_bar'.@tanstack/router-generator1.166.41).fullPath/pathentries — the underscore is stripped from any[_]…segment that lives under a pathless layout.I also reproduced this directly against the published utils:
If you're using the app in the stackblitz, you can observe that:
<Link />with atofor_nested./_nestedfails.Expected behavior
'/[_]foo'should produce'/_foo'regardless of whether the route is nested under a pathless layout.Screenshots or Videos
No response
Platform
@tanstack/router-generator: 1.166.41@tanstack/virtual-file-routes: 1.161.7Additional context
Here's my best guess at the underlying problem:
In
inferFullPath(packages/router-generator/src/utils.ts):removeLayoutSegmentsWithEscapefilters segments out ofroutePath, butremoveUnderscoresWithEscapeis then handed the unmodifiedoriginalRoutePathand uses segment array indices to look up the escape state:After the layout segment is dropped from
routeSegments, the indexes no longer align withoriginalSegments, sohasEscapedLeadingUnderscoreis checked against the wrong segment and returns false.A fix would be to either (a) also strip the corresponding original segments inside
removeLayoutSegmentsWithEscapeand pass the trimmedoriginalRoutePathalong, or (b) align segments by content rather than by index.