-
-
Notifications
You must be signed in to change notification settings - Fork 1.4k
fix(router-core): correct TypeScript param inference for routes nested inside _pathless directories (#6011) #6018
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
base: main
Are you sure you want to change the base?
Conversation
…d inside _pathless directories (TanStack#6011)
WalkthroughIntroduces a new Changes
Estimated code review effort🎯 1 (Trivial) | ⏱️ ~3 minutes
Possibly related PRs
Suggested reviewers
Poem
Pre-merge checks and finishing touches✅ Passed checks (3 passed)
✨ Finishing touches
🧪 Generate unit tests (beta)
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. Comment |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Actionable comments posted: 1
📜 Review details
Configuration used: CodeRabbit UI
Review profile: CHILL
Plan: Pro
📒 Files selected for processing (1)
packages/router-core/src/typePrimitives.ts(2 hunks)
🧰 Additional context used
📓 Path-based instructions (1)
**/*.{ts,tsx}
📄 CodeRabbit inference engine (AGENTS.md)
Use TypeScript strict mode with extensive type safety throughout the codebase
Files:
packages/router-core/src/typePrimitives.ts
🧠 Learnings (7)
📓 Common learnings
Learnt from: nlynzaad
Repo: TanStack/router PR: 5182
File: e2e/react-router/basic-file-based/src/routes/non-nested/named/$baz_.bar.tsx:3-5
Timestamp: 2025-09-22T00:56:49.237Z
Learning: In TanStack Router, underscores are intentionally stripped from route segments (e.g., `$baz_` becomes `baz` in generated types) but should be preserved in base path segments. This is the correct behavior as of the fix in PR #5182.
Learnt from: CR
Repo: TanStack/router PR: 0
File: AGENTS.md:0-0
Timestamp: 2025-11-25T00:18:21.282Z
Learning: Applies to packages/solid-router/**/*.{ts,tsx} : Solid Router components and primitives should use the tanstack/solid-router package
Learnt from: CR
Repo: TanStack/router PR: 0
File: AGENTS.md:0-0
Timestamp: 2025-11-25T00:18:21.282Z
Learning: Applies to **/src/routes/**/*.{ts,tsx} : Use file-based routing in src/routes/ directories or code-based routing with route definitions
Learnt from: nlynzaad
Repo: TanStack/router PR: 5182
File: e2e/react-router/basic-file-based/tests/non-nested-paths.spec.ts:167-172
Timestamp: 2025-09-22T00:56:53.426Z
Learning: In TanStack Router, underscores are intentionally stripped from route segments during path parsing, but preserved in base path segments. This is the expected behavior implemented in PR #5182.
Learnt from: schiller-manuel
Repo: TanStack/router PR: 5330
File: packages/router-core/src/router.ts:2231-2245
Timestamp: 2025-10-01T18:30:26.591Z
Learning: In `packages/router-core/src/router.ts`, the `resolveRedirect` method intentionally strips the router's origin from redirect URLs when they match (e.g., `https://foo.com/bar` → `/bar` for same-origin redirects) while preserving the full URL for cross-origin redirects. This logic should not be removed or simplified to use `location.publicHref` directly.
Learnt from: FatahChan
Repo: TanStack/router PR: 5475
File: e2e/react-start/basic-prerendering/src/routes/redirect/$target/via-beforeLoad.tsx:8-0
Timestamp: 2025-10-14T18:59:33.990Z
Learning: In TanStack Router e2e test files, when a route parameter is validated at the route level (e.g., using zod in validateSearch or param validation), switch statements on that parameter do not require a default case, as the validation ensures only expected values will reach the switch.
📚 Learning: 2025-09-22T00:56:49.237Z
Learnt from: nlynzaad
Repo: TanStack/router PR: 5182
File: e2e/react-router/basic-file-based/src/routes/non-nested/named/$baz_.bar.tsx:3-5
Timestamp: 2025-09-22T00:56:49.237Z
Learning: In TanStack Router, underscores are intentionally stripped from route segments (e.g., `$baz_` becomes `baz` in generated types) but should be preserved in base path segments. This is the correct behavior as of the fix in PR #5182.
Applied to files:
packages/router-core/src/typePrimitives.ts
📚 Learning: 2025-09-22T00:56:53.426Z
Learnt from: nlynzaad
Repo: TanStack/router PR: 5182
File: e2e/react-router/basic-file-based/tests/non-nested-paths.spec.ts:167-172
Timestamp: 2025-09-22T00:56:53.426Z
Learning: In TanStack Router, underscores are intentionally stripped from route segments during path parsing, but preserved in base path segments. This is the expected behavior implemented in PR #5182.
Applied to files:
packages/router-core/src/typePrimitives.ts
📚 Learning: 2025-11-25T00:18:21.282Z
Learnt from: CR
Repo: TanStack/router PR: 0
File: AGENTS.md:0-0
Timestamp: 2025-11-25T00:18:21.282Z
Learning: Applies to packages/solid-router/**/*.{ts,tsx} : Solid Router components and primitives should use the tanstack/solid-router package
Applied to files:
packages/router-core/src/typePrimitives.ts
📚 Learning: 2025-10-01T18:30:26.591Z
Learnt from: schiller-manuel
Repo: TanStack/router PR: 5330
File: packages/router-core/src/router.ts:2231-2245
Timestamp: 2025-10-01T18:30:26.591Z
Learning: In `packages/router-core/src/router.ts`, the `resolveRedirect` method intentionally strips the router's origin from redirect URLs when they match (e.g., `https://foo.com/bar` → `/bar` for same-origin redirects) while preserving the full URL for cross-origin redirects. This logic should not be removed or simplified to use `location.publicHref` directly.
Applied to files:
packages/router-core/src/typePrimitives.ts
📚 Learning: 2025-11-25T00:18:21.282Z
Learnt from: CR
Repo: TanStack/router PR: 0
File: AGENTS.md:0-0
Timestamp: 2025-11-25T00:18:21.282Z
Learning: Applies to **/src/routes/**/*.{ts,tsx} : Use file-based routing in src/routes/ directories or code-based routing with route definitions
Applied to files:
packages/router-core/src/typePrimitives.ts
📚 Learning: 2025-10-14T18:59:33.990Z
Learnt from: FatahChan
Repo: TanStack/router PR: 5475
File: e2e/react-start/basic-prerendering/src/routes/redirect/$target/via-beforeLoad.tsx:8-0
Timestamp: 2025-10-14T18:59:33.990Z
Learning: In TanStack Router e2e test files, when a route parameter is validated at the route level (e.g., using zod in validateSearch or param validation), switch statements on that parameter do not require a default case, as the validation ensures only expected values will reach the switch.
Applied to files:
packages/router-core/src/typePrimitives.ts
🔇 Additional comments (1)
packages/router-core/src/typePrimitives.ts (1)
42-46: Good targeted normalization of_pathlessfor parameter validationApplying
StripPathlessto bothTFromand (when a string)TToinValidateParamsis a clean way to align type-level param extraction with the runtime behavior for_pathlessdirectories, and the defaults still reduce to the previousPathParamOptions<TRouter, string, undefined>shape when generics aren’t specified.If, in the future, you find similar inference gaps for other helpers (
ValidateSearch,ValidateToPath, etc.), you might consider reusingStripPathlessthere as well for consistency, but what you have here is sufficient for the reported issue.
| // Remove any `_pathless` folder segments for type extraction | ||
| export type StripPathless<T extends string> = | ||
| T extends `${infer A}/_pathless/${infer B}` | ||
| ? `${A}/${B}` | ||
| : T; |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
StripPathless only removes the first _pathless segment; consider making it recursive
As written, StripPathless stops after stripping the first /_pathless/ occurrence, so paths with multiple _pathless folders (e.g. "/foo/_pathless/bar/_pathless/baz") will still contain a _pathless segment and won’t be fully normalized for param extraction.
You can make this handle all occurrences with a small recursive change:
-export type StripPathless<T extends string> =
- T extends `${infer A}/_pathless/${infer B}`
- ? `${A}/${B}`
- : T;
+export type StripPathless<T extends string> =
+ T extends `${infer A}/_pathless/${infer B}`
+ ? StripPathless<`${A}/${B}`>
+ : T;This preserves behavior for simple cases like "/_pathless/nested/$id" while correctly normalizing any deeper _pathless nesting. Based on learnings, this keeps the special handling scoped to the _pathless folder name without affecting other underscore semantics.
🤖 Prompt for AI Agents
In packages/router-core/src/typePrimitives.ts around lines 14 to 18,
StripPathless currently only removes the first "/_pathless/" segment; change it
to be recursive so all occurrences are stripped. Replace the conditional branch
so that when T extends `${infer A}/_pathless/${infer B}` you return
StripPathless<`${A}/${B}`> (rather than just `${A}/${B}`) so nested/multiple
_pathless segments are normalized; keep the fallback as T.
|
very likely we need the same fix as in #1690 we also need type tests |
|
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. |
|
we wouldn't need any additional typescript types it the other fix works out |
|
Hey! Just checking in — 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. |
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.tsxthe 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:
Fix
We normalize the type-level path by stripping _pathless before param extraction:
StripPathless<TFrom>, StripPathless<TTo>Then apply this normalization inside ValidateParams:
This ensures TypeScript sees the correct path:
"/nested/$id"allowing parameter extraction to work as intended.
Result (after fix)
fixes: (#6011)
Summary by CodeRabbit
✏️ Tip: You can customize this high-level summary in your review settings.