Skip to content

Generated type FileRoutesByFullPath doesn't match runtime type #3780

Closed

Description

@am-a

Which project does this relate to?

Router

Describe the bug

Problem

router.routesByPath is defined as RoutesByPath<TRouteTree> which includes trailing '/' for index routes. However this does not match the runtime type which removes trailing slashes.

Description

Given the following structure:

foo/
└── bar/
    ├── index.tsx
    ├── route.tsx
    └── __root.tsx

generates:

export interface FileRoutesByFullPath {
  '/bar': typeof fooBarRouteRouteWithChildren
  '/bar/': typeof fooBarIndexRoute
}

but in:

https://github.com/TanStack/router/blob/4568b38b69b5a649484a8168f4765de97c10f193/packages/react-router/src/router.ts

       if (!childRoute.isRoot && childRoute.path) {
          const trimmedFullPath = trimPathRight(childRoute.fullPath)
          if (
            !(this.routesByPath as any)[trimmedFullPath] ||
            childRoute.fullPath.endsWith('/')
          ) {
            ;(this.routesByPath as any)[trimmedFullPath] = childRoute
          }
        }

we can see that the trimmed path is the key that is used for the object at runtime.

Your Example Website or App

https://stackblitz.com/edit/tanstack-router-nychtyxe

Steps to Reproduce the Bug or Issue

  1. Create a router project with an index route matching above structure
  2. Observe that Types does not match runtime types

Expected behavior

The typing for the property should match the runtime type i.e. without slashes

Screenshots or Videos

Image

Platform

All

Additional context

The included link goes to a StackBlitz where you can quickly see the problem in action by comparing the interface and the actual runtime types.

Activity

  1. schiller-manuel commented on Mar 15, 2025

    @schiller-manuel
    Collaborator

    same as #3005 ?

  2. am-a commented on Mar 15, 2025

    @am-a
    Author

    same as #3005 ?

    Not quite the same, almost the inverse. Probably fixed in the same area though.

  3. codeart1st commented on Oct 13, 2025

    @codeart1st

    This issue seems still relevant

      const parentFullPath = useParentMatches({
        select: (matches) => {
          const lastMatch = matches.pop()
          const fullPath = lastMatch?.fullPath
          // https://github.com/TanStack/router/issues/3780
          return (fullPath as RemoveTrailingSlashes<typeof fullPath>) || PATH_ROOT
        }
      })
      return (
        <RouterLink to={parentFullPath} {...props}>
          {children}
        </RouterLink>
      )
    
  4. SemStassen commented on Mar 2, 2026

    @SemStassen

    Still relevant. This is my current workaround.

    import { useRouter, type RegisteredRouter } from "@tanstack/react-router";
    
    type FileRoutesByFullPath = RegisteredRouter["routesByPath"];
    type RouteFullPathWithSlash = keyof FileRoutesByFullPath;
    type TrimSlash<T extends string> = T extends `${infer P}/` ? P : T;
    
    /**
     * Type-safe wrapper around `routesByPath` that handles the mismatch between
     * the generated types (which include trailing slashes for index routes) and
     * the runtime object (which trims them).
     *
     * @see https://github.com/TanStack/router/issues/3780
     */
    function useRoutePath<T extends RouteFullPathWithSlash>(path: T): TrimSlash<T> {
      const { routesByPath } = useRouter();
      // The generated FileRoutesByFullPath type includes trailing slashes for index
      // routes, but routesByPath trims them at runtime — so we trim before lookup.
      const trimmed = path.replace(/\/$/, "") as T;
    
      return routesByPath[trimmed].fullPath as TrimSlash<T>;
    }
    
    export { useRoutePath };
  5. schiller-manuel commented on Mar 7, 2026

    @schiller-manuel
    Collaborator

    closing this as neither those types nor the router properties are public API.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Metadata

Metadata

Assignees

No one assigned

    Labels

    No labels
    No labels

    Type

    No type

    Projects

    No projects

      Milestone

      No milestone

      Relationships

      None yet

      Development

      No branches or pull requests

      Issue actions