Skip to content

virtualRouteConfig regression: pathless layout() with a dotted filename splits the dot into a new path segment #7761

Description

@paulgaspardo

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

  1. git clone, npm install, npm run dev
  2. Open http://localhost:5173/subpath
  3. Open http://localhost:5173/layout/subpath

There are 3 commits/tags on the repository with working and non-working versions

  1. First commit, working-1.144.0 - with the last version of @tanstack/router-plugin having the expected behavior in package.json
  2. first-broken-1.145.1 - with the first version where broken behavior is observed
  3. 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.

Metadata

Metadata

Assignees

No one assigned

    Labels

    No labels
    No labels

    Type

    No type

    Fields

    No fields configured for issues without a type.

    Projects

    No projects

    Milestone

    No milestone

    Relationships

    None yet

    Development

    No branches or pull requests

    Issue actions