router icon indicating copy to clipboard operation
router copied to clipboard

fix(router-core): correct TypeScript param inference for routes nested inside _pathless directories (#6011)

Open devel-maverick opened this issue 1 month ago • 5 comments

Description

This PR fixes a TypeScript inference issue where route parameters are not detected when a route file is placed inside a _pathless directory. Runtime behavior was already correct, but TypeScript always inferred router.matchRoute() as false, causing parameter access to fail at compile time.

Problem

When using a structure like:

routes/_pathless/nested/$id.tsx

the type-level path becomes:

"/_pathless/nested/$id"

Even though _pathless is removed from the actual URL at runtime, the type system still sees it as a real segment. This prevents $id from being extracted, producing:

const match = router.matchRoute({ 
  to: "/nested/$id", 
  params: { id: "123" } 
})

// match inferred as false only
match?.id // Property 'id' does not exist

Fix

We normalize the type-level path by stripping _pathless before param extraction:

StripPathless<TFrom>, StripPathless<TTo>

Then apply this normalization inside ValidateParams:

export type ValidateParams<
  TRouter extends AnyRouter = RegisteredRouter,
  TTo extends string | undefined = undefined,
  TFrom extends string = string,
> = PathParamOptions<
  TRouter,
  StripPathless<TFrom>,
  TTo extends string ? StripPathless<TTo> : TTo
>

This ensures TypeScript sees the correct path:

"/nested/$id"

allowing parameter extraction to work as intended.

Result (after fix)

const match = router.matchRoute({
  to: "/nested/$id",
  params: { id: "123" }
})

// ✔ match: false | { id: string }
match?.id // correctly typed

fixes: (#6011)

Summary by CodeRabbit

  • Improvements
    • Path validation in the router now features automatic normalization of special routing segments for consistent and reliable behavior. This enhancement reduces configuration edge cases, improves routing predictability, and ensures stable handling across complex scenarios with multiple parameters and nested paths, making the routing system more robust and maintainable.

✏️ Tip: You can customize this high-level summary in your review settings.

devel-maverick avatar Dec 03 '25 16:12 devel-maverick

Walkthrough

Introduces a new StripPathless type utility that recursively removes _pathless folder segments from path strings and updates ValidateParams to normalize both source and destination paths before type validation, eliminating patterns like A/_pathless/B to A/B.

Changes

Cohort / File(s) Summary
Type utility enhancement
packages/router-core/src/typePrimitives.ts
Adds StripPathless<T> type that removes _pathless segments from path strings using recursive conditional types; updates ValidateParams to apply StripPathless to both TFrom and TTo parameters for path normalization before passing to PathParamOptions

Estimated code review effort

🎯 1 (Trivial) | ⏱️ ~3 minutes

  • Single-file, type-only change with straightforward recursive conditional type logic
  • No runtime behavior modifications or control flow changes
  • Clear and self-contained utility with minimal surface area

Possibly related PRs

  • TanStack/router#6015: Addresses complementary handling of pathless layouts and param extraction; one at the type level and one at runtime.

Suggested reviewers

  • schiller-manuel
  • nlynzaad

Poem

🐰 Pathless folders hiding in the trees,
_Stripped away with such gentle ease,
Types now pure, the path runs clean,
No tangled \_ between!

Pre-merge checks and finishing touches

✅ Passed checks (3 passed)
Check name Status Explanation
Description Check ✅ Passed Check skipped - CodeRabbit’s high-level summary is enabled.
Title check ✅ Passed The title accurately describes the main change: fixing TypeScript parameter inference for routes in _pathless directories, which matches the core purpose of introducing the StripPathless type and updating ValidateParams.
Docstring Coverage ✅ Passed No functions found in the changed files to evaluate docstring coverage. Skipping docstring coverage check.
✨ Finishing touches
  • [ ] 📝 Generate docstrings
🧪 Generate unit tests (beta)
  • [ ] Create PR with unit tests
  • [ ] Post copyable unit tests in a comment

Thanks for using CodeRabbit! It's free for OSS, and your support helps us grow. If you like it, consider giving us a shout-out.

❤️ Share

Comment @coderabbitai help to get the list of available commands and usage tips.

coderabbitai[bot] avatar Dec 03 '25 16:12 coderabbitai[bot]

very likely we need the same fix as in https://github.com/TanStack/router/pull/1690

we also need type tests

schiller-manuel avatar Dec 04 '25 01:12 schiller-manuel

Thanks! Yes — I can mirror the approach from #1690.

I'll update this PR to:

Apply the same normalization logic inside useMatchRoute for layout routes

Add the missing type tests to ensure StripPathless behaves correctly, including nested cases.

Also included the recursive StripPathless fix suggested by CodeRabbit so multiple _pathless segments are fully stripped.

Let me push an update shortly.

devel-maverick avatar Dec 04 '25 07:12 devel-maverick

we wouldn't need any additional typescript types it the other fix works out

schiller-manuel avatar Dec 04 '25 09:12 schiller-manuel

Hey! Just checking in — Should I wait for the other related fix to land first before updating this PR? Or is this one good to proceed independently?

Happy to change the implementation either way. Let me know if there's anything blocking the merge, since I can update this PR pretty quickly.

devel-maverick avatar Dec 04 '25 16:12 devel-maverick