diff --git a/.changeset/virtual-pathless-layout-dotted-filename.md b/.changeset/virtual-pathless-layout-dotted-filename.md new file mode 100644 index 0000000000..eca8a4f8d2 --- /dev/null +++ b/.changeset/virtual-pathless-layout-dotted-filename.md @@ -0,0 +1,9 @@ +--- +'@tanstack/router-generator': patch +--- + +fix(router-generator): keep dotted virtual pathless layout filenames as one segment + +`layout('pathless.layout.tsx')` was running the layout id through `determineInitialRoutePath`, which treats unescaped dots as path separators. That turned `_pathless.layout` into `/_pathless/layout` and mounted children under a real `/layout` URL segment. Dots in layout ids are now escaped so the id stays a single pathless segment and children keep their original paths (e.g. `/subpath`). + +Fixes #7761. diff --git a/packages/router-generator/src/filesystem/virtual/getRouteNodes.ts b/packages/router-generator/src/filesystem/virtual/getRouteNodes.ts index 509b1be321..fafb6f4f1b 100644 --- a/packages/router-generator/src/filesystem/virtual/getRouteNodes.ts +++ b/packages/router-generator/src/filesystem/virtual/getRouteNodes.ts @@ -301,11 +301,21 @@ export async function getRouteNodesRecursive( node.id = ensureLeadingUnderScore(fileNameWithoutExt) } const lastSegment = node.id + // Layout ids from filenames like `pathless.layout.tsx` become + // `_pathless.layout`. Those dots are part of the identifier, not + // path separators — if we pass them through determineInitialRoutePath + // raw, SPLIT_REGEX turns them into `/` and children mount under a + // real `/layout` URL segment (regression from 1.145.1, #7761). + // Escape unbracketed dots so the id stays one pathless segment. + const segmentForPath = removeLeadingSlash(lastSegment).replace( + /(? rootRouteImport, +} as any) +const indexDotrouteRoute = indexDotrouteRouteImport.update({ + id: '/', + path: '/', + getParentRoute: () => rootRouteImport, +} as any) +const subpathDotrouteRoute = subpathDotrouteRouteImport.update({ + id: '/subpath', + path: '/subpath', + getParentRoute: () => pathlessDotlayoutRoute, +} as any) + +export interface FileRoutesByFullPath { + '/': typeof indexDotrouteRoute + '/subpath': typeof subpathDotrouteRoute +} +export interface FileRoutesByTo { + '/': typeof indexDotrouteRoute + '/subpath': typeof subpathDotrouteRoute +} +export interface FileRoutesById { + __root__: typeof rootRouteImport + '/': typeof indexDotrouteRoute + '/_pathless.layout': typeof pathlessDotlayoutRouteWithChildren + '/_pathless.layout/subpath': typeof subpathDotrouteRoute +} +export interface FileRouteTypes { + fileRoutesByFullPath: FileRoutesByFullPath + fullPaths: '/' | '/subpath' + fileRoutesByTo: FileRoutesByTo + to: '/' | '/subpath' + id: '__root__' | '/' | '/_pathless.layout' | '/_pathless.layout/subpath' + fileRoutesById: FileRoutesById +} +export interface RootRouteChildren { + indexDotrouteRoute: typeof indexDotrouteRoute + pathlessDotlayoutRoute: typeof pathlessDotlayoutRouteWithChildren +} + +declare module '@tanstack/react-router' { + interface FileRoutesByPath { + '/_pathless.layout': { + id: '/_pathless.layout' + path: '' + fullPath: '/' + preLoaderRoute: typeof pathlessDotlayoutRouteImport + parentRoute: typeof rootRouteImport + } + '/': { + id: '/' + path: '/' + fullPath: '/' + preLoaderRoute: typeof indexDotrouteRouteImport + parentRoute: typeof rootRouteImport + } + '/_pathless.layout/subpath': { + id: '/_pathless.layout/subpath' + path: '/subpath' + fullPath: '/subpath' + preLoaderRoute: typeof subpathDotrouteRouteImport + parentRoute: typeof pathlessDotlayoutRoute + } + } +} + +interface pathlessDotlayoutRouteChildren { + subpathDotrouteRoute: typeof subpathDotrouteRoute +} + +const pathlessDotlayoutRouteChildren: pathlessDotlayoutRouteChildren = { + subpathDotrouteRoute: subpathDotrouteRoute, +} + +const pathlessDotlayoutRouteWithChildren = + pathlessDotlayoutRoute._addFileChildren(pathlessDotlayoutRouteChildren) + +const rootRouteChildren: RootRouteChildren = { + indexDotrouteRoute: indexDotrouteRoute, + pathlessDotlayoutRoute: pathlessDotlayoutRouteWithChildren, +} +export const routeTree = rootRouteImport + ._addFileChildren(rootRouteChildren) + ._addFileTypes() diff --git a/packages/router-generator/tests/generator/virtual-pathless-layout-dotted-filename/routes/index.route.tsx b/packages/router-generator/tests/generator/virtual-pathless-layout-dotted-filename/routes/index.route.tsx new file mode 100644 index 0000000000..379428d7b2 --- /dev/null +++ b/packages/router-generator/tests/generator/virtual-pathless-layout-dotted-filename/routes/index.route.tsx @@ -0,0 +1,5 @@ +import { createFileRoute } from '@tanstack/react-router' + +export const Route = createFileRoute('/')({ + component: () => 'Index', +}) diff --git a/packages/router-generator/tests/generator/virtual-pathless-layout-dotted-filename/routes/pathless.layout.tsx b/packages/router-generator/tests/generator/virtual-pathless-layout-dotted-filename/routes/pathless.layout.tsx new file mode 100644 index 0000000000..3e0a504adc --- /dev/null +++ b/packages/router-generator/tests/generator/virtual-pathless-layout-dotted-filename/routes/pathless.layout.tsx @@ -0,0 +1,5 @@ +import { createFileRoute } from '@tanstack/react-router' + +export const Route = createFileRoute('/_pathless.layout')({ + component: () => 'PathlessLayout', +}) diff --git a/packages/router-generator/tests/generator/virtual-pathless-layout-dotted-filename/routes/root.route.tsx b/packages/router-generator/tests/generator/virtual-pathless-layout-dotted-filename/routes/root.route.tsx new file mode 100644 index 0000000000..9c657c7d5b --- /dev/null +++ b/packages/router-generator/tests/generator/virtual-pathless-layout-dotted-filename/routes/root.route.tsx @@ -0,0 +1,3 @@ +import { createRootRoute } from '@tanstack/react-router' + +export const Route = createRootRoute() diff --git a/packages/router-generator/tests/generator/virtual-pathless-layout-dotted-filename/routes/subpath.route.tsx b/packages/router-generator/tests/generator/virtual-pathless-layout-dotted-filename/routes/subpath.route.tsx new file mode 100644 index 0000000000..6c28fa210f --- /dev/null +++ b/packages/router-generator/tests/generator/virtual-pathless-layout-dotted-filename/routes/subpath.route.tsx @@ -0,0 +1,5 @@ +import { createFileRoute } from '@tanstack/react-router' + +export const Route = createFileRoute('/_pathless.layout/subpath')({ + component: () => 'Subpath', +})