Skip to content

Conversation

@Sheraff
Copy link
Contributor

@Sheraff Sheraff commented Nov 21, 2025

interpolatePath should be able to skip optional segments when building a URL if the param provided for that segment is nullish (null | undefined). We keep it in if the value is '' so users still have the ability to do /prefixsuffix

The issue was reported on discord:

my route looks like the following:

export const Route = createFileRoute(
    "/_sidebar/chat/f_{-$folderId}/c_{$chatId}",
)({
    component: RouteComponent,
});
<Link
    to={`/chat/f_{-$folderId}/c_{$chatId}`}
    params={{ chatId: chat.id }}
>
</Link>

clicking the link without the folder path results in chat/f_/c_{CHAT_ID}. Manually setting it to chat/c_{CHAT_ID} works fine. Also removing the prefixes correctly results in chat/{CHAT_ID}

Summary by CodeRabbit

  • Bug Fixes

    • Optional URL parameters now consistently omit their entire segment (including any surrounding prefix/suffix) when values are missing, yielding cleaner base paths in edge cases.
  • Tests

    • Updated and expanded tests to cover omitted vs empty-string optional params and various prefix/suffix placements, aligning expectations with the new routing behavior.

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

@coderabbitai
Copy link
Contributor

coderabbitai bot commented Nov 21, 2025

Walkthrough

interpolatePath's handling of optional path segments was changed: when an optional param value is missing the segment is now skipped entirely (no prefix/suffix emitted); when present, prefix/suffix are computed and emitted around the value.

Changes

Cohort / File(s) Summary
Core: optional-segment interpolation
packages/router-core/src/path.ts
Changed interpolatePath for SEGMENT_TYPE_OPTIONAL_PARAM to skip emitting the segment (and any surrounding prefix/suffix) when the value is missing; when value exists, compute and emit prefix + value + suffix.
Router tests — core
packages/router-core/tests/optional-path-params.test.ts
Added/updated tests covering omitted and empty-string optional param cases for prefix, suffix, and combined variants.
Router tests — react
packages/react-router/tests/navigate.test.tsx, packages/react-router/tests/optional-path-params.test.tsx
Updated expectations to reflect removed optional segments (e.g., /files instead of /files/prefix.txt) and added empty-string/undefined distinctions.
Router tests — solid
packages/solid-router/tests/navigate.test.tsx, packages/solid-router/tests/optional-path-params.test.tsx
Updated expectations to reflect removed optional segments (e.g., /files instead of /files/prefix.txt) for omitted and empty-string cases.

Sequence Diagram(s)

sequenceDiagram
  participant Caller
  participant interpolatePath
  participant Segment (optional param)

  Caller->>interpolatePath: build path with segments
  interpolatePath->>Segment: examine segment type
  alt SEGMENT_TYPE_OPTIONAL_PARAM
    Segment->>interpolatePath: hasValue? (value)
    alt value is missing
      interpolatePath-->>Caller: skip segment (no prefix/suffix)
    else value present
      interpolatePath->>Segment: compute prefix & suffix
      interpolatePath-->>Caller: emit prefix + value + suffix
    end
  else other segment types
    interpolatePath-->>Caller: emit segment per existing rules
  end
Loading

Estimated code review effort

🎯 3 (Moderate) | ⏱️ ~20 minutes

  • Areas to review closely:
    • packages/router-core/src/path.ts — verify missing-value check and prefix/suffix emission order and edge cases (empty string vs undefined/null).
    • Updated tests in core/react/solid packages — ensure expectations match intended runtime behavior and all variants are covered.

Possibly related PRs

Suggested labels

package: react-router, package: solid-router

Suggested reviewers

  • schiller-manuel
  • nlynzaad

Poem

🐰 I hop through paths both near and far,
If a name is gone I leave no scar.
No lonely prefix clings to a trail,
The route is tidy, light as a tail.
Hooray for tests that show it well!

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: interpolatePath now skips optional segments when parameter values are nullish, which is the core behavior modification across all affected files.
Docstring Coverage ✅ Passed Docstring coverage is 100.00% which is sufficient. The required threshold is 80.00%.
✨ Finishing touches
  • 📝 Generate docstrings
🧪 Generate unit tests (beta)
  • Create PR with unit tests
  • Post copyable unit tests in a comment
  • Commit unit tests in branch refactor-router-core-interpolate-path-skips-nullish-optionals

📜 Recent review details

Configuration used: CodeRabbit UI

Review profile: CHILL

Plan: Pro

📥 Commits

Reviewing files that changed from the base of the PR and between 3716539 and a30b88d.

📒 Files selected for processing (4)
  • packages/react-router/tests/navigate.test.tsx (2 hunks)
  • packages/react-router/tests/optional-path-params.test.tsx (1 hunks)
  • packages/solid-router/tests/navigate.test.tsx (2 hunks)
  • packages/solid-router/tests/optional-path-params.test.tsx (1 hunks)
🧰 Additional context used
🧠 Learnings (6)
📓 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: 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.
📚 Learning: 2025-10-08T08:11:47.088Z
Learnt from: nlynzaad
Repo: TanStack/router PR: 5402
File: packages/router-generator/tests/generator/no-formatted-route-tree/routeTree.nonnested.snapshot.ts:19-21
Timestamp: 2025-10-08T08:11:47.088Z
Learning: Test snapshot files in the router-generator tests directory (e.g., files matching the pattern `packages/router-generator/tests/generator/**/routeTree*.snapshot.ts` or `routeTree*.snapshot.js`) should not be modified or have issues flagged, as they are fixtures used to verify the generator's output and are intentionally preserved as-is.

Applied to files:

  • packages/react-router/tests/optional-path-params.test.tsx
  • packages/react-router/tests/navigate.test.tsx
  • packages/solid-router/tests/optional-path-params.test.tsx
  • packages/solid-router/tests/navigate.test.tsx
📚 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/react-router/tests/optional-path-params.test.tsx
  • packages/react-router/tests/navigate.test.tsx
  • packages/solid-router/tests/optional-path-params.test.tsx
  • packages/solid-router/tests/navigate.test.tsx
📚 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/react-router/tests/optional-path-params.test.tsx
  • packages/react-router/tests/navigate.test.tsx
  • packages/solid-router/tests/optional-path-params.test.tsx
  • packages/solid-router/tests/navigate.test.tsx
📚 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/solid-router/tests/navigate.test.tsx
📚 Learning: 2025-10-09T12:59:02.129Z
Learnt from: hokkyss
Repo: TanStack/router PR: 5418
File: e2e/react-start/custom-identifier-prefix/src/styles/app.css:19-21
Timestamp: 2025-10-09T12:59:02.129Z
Learning: In e2e test directories (paths containing `e2e/`), accessibility concerns like outline suppression patterns are less critical since the code is for testing purposes, not production use.

Applied to files:

  • packages/solid-router/tests/navigate.test.tsx
⏰ Context from checks skipped due to timeout of 90000ms. You can increase the timeout in your CodeRabbit configuration to a maximum of 15 minutes (900000ms). (2)
  • GitHub Check: Preview
  • GitHub Check: Test
🔇 Additional comments (4)
packages/react-router/tests/optional-path-params.test.tsx (1)

537-583: Updated href expectation correctly reflects nullish optional param semantics

For to="/files/prefix{-$name}.txt" without params, expecting href="/files" is consistent with the new behavior of skipping the entire {-$name} segment (including prefix/.txt) when the param is nullish, while keeping /files/prefixdocument.txt when name is provided.

packages/solid-router/tests/optional-path-params.test.tsx (1)

538-584: Solid Link expectations aligned with new optional param behavior and React tests

Expecting the files link to resolve to href="/files" with no name param, while keeping /files/prefixdocument.txt when name is set, matches the new interpolatePath contract and keeps Solid in sync with the React test suite.

packages/solid-router/tests/navigate.test.tsx (1)

1159-1174: Programmatic navigation now correctly drops prefixed optional segment when param is undefined

Both edge-case tests now expect pathname === '/files' after navigating with { name: undefined } for route /files/prefix{-$name}.txt. This validates that:

  • A previously present {-$name} segment (/files/prefixdocument.txt or /files/prefix.txt/prefixtest.txt) is fully removed when set to undefined.
  • Earlier assertions in the second test (for name: '' and 'test') still ensure the prefix/suffix segment is retained when the param is non-nullish.

This is consistent with the intended interpolatePath semantics.

Also applies to: 1230-1264

packages/react-router/tests/navigate.test.tsx (1)

1134-1159: React navigate edge-case expectations correctly updated for nullish prefixed optional param

After navigating with { name: undefined } on /files/prefix{-$name}.txt, asserting pathname === '/files' (rather than /files/prefix.txt) confirms that optional segments with prefix/suffix are fully omitted when the value is nullish, while being retained for non-nullish values in earlier steps. This matches the updated interpolatePath rules and stays consistent with the Solid router tests.

Also applies to: 1206-1240


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

@nx-cloud
Copy link

nx-cloud bot commented Nov 21, 2025

View your CI Pipeline Execution ↗ for commit a30b88d

Command Status Duration Result
nx affected --targets=test:eslint,test:unit,tes... ✅ Succeeded 2m 18s View ↗
nx run-many --target=build --exclude=examples/*... ✅ Succeeded 1s View ↗

☁️ Nx Cloud last updated this comment at 2025-11-21 18:33:09 UTC

@pkg-pr-new
Copy link

pkg-pr-new bot commented Nov 21, 2025

More templates

@tanstack/arktype-adapter

npm i https://pkg.pr.new/TanStack/router/@tanstack/arktype-adapter@5934

@tanstack/directive-functions-plugin

npm i https://pkg.pr.new/TanStack/router/@tanstack/directive-functions-plugin@5934

@tanstack/eslint-plugin-router

npm i https://pkg.pr.new/TanStack/router/@tanstack/eslint-plugin-router@5934

@tanstack/history

npm i https://pkg.pr.new/TanStack/router/@tanstack/history@5934

@tanstack/nitro-v2-vite-plugin

npm i https://pkg.pr.new/TanStack/router/@tanstack/nitro-v2-vite-plugin@5934

@tanstack/react-router

npm i https://pkg.pr.new/TanStack/router/@tanstack/react-router@5934

@tanstack/react-router-devtools

npm i https://pkg.pr.new/TanStack/router/@tanstack/react-router-devtools@5934

@tanstack/react-router-ssr-query

npm i https://pkg.pr.new/TanStack/router/@tanstack/react-router-ssr-query@5934

@tanstack/react-start

npm i https://pkg.pr.new/TanStack/router/@tanstack/react-start@5934

@tanstack/react-start-client

npm i https://pkg.pr.new/TanStack/router/@tanstack/react-start-client@5934

@tanstack/react-start-server

npm i https://pkg.pr.new/TanStack/router/@tanstack/react-start-server@5934

@tanstack/router-cli

npm i https://pkg.pr.new/TanStack/router/@tanstack/router-cli@5934

@tanstack/router-core

npm i https://pkg.pr.new/TanStack/router/@tanstack/router-core@5934

@tanstack/router-devtools

npm i https://pkg.pr.new/TanStack/router/@tanstack/router-devtools@5934

@tanstack/router-devtools-core

npm i https://pkg.pr.new/TanStack/router/@tanstack/router-devtools-core@5934

@tanstack/router-generator

npm i https://pkg.pr.new/TanStack/router/@tanstack/router-generator@5934

@tanstack/router-plugin

npm i https://pkg.pr.new/TanStack/router/@tanstack/router-plugin@5934

@tanstack/router-ssr-query-core

npm i https://pkg.pr.new/TanStack/router/@tanstack/router-ssr-query-core@5934

@tanstack/router-utils

npm i https://pkg.pr.new/TanStack/router/@tanstack/router-utils@5934

@tanstack/router-vite-plugin

npm i https://pkg.pr.new/TanStack/router/@tanstack/router-vite-plugin@5934

@tanstack/server-functions-plugin

npm i https://pkg.pr.new/TanStack/router/@tanstack/server-functions-plugin@5934

@tanstack/solid-router

npm i https://pkg.pr.new/TanStack/router/@tanstack/solid-router@5934

@tanstack/solid-router-devtools

npm i https://pkg.pr.new/TanStack/router/@tanstack/solid-router-devtools@5934

@tanstack/solid-router-ssr-query

npm i https://pkg.pr.new/TanStack/router/@tanstack/solid-router-ssr-query@5934

@tanstack/solid-start

npm i https://pkg.pr.new/TanStack/router/@tanstack/solid-start@5934

@tanstack/solid-start-client

npm i https://pkg.pr.new/TanStack/router/@tanstack/solid-start-client@5934

@tanstack/solid-start-server

npm i https://pkg.pr.new/TanStack/router/@tanstack/solid-start-server@5934

@tanstack/start-client-core

npm i https://pkg.pr.new/TanStack/router/@tanstack/start-client-core@5934

@tanstack/start-plugin-core

npm i https://pkg.pr.new/TanStack/router/@tanstack/start-plugin-core@5934

@tanstack/start-server-core

npm i https://pkg.pr.new/TanStack/router/@tanstack/start-server-core@5934

@tanstack/start-static-server-functions

npm i https://pkg.pr.new/TanStack/router/@tanstack/start-static-server-functions@5934

@tanstack/start-storage-context

npm i https://pkg.pr.new/TanStack/router/@tanstack/start-storage-context@5934

@tanstack/valibot-adapter

npm i https://pkg.pr.new/TanStack/router/@tanstack/valibot-adapter@5934

@tanstack/virtual-file-routes

npm i https://pkg.pr.new/TanStack/router/@tanstack/virtual-file-routes@5934

@tanstack/zod-adapter

npm i https://pkg.pr.new/TanStack/router/@tanstack/zod-adapter@5934

commit: a30b88d

Copy link
Contributor

@coderabbitai coderabbitai bot left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Actionable comments posted: 0

🧹 Nitpick comments (2)
packages/router-core/src/path.ts (1)

321-333: Optional param interpolation correctly skips nullish values

The new logic for SEGMENT_TYPE_OPTIONAL_PARAM looks solid:

  • valueRaw == null short‑circuits before computing prefix/suffix or calling encodeParam, so segments like f_{-$folderId} are fully omitted (including the slash and any prefix/suffix) when the param is undefined or null.
  • For present values (including empty string), prefix/suffix are still honored and encodeParam is reused, so cases like '' yielding /posts/prefix or /posts/.html behave as expected.
  • usedParams only records actually interpolated optional params and isMissingParams remains unaffected by omitted optionals, which matches the intended “optional” semantics.

Behavior aligns with the described bug and with the new tests. As a minor enhancement, you might consider adding a dedicated test for params: { category: null } to document that null is treated like an omitted value, but the current implementation is already correct.

packages/router-core/tests/optional-path-params.test.ts (1)

205-251: Tests accurately pin new prefix/suffix behavior for optional params

The updated expectations and added cases around:

  • optional param with prefix (omitted → /posts, empty string → /posts/prefix),
  • optional param with suffix (omitted → /posts, empty string → /posts/.html),
  • optional param with prefix and suffix (omitted → /posts, empty string → /posts/prefixsuffix),

nicely exercise the new interpolation semantics: nullish/omitted optionals drop the whole segment (including surrounding text), while empty string keeps the segment and just omits the value.

You already cover omitted, undefined, and empty string; if you want to be extra explicit, you could add a params: { category: null } variant mirroring the undefined case to lock in that null is treated as “not provided”.

📜 Review details

Configuration used: CodeRabbit UI

Review profile: CHILL

Plan: Pro

📥 Commits

Reviewing files that changed from the base of the PR and between bc79c97 and 3716539.

📒 Files selected for processing (2)
  • packages/router-core/src/path.ts (1 hunks)
  • packages/router-core/tests/optional-path-params.test.ts (3 hunks)
🧰 Additional context used
🧠 Learnings (6)
📓 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: 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.
📚 Learning: 2025-10-08T08:11:47.088Z
Learnt from: nlynzaad
Repo: TanStack/router PR: 5402
File: packages/router-generator/tests/generator/no-formatted-route-tree/routeTree.nonnested.snapshot.ts:19-21
Timestamp: 2025-10-08T08:11:47.088Z
Learning: Test snapshot files in the router-generator tests directory (e.g., files matching the pattern `packages/router-generator/tests/generator/**/routeTree*.snapshot.ts` or `routeTree*.snapshot.js`) should not be modified or have issues flagged, as they are fixtures used to verify the generator's output and are intentionally preserved as-is.

Applied to files:

  • packages/router-core/tests/optional-path-params.test.ts
  • packages/router-core/src/path.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/tests/optional-path-params.test.ts
📚 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/path.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/path.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/path.ts
⏰ Context from checks skipped due to timeout of 90000ms. You can increase the timeout in your CodeRabbit configuration to a maximum of 15 minutes (900000ms). (2)
  • GitHub Check: Test
  • GitHub Check: Preview

@Sheraff Sheraff merged commit 5e130d7 into main Nov 21, 2025
6 checks passed
@Sheraff Sheraff deleted the refactor-router-core-interpolate-path-skips-nullish-optionals branch November 21, 2025 22:55
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Projects

None yet

Development

Successfully merging this pull request may close these issues.

4 participants