diff --git a/packages/router-generator/src/generator.ts b/packages/router-generator/src/generator.ts index e9efdea4456..60c2cf4a5ac 100644 --- a/packages/router-generator/src/generator.ts +++ b/packages/router-generator/src/generator.ts @@ -1439,21 +1439,31 @@ ${acc.routeTree.map((child) => `${child.variableName}Route: typeof ${getResolved // - 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. + // We walk up the path segments to find the closest registered parent. This handles + // cases where multiple path segments (e.g., $a/$b) don't have intermediate routes. 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 + 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) { + // Found a parent in routeNodesByPath + // If it's different from what prefixMap found AND is a closer match, use it + if (candidate !== parentRoute) { + // Check if this candidate is a closer parent than what prefixMap found + // (longer path prefix means closer parent) + if ( + !parentRoute || + (candidate.routePath?.length ?? 0) > + (parentRoute.routePath?.length ?? 0) + ) { + parentRoute = candidate + } + } + break } } } diff --git a/packages/router-generator/tests/generator/lazy-multi-slug-params/routeTree.snapshot.ts b/packages/router-generator/tests/generator/lazy-multi-slug-params/routeTree.snapshot.ts new file mode 100644 index 00000000000..6eb3a308bd6 --- /dev/null +++ b/packages/router-generator/tests/generator/lazy-multi-slug-params/routeTree.snapshot.ts @@ -0,0 +1,108 @@ +/* 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' + +const SubRouteLazyRouteImport = createFileRoute('/sub')() +const SubTestLazyRouteImport = createFileRoute('/sub/test')() +const SubABLazyRouteImport = createFileRoute('/sub/$a/$b')() + +const SubRouteLazyRoute = SubRouteLazyRouteImport.update({ + id: '/sub', + path: '/sub', + getParentRoute: () => rootRouteImport, +} as any).lazy(() => import('./routes/sub/route.lazy').then((d) => d.Route)) +const SubTestLazyRoute = SubTestLazyRouteImport.update({ + id: '/test', + path: '/test', + getParentRoute: () => SubRouteLazyRoute, +} as any).lazy(() => import('./routes/sub/test.lazy').then((d) => d.Route)) +const SubABLazyRoute = SubABLazyRouteImport.update({ + id: '/$a/$b', + path: '/$a/$b', + getParentRoute: () => SubRouteLazyRoute, +} as any).lazy(() => import('./routes/sub/$a.$b.lazy').then((d) => d.Route)) + +export interface FileRoutesByFullPath { + '/sub': typeof SubRouteLazyRouteWithChildren + '/sub/test': typeof SubTestLazyRoute + '/sub/$a/$b': typeof SubABLazyRoute +} +export interface FileRoutesByTo { + '/sub': typeof SubRouteLazyRouteWithChildren + '/sub/test': typeof SubTestLazyRoute + '/sub/$a/$b': typeof SubABLazyRoute +} +export interface FileRoutesById { + __root__: typeof rootRouteImport + '/sub': typeof SubRouteLazyRouteWithChildren + '/sub/test': typeof SubTestLazyRoute + '/sub/$a/$b': typeof SubABLazyRoute +} +export interface FileRouteTypes { + fileRoutesByFullPath: FileRoutesByFullPath + fullPaths: '/sub' | '/sub/test' | '/sub/$a/$b' + fileRoutesByTo: FileRoutesByTo + to: '/sub' | '/sub/test' | '/sub/$a/$b' + id: '__root__' | '/sub' | '/sub/test' | '/sub/$a/$b' + fileRoutesById: FileRoutesById +} +export interface RootRouteChildren { + SubRouteLazyRoute: typeof SubRouteLazyRouteWithChildren +} + +declare module '@tanstack/react-router' { + interface FileRoutesByPath { + '/sub': { + id: '/sub' + path: '/sub' + fullPath: '/sub' + preLoaderRoute: typeof SubRouteLazyRouteImport + parentRoute: typeof rootRouteImport + } + '/sub/test': { + id: '/sub/test' + path: '/test' + fullPath: '/sub/test' + preLoaderRoute: typeof SubTestLazyRouteImport + parentRoute: typeof SubRouteLazyRoute + } + '/sub/$a/$b': { + id: '/sub/$a/$b' + path: '/$a/$b' + fullPath: '/sub/$a/$b' + preLoaderRoute: typeof SubABLazyRouteImport + parentRoute: typeof SubRouteLazyRoute + } + } +} + +interface SubRouteLazyRouteChildren { + SubTestLazyRoute: typeof SubTestLazyRoute + SubABLazyRoute: typeof SubABLazyRoute +} + +const SubRouteLazyRouteChildren: SubRouteLazyRouteChildren = { + SubTestLazyRoute: SubTestLazyRoute, + SubABLazyRoute: SubABLazyRoute, +} + +const SubRouteLazyRouteWithChildren = SubRouteLazyRoute._addFileChildren( + SubRouteLazyRouteChildren, +) + +const rootRouteChildren: RootRouteChildren = { + SubRouteLazyRoute: SubRouteLazyRouteWithChildren, +} +export const routeTree = rootRouteImport + ._addFileChildren(rootRouteChildren) + ._addFileTypes() diff --git a/packages/router-generator/tests/generator/lazy-multi-slug-params/routes/__root.tsx b/packages/router-generator/tests/generator/lazy-multi-slug-params/routes/__root.tsx new file mode 100644 index 00000000000..87099187f86 --- /dev/null +++ b/packages/router-generator/tests/generator/lazy-multi-slug-params/routes/__root.tsx @@ -0,0 +1,2 @@ +import { createRootRoute } from '@tanstack/react-router' +export const Route = createRootRoute() diff --git a/packages/router-generator/tests/generator/lazy-multi-slug-params/routes/sub/$a.$b.lazy.tsx b/packages/router-generator/tests/generator/lazy-multi-slug-params/routes/sub/$a.$b.lazy.tsx new file mode 100644 index 00000000000..383e4833f75 --- /dev/null +++ b/packages/router-generator/tests/generator/lazy-multi-slug-params/routes/sub/$a.$b.lazy.tsx @@ -0,0 +1,4 @@ +import { createLazyFileRoute } from '@tanstack/react-router' +export const Route = createLazyFileRoute('/sub/$a/$b')({ + component: () => 'Multi Slug Route', +}) diff --git a/packages/router-generator/tests/generator/lazy-multi-slug-params/routes/sub/route.lazy.tsx b/packages/router-generator/tests/generator/lazy-multi-slug-params/routes/sub/route.lazy.tsx new file mode 100644 index 00000000000..c6624d5ac6e --- /dev/null +++ b/packages/router-generator/tests/generator/lazy-multi-slug-params/routes/sub/route.lazy.tsx @@ -0,0 +1,4 @@ +import { createLazyFileRoute } from '@tanstack/react-router' +export const Route = createLazyFileRoute('/sub')({ + component: () => 'Sub Layout', +}) diff --git a/packages/router-generator/tests/generator/lazy-multi-slug-params/routes/sub/test.lazy.tsx b/packages/router-generator/tests/generator/lazy-multi-slug-params/routes/sub/test.lazy.tsx new file mode 100644 index 00000000000..431a85a776a --- /dev/null +++ b/packages/router-generator/tests/generator/lazy-multi-slug-params/routes/sub/test.lazy.tsx @@ -0,0 +1,4 @@ +import { createLazyFileRoute } from '@tanstack/react-router' +export const Route = createLazyFileRoute('/sub/test')({ + component: () => 'Test Route', +})