diff --git a/packages/router-generator/src/generator.ts b/packages/router-generator/src/generator.ts index e7009eb8369..62cab9a106d 100644 --- a/packages/router-generator/src/generator.ts +++ b/packages/router-generator/src/generator.ts @@ -1422,19 +1422,33 @@ ${acc.routeTree.map((child) => `${child.variableName}Route: typeof ${getResolved ) { let parentRoute = hasParentRoute(prefixMap, node, node.routePath) - // Fallback: check acc.routeNodesByPath for parents not in prefixMap - // This handles virtual routes created from lazy-only files that weren't - // in the initial prefixMap build - if (!parentRoute && node.routePath) { - let searchPath = node.routePath - while (searchPath.length > 0) { - const lastSlash = searchPath.lastIndexOf('/') - if (lastSlash <= 0) break - searchPath = searchPath.substring(0, lastSlash) - const candidate = acc.routeNodesByPath.get(searchPath) - if (candidate && candidate.routePath !== node.routePath) { + // Check routeNodesByPath for a closer parent that may not be in prefixMap. + // + // Why: The prefixMap excludes lazy routes by design. When lazy-only routes are + // nested inside a pathless layout, the virtual route created from the lazy file + // won't be in the prefixMap, but it will be in routeNodesByPath. + // + // Example: Given files _layout/path.lazy.tsx and _layout/path.index.lazy.tsx: + // - prefixMap contains: /_layout (from route.tsx) + // - routeNodesByPath contains: /_layout AND /_layout/path (virtual from lazy) + // - For /_layout/path/, hasParentRoute returns /_layout (wrong) + // - But the correct parent is /_layout/path (the virtual route from path.lazy.tsx) + // + // Optimization: Only search if we might find a closer parent. The search walks + // up from the immediate parent path, so if the first candidate matches what + // prefixMap found, there's no closer parent to find. + if (node.routePath) { + const lastSlash = node.routePath.lastIndexOf('/') + if (lastSlash > 0) { + const immediateParentPath = node.routePath.substring(0, lastSlash) + const candidate = acc.routeNodesByPath.get(immediateParentPath) + if ( + candidate && + candidate.routePath !== node.routePath && + candidate !== parentRoute + ) { + // Found a closer parent in routeNodesByPath that differs from prefixMap result parentRoute = candidate - break } } } diff --git a/packages/router-generator/tests/generator/lazy-only-layout-and-index-in-pathless/routeTree.snapshot.ts b/packages/router-generator/tests/generator/lazy-only-layout-and-index-in-pathless/routeTree.snapshot.ts new file mode 100644 index 00000000000..2de1aa6bb67 --- /dev/null +++ b/packages/router-generator/tests/generator/lazy-only-layout-and-index-in-pathless/routeTree.snapshot.ts @@ -0,0 +1,118 @@ +/* eslint-disable */ + +// @ts-nocheck + +// noinspection JSUnusedGlobalSymbols + +// This file was automatically generated by TanStack Router. +// You should NOT make any changes in this file as it will be overwritten. +// Additionally, you should also exclude this file from your linter and/or formatter to prevent it from being checked or modified. + +import { createFileRoute } from '@tanstack/react-router' + +import { Route as rootRouteImport } from './routes/__root' +import { Route as LayoutRouteRouteImport } from './routes/_layout/route' + +const LayoutPathLazyRouteImport = createFileRoute('/_layout/path')() +const LayoutPathIndexLazyRouteImport = createFileRoute('/_layout/path/')() + +const LayoutRouteRoute = LayoutRouteRouteImport.update({ + id: '/_layout', + getParentRoute: () => rootRouteImport, +} as any) +const LayoutPathLazyRoute = LayoutPathLazyRouteImport.update({ + id: '/path', + path: '/path', + getParentRoute: () => LayoutRouteRoute, +} as any).lazy(() => import('./routes/_layout/path.lazy').then((d) => d.Route)) +const LayoutPathIndexLazyRoute = LayoutPathIndexLazyRouteImport.update({ + id: '/', + path: '/', + getParentRoute: () => LayoutPathLazyRoute, +} as any).lazy(() => + import('./routes/_layout/path.index.lazy').then((d) => d.Route), +) + +export interface FileRoutesByFullPath { + '/': typeof LayoutRouteRouteWithChildren + '/path': typeof LayoutPathLazyRouteWithChildren + '/path/': typeof LayoutPathIndexLazyRoute +} +export interface FileRoutesByTo { + '/': typeof LayoutRouteRouteWithChildren + '/path': typeof LayoutPathIndexLazyRoute +} +export interface FileRoutesById { + __root__: typeof rootRouteImport + '/_layout': typeof LayoutRouteRouteWithChildren + '/_layout/path': typeof LayoutPathLazyRouteWithChildren + '/_layout/path/': typeof LayoutPathIndexLazyRoute +} +export interface FileRouteTypes { + fileRoutesByFullPath: FileRoutesByFullPath + fullPaths: '/' | '/path' | '/path/' + fileRoutesByTo: FileRoutesByTo + to: '/' | '/path' + id: '__root__' | '/_layout' | '/_layout/path' | '/_layout/path/' + fileRoutesById: FileRoutesById +} +export interface RootRouteChildren { + LayoutRouteRoute: typeof LayoutRouteRouteWithChildren +} + +declare module '@tanstack/react-router' { + interface FileRoutesByPath { + '/_layout': { + id: '/_layout' + path: '' + fullPath: '/' + preLoaderRoute: typeof LayoutRouteRouteImport + parentRoute: typeof rootRouteImport + } + '/_layout/path': { + id: '/_layout/path' + path: '/path' + fullPath: '/path' + preLoaderRoute: typeof LayoutPathLazyRouteImport + parentRoute: typeof LayoutRouteRoute + } + '/_layout/path/': { + id: '/_layout/path/' + path: '/' + fullPath: '/path/' + preLoaderRoute: typeof LayoutPathIndexLazyRouteImport + parentRoute: typeof LayoutPathLazyRoute + } + } +} + +interface LayoutPathLazyRouteChildren { + LayoutPathIndexLazyRoute: typeof LayoutPathIndexLazyRoute +} + +const LayoutPathLazyRouteChildren: LayoutPathLazyRouteChildren = { + LayoutPathIndexLazyRoute: LayoutPathIndexLazyRoute, +} + +const LayoutPathLazyRouteWithChildren = LayoutPathLazyRoute._addFileChildren( + LayoutPathLazyRouteChildren, +) + +interface LayoutRouteRouteChildren { + LayoutPathLazyRoute: typeof LayoutPathLazyRouteWithChildren +} + +const LayoutRouteRouteChildren: LayoutRouteRouteChildren = { + LayoutPathLazyRoute: LayoutPathLazyRouteWithChildren, +} + +const LayoutRouteRouteWithChildren = LayoutRouteRoute._addFileChildren( + LayoutRouteRouteChildren, +) + +const rootRouteChildren: RootRouteChildren = { + LayoutRouteRoute: LayoutRouteRouteWithChildren, +} +export const routeTree = rootRouteImport + ._addFileChildren(rootRouteChildren) + ._addFileTypes() diff --git a/packages/router-generator/tests/generator/lazy-only-layout-and-index-in-pathless/routes/__root.tsx b/packages/router-generator/tests/generator/lazy-only-layout-and-index-in-pathless/routes/__root.tsx new file mode 100644 index 00000000000..9c657c7d5b4 --- /dev/null +++ b/packages/router-generator/tests/generator/lazy-only-layout-and-index-in-pathless/routes/__root.tsx @@ -0,0 +1,3 @@ +import { createRootRoute } from '@tanstack/react-router' + +export const Route = createRootRoute() diff --git a/packages/router-generator/tests/generator/lazy-only-layout-and-index-in-pathless/routes/_layout/path.index.lazy.tsx b/packages/router-generator/tests/generator/lazy-only-layout-and-index-in-pathless/routes/_layout/path.index.lazy.tsx new file mode 100644 index 00000000000..86103a57963 --- /dev/null +++ b/packages/router-generator/tests/generator/lazy-only-layout-and-index-in-pathless/routes/_layout/path.index.lazy.tsx @@ -0,0 +1,5 @@ +import { createLazyFileRoute } from '@tanstack/react-router' + +export const Route = createLazyFileRoute('/_layout/path/')({ + component: () => 'Path Index', +}) diff --git a/packages/router-generator/tests/generator/lazy-only-layout-and-index-in-pathless/routes/_layout/path.lazy.tsx b/packages/router-generator/tests/generator/lazy-only-layout-and-index-in-pathless/routes/_layout/path.lazy.tsx new file mode 100644 index 00000000000..8778295a8dd --- /dev/null +++ b/packages/router-generator/tests/generator/lazy-only-layout-and-index-in-pathless/routes/_layout/path.lazy.tsx @@ -0,0 +1,5 @@ +import { createLazyFileRoute } from '@tanstack/react-router' + +export const Route = createLazyFileRoute('/_layout/path')({ + component: () => 'Path Layout', +}) diff --git a/packages/router-generator/tests/generator/lazy-only-layout-and-index-in-pathless/routes/_layout/route.tsx b/packages/router-generator/tests/generator/lazy-only-layout-and-index-in-pathless/routes/_layout/route.tsx new file mode 100644 index 00000000000..da869f097e0 --- /dev/null +++ b/packages/router-generator/tests/generator/lazy-only-layout-and-index-in-pathless/routes/_layout/route.tsx @@ -0,0 +1,9 @@ +import { Outlet, createFileRoute } from '@tanstack/react-router' + +export const Route = createFileRoute('/_layout')({ + component: RouteComponent, +}) + +function RouteComponent() { + return +}