Skip to content

Conversation

Sheraff
Copy link
Contributor

@Sheraff Sheraff commented Aug 30, 2025

Description

matchByPath takes 2 (main) arguments:

  • to => a route with custom params syntax, for example /foo/$bar
  • from => a fully formed path (without params), for example /foo/123

Currently, we handle both the same way:

  1. clean the path
  2. split into parts
  3. map through a series of regex matchers to generate segment objects

However, in practice, the from argument doesn't need the 3rd step, we only need its string parts to match it against the to route.

This PR proposes that we only do steps 1 and 2 on from, and keep 1, 2, 3 on to. This change requires splitting the LRU cache into 2 caches for those 2 variants.

Performance

This is very slightly (but consistently) beneficial in terms of performance, because path matching is already very costly, and the parsing itself is already cached.

 ✓  @tanstack/router-core  tests/match-by-path.bench.ts > matchByPath performance 1294ms
     name                       hz     min     max    mean     p75     p99    p995    p999     rme  samples
   · matchByPath (new)  349,141.94  0.0025  0.0875  0.0029  0.0029  0.0037  0.0038  0.0081  ±0.11%   174571   fastest
   · matchByPath (old)  344,630.65  0.0025  0.1339  0.0029  0.0029  0.0038  0.0039  0.0115  ±0.15%   172316

 ✓  @tanstack/router-core  tests/match-by-path.bench.ts > buildLocation simulator 1223ms
     name                      hz     min     max    mean     p75     p99    p995    p999     rme  samples
   · matchByPath (new)  74,699.41  0.0117  4.9568  0.0134  0.0128  0.0230  0.0260  0.0343  ±1.95%    37350   fastest
   · matchByPath (old)  69,298.68  0.0125  4.0560  0.0144  0.0138  0.0242  0.0268  0.0344  ±1.60%    34650

 BENCH  Summary

   @tanstack/router-core  matchByPath (new) - tests/match-by-path.bench.ts > matchByPath performance
    1.01x faster than matchByPath (old)

   @tanstack/router-core  matchByPath (new) - tests/match-by-path.bench.ts > buildLocation simulator
    1.08x faster than matchByPath (old)
  • The 1st benchmark simply compares the two matchByPath implementations side by side.
  • The 2nd benchmark uses a scenario closer to a buildLocation, where we have a single from that we try and match w/ many to routes.

Additional improvements

With this change, we now have 2 caches:

  • parseRouteCache caches a string => Segment[] transformation (steps 1, 2, 3)
  • parsePathCache caches a string => string[] transformation (steps 1, 2)

And while parsePathCache could have an arbitrary size at runtime depending on navigations, the parseRouteCache will only contain at most 1 entry per route. This means parseRouteCache doesn't need to be an LRU cache, its size is guaranteed to be reasonable, and it can be a regular Map.

Warning

This is not exactly true, this cache is also used for relative to routes like '../foo/$bar'. This means depending on usage, it could also grow quite large.

Making this additional change widens the gap in the benchmark:

 ✓  @tanstack/router-core  tests/match-by-path.bench.ts > matchByPath performance 1300ms
     name                       hz     min     max    mean     p75     p99    p995    p999     rme  samples
   · matchByPath (new)  383,148.82  0.0023  0.8025  0.0026  0.0026  0.0034  0.0036  0.0063  ±0.34%   191575   fastest
   · matchByPath (old)  342,330.18  0.0026  0.1470  0.0029  0.0030  0.0037  0.0038  0.0065  ±0.11%   171166

 ✓  @tanstack/router-core  tests/match-by-path.bench.ts > buildLocation simulator 1223ms
     name                      hz     min     max    mean     p75     p99    p995    p999     rme  samples
   · matchByPath (new)  83,198.85  0.0105  5.8525  0.0120  0.0115  0.0200  0.0218  0.0289  ±2.30%    41600   fastest
   · matchByPath (old)  74,553.22  0.0123  0.1893  0.0134  0.0135  0.0170  0.0175  0.0215  ±0.14%    37277

 BENCH  Summary

   @tanstack/router-core  matchByPath (new) - tests/match-by-path.bench.ts > matchByPath performance
    1.12x faster than matchByPath (old)

   @tanstack/router-core  matchByPath (new) - tests/match-by-path.bench.ts > buildLocation simulator
    1.12x faster than matchByPath (old)

Summary by CodeRabbit

  • Breaking Changes

    • Public routing APIs now require/accept separate caches for route-pattern parsing and pathname splitting; the previous single-cache option is removed and related function signatures changed.
  • Improvements

    • More robust path resolution and matching: improved handling of trailing slashes, relative segments (., ..), wildcards, params, optional params, and configurable case-sensitivity for basepath removal.
    • Parsing behavior standardized via unified caching wrappers.
  • Performance

    • Separate, dedicated caches for route parsing and pathname splitting reduce repeated work and improve routing performance.

Copy link
Contributor

coderabbitai bot commented Aug 30, 2025

Walkthrough

Refactors path parsing to use two caches—ParseRouteCache for route-segment parsing and ParsePathCache for string-based path splits. Updates path parsing, matching, resolving, and RouterCore to pass and manage both caches, replacing the prior single-cache approach and updating public signatures and exports.

Changes

Cohort / File(s) Summary
Path parsing and caching refactor
packages/router-core/src/path.ts
Replaces single ParsePathnameCache with dual caches (ParseRouteCache, ParsePathCache). Adds cacheMethod, baseSplitPathname, splitPathname. Converts parsePathname to a cached wrapper, separates string-based splitting from Segment parsing, and updates resolvePath, matchPathname, matchByPath, isMatch, and removeBasepath to accept/use the two caches and new string-vs-segment representations. Exports updated types (ParsePathCache, ParseRouteCache); ParsePathnameCache removed.
Router integration updates
packages/router-core/src/router.ts
Replaces import of ParsePathnameCache with ParsePathCache and ParseRouteCache. Adds parseRouteCache and parsePathCache fields on RouterCore (LRU for path cache). Updates getMatchedRoutes and call sites to accept/propagate both caches and replaces prior parseCache usage across interpolation, resolve, match, and build flows.

Sequence Diagram(s)

sequenceDiagram
  autonumber
  participant App
  participant RouterCore
  participant PathModule as path.ts
  participant RouteCache as ParseRouteCache
  participant PathCache as ParsePathCache

  App->>RouterCore: navigate(to, base)
  RouterCore->>PathModule: resolvePath({ to, base, parseRouteCache: RouteCache, parsePathCache: PathCache })
  activate PathModule
  PathModule->>RouteCache: get/lookup(route pattern → Segment[])
  alt route cache miss
    PathModule-->>RouteCache: set(route pattern → Segment[])
  end
  PathModule->>PathCache: get/lookup(path string → string[])
  alt path cache miss
    PathModule-->>PathCache: set(path string → string[])
  end
  PathModule-->>RouterCore: resolvedPath (joined from string[] and Segment[])
  deactivate PathModule

  RouterCore->>PathModule: matchPathname({ pathname, base, parseRouteCache: RouteCache, parsePathCache: PathCache })
  activate PathModule
  PathModule->>RouteCache: get(route pattern)
  PathModule->>PathCache: get(split pathname)
  PathModule-->>RouterCore: match result (params / matched route)
  deactivate PathModule
  RouterCore-->>App: routing outcome
Loading

Estimated code review effort

🎯 4 (Complex) | ⏱️ ~60 minutes

Possibly related PRs

Suggested reviewers

  • schiller-manuel
  • SeanCassiere

Poem

I hopped through routes with caches two,
One for patterns, one for stringy glue.
I parsed the dots and trimmed each slash,
Matched params quick in a lightning flash.
My burrow’s tidy now—hip-hop, woohoo! 🐇✨

✨ Finishing Touches
  • 📝 Generate Docstrings
🧪 Generate unit tests
  • Create PR with unit tests
  • Post copyable unit tests in a comment
  • Commit unit tests in branch refactor-router-core-router-matching-from-string-only-segments

🪧 Tips

Chat

There are 3 ways to chat with CodeRabbit:

  • Review comments: Directly reply to a review comment made by CodeRabbit. Example:
    • I pushed a fix in commit <commit_id>, please review it.
    • Open a follow-up GitHub issue for this discussion.
  • Files and specific lines of code (under the "Files changed" tab): Tag @coderabbitai in a new review comment at the desired location with your query.
  • PR comments: Tag @coderabbitai in a new PR comment to ask questions about the PR branch. For the best results, please provide a very specific query, as very limited context is provided in this mode. Examples:
    • @coderabbitai gather interesting stats about this repository and render them as a table. Additionally, render a pie chart showing the language distribution in the codebase.
    • @coderabbitai read the files in the src/scheduler package and generate a class diagram using mermaid and a README in the markdown format.

Support

Need help? Create a ticket on our support page for assistance with any issues or questions.

CodeRabbit Commands (Invoked using PR/Issue comments)

Type @coderabbitai help to get the list of available commands.

Other keywords and placeholders

  • Add @coderabbitai ignore or @coderabbit ignore anywhere in the PR description to prevent this PR from being reviewed.
  • Add @coderabbitai summary to generate the high-level summary at a specific location in the PR description.
  • Add @coderabbitai anywhere in the PR title to generate the title automatically.

CodeRabbit Configuration File (.coderabbit.yaml)

  • You can programmatically configure CodeRabbit by adding a .coderabbit.yaml file to the root of your repository.
  • Please see the configuration documentation for more information.
  • If your editor has YAML language server enabled, you can add the path at the top of this file to enable auto-completion and validation: # yaml-language-server: $schema=https://coderabbit.ai/integrations/schema.v2.json

Status, Documentation and Community

  • Visit our Status Page to check the current availability of CodeRabbit.
  • Visit our Documentation for detailed information on how to use CodeRabbit.
  • Join our Discord Community to get help, request features, and share feedback.
  • Follow us on X/Twitter for updates and announcements.

Copy link

nx-cloud bot commented Aug 30, 2025

View your CI Pipeline Execution ↗ for commit 7816b7a

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

☁️ Nx Cloud last updated this comment at 2025-08-30 17:15:08 UTC

Copy link

pkg-pr-new bot commented Aug 30, 2025

More templates

@tanstack/arktype-adapter

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

@tanstack/directive-functions-plugin

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

@tanstack/eslint-plugin-router

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

@tanstack/history

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

@tanstack/react-router

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

@tanstack/react-router-devtools

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

@tanstack/react-router-ssr-query

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

@tanstack/react-start

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

@tanstack/react-start-client

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

@tanstack/react-start-plugin

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

@tanstack/react-start-server

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

@tanstack/router-cli

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

@tanstack/router-core

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

@tanstack/router-devtools

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

@tanstack/router-devtools-core

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

@tanstack/router-generator

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

@tanstack/router-plugin

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

@tanstack/router-ssr-query-core

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

@tanstack/router-utils

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

@tanstack/router-vite-plugin

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

@tanstack/server-functions-plugin

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

@tanstack/solid-router

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

@tanstack/solid-router-devtools

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

@tanstack/solid-start

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

@tanstack/solid-start-client

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

@tanstack/solid-start-plugin

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

@tanstack/solid-start-server

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

@tanstack/start-client-core

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

@tanstack/start-plugin-core

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

@tanstack/start-server-core

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

@tanstack/start-server-functions-client

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

@tanstack/start-server-functions-fetcher

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

@tanstack/start-server-functions-server

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

@tanstack/start-storage-context

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

@tanstack/valibot-adapter

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

@tanstack/virtual-file-routes

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

@tanstack/zod-adapter

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

commit: 7816b7a

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)

274-355: Consider extracting segment creation logic for better maintainability.

While the implementation is correct, the baseParsePathname function has become quite long with repetitive segment creation patterns. Consider extracting helper functions for creating each segment type to reduce duplication and improve readability.

For example:

+function createWildcardSegment(prefix?: string, suffix?: string): Segment {
+  return {
+    type: SEGMENT_TYPE_WILDCARD,
+    value: '$',
+    prefixSegment: prefix || undefined,
+    suffixSegment: suffix || undefined,
+  }
+}
+
+function createOptionalParamSegment(paramName: string, prefix?: string, suffix?: string): Segment {
+  return {
+    type: SEGMENT_TYPE_OPTIONAL_PARAM,
+    value: paramName,
+    prefixSegment: prefix || undefined,
+    suffixSegment: suffix || undefined,
+  }
+}

 function baseParsePathname(pathname: string): ReadonlyArray<Segment> {
   // ... existing code ...
   
   if (wildcardBracesMatch) {
-    return {
-      type: SEGMENT_TYPE_WILDCARD,
-      value: '$',
-      prefixSegment: prefix || undefined,
-      suffixSegment: suffix || undefined,
-    }
+    return createWildcardSegment(prefix, suffix)
   }
packages/router-core/src/router.ts (1)

1370-1373: Consider making cache sizes configurable for different use cases.

While 500 entries is a reasonable default, different applications might benefit from different cache sizes. The comment about potentially using a plain Map for parseRouteCache (sized to the exact number of routes) is a good future optimization to consider.

For a future enhancement, consider:

  1. Making cache sizes configurable through router options
  2. Using a plain Map for parseRouteCache since the number of routes is known and fixed
  3. Monitoring cache hit rates in development mode to help tune sizes
📜 Review details

Configuration used: CodeRabbit UI

Review profile: CHILL

Plan: Pro

💡 Knowledge Base configuration:

  • MCP integration is disabled by default for public repositories
  • Jira integration is disabled by default for public repositories
  • Linear integration is disabled by default for public repositories

You can enable these sources in your CodeRabbit configuration.

📥 Commits

Reviewing files that changed from the base of the PR and between d872397 and fa294cc.

📒 Files selected for processing (2)
  • packages/router-core/src/path.ts (21 hunks)
  • packages/router-core/src/router.ts (12 hunks)
🧰 Additional context used
🧬 Code graph analysis (2)
packages/router-core/src/router.ts (2)
packages/router-core/src/path.ts (2)
  • ParseRouteCache (225-225)
  • ParsePathCache (224-224)
packages/router-core/src/lru-cache.ts (1)
  • createLRUCache (6-68)
packages/router-core/src/path.ts (2)
packages/router-core/src/lru-cache.ts (1)
  • LRUCache (1-4)
packages/router-core/src/utils.ts (1)
  • last (187-189)
⏰ 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
🔇 Additional comments (9)
packages/router-core/src/path.ts (7)

105-106: LGTM! Clean cache separation for improved performance.

The refactoring from a single parseCache to separate parseRouteCache appropriately distinguishes between route pattern parsing (which needs full segment information) and simple path splitting (which only needs strings).


164-165: Verify segment-to-string conversion preserves path semantics.

The change from string-based path resolution to segment-based resolution with parsePathname is correct. The final conversion back to strings via segmentToString properly handles all segment types including parameterized segments.


208-222: Well-designed generic caching wrapper.

The cacheMethod utility provides a clean abstraction for creating cached versions of parsing functions. The pattern of returning empty arrays for undefined keys and proper cache management is well implemented.


237-255: Proper URL decoding with special handling for encoded percent signs.

The baseSplitPathname function correctly handles the edge case of %25 (encoded percent sign) to prevent double-decoding issues. The normalization of leading/trailing slashes is also appropriate.


567-604: Efficient string-based matching with proper cache utilization.

The updated matchByPath correctly uses splitPathname for the base path (getting string arrays) and parsePathname for the route pattern (getting Segment arrays). This separation is the core optimization that avoids unnecessary regex processing for concrete paths.


607-608: Type signature correctly reflects the new string-based approach.

The change from Segment[] to ReadonlyArray<string> for baseSegments properly enforces the optimization at the type level.


636-641: String comparisons are consistently updated throughout matching logic.

All the places where baseSegment values are accessed have been properly updated to work with strings instead of segment objects. The comparisons now directly use the string values, which is more efficient.

Also applies to: 701-704, 755-759, 770-770, 803-803, 825-825, 842-842

packages/router-core/src/router.ts (2)

38-38: Clear documentation of the dual-cache system.

The comments clearly explain the purpose of each cache, which will help future maintainers understand the performance optimization.

Also applies to: 1370-1373


1043-1043: Consistent propagation of both caches throughout the codebase.

All call sites have been properly updated to pass both parseRouteCache and parsePathCache where needed. The separation is maintained consistently across interpolation, matching, and resolution operations.

Also applies to: 1225-1227, 1486-1488, 1513-1515, 1606-1608, 2287-2289, 2623-2625, 2648-2650

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/router.ts (2)

1370-1374: Type the Map and reset on route-tree changes to avoid stale entries.

  • Use typed Map generics (or cast) to align with ParseRouteCache and prevent implicit any.
  • Consider resetting the route-segment cache when the route tree changes to bound growth if routes are hot-swapped.

Apply:

-  /** a cache of parsed routes for `parsePathname` (e.g. `/foo/$bar` => Array<Segment>) */
-  private parseRouteCache: ParseRouteCache = new Map()
+  /** a cache of parsed routes for `parsePathname` (e.g. `/foo/$bar` => Array<Segment>) */
+  private parseRouteCache: ParseRouteCache =
+    new Map<string, ReadonlyArray<Segment>>() as ParseRouteCache

And inside the update branch where the route tree changes:

     if (this.options.routeTree !== this.routeTree) {
       this.routeTree = this.options.routeTree as TRouteTree
+      // Reset route-segment parse cache when routes change
+      this.parseRouteCache =
+        new Map<string, ReadonlyArray<Segment>>() as ParseRouteCache
       this.buildRouteTree()
     }

1036-1044: Destructure and forward parsePathCache in resolvePathWithBase
resolvePathWithBase currently only passes parseRouteCache into parsePathname. To enable raw-path caching, extend its signature to accept parsePathCache and invoke parsePathname(base, parsePathCache) / parsePathname(to, parsePathCache).

📜 Review details

Configuration used: CodeRabbit UI

Review profile: CHILL

Plan: Pro

💡 Knowledge Base configuration:

  • MCP integration is disabled by default for public repositories
  • Jira integration is disabled by default for public repositories
  • Linear integration is disabled by default for public repositories

You can enable these sources in your CodeRabbit configuration.

📥 Commits

Reviewing files that changed from the base of the PR and between fa294cc and daf4e70.

📒 Files selected for processing (1)
  • packages/router-core/src/router.ts (12 hunks)
🧰 Additional context used
🧬 Code graph analysis (1)
packages/router-core/src/router.ts (2)
packages/router-core/src/path.ts (2)
  • ParseRouteCache (225-225)
  • ParsePathCache (224-224)
packages/router-core/src/lru-cache.ts (1)
  • createLRUCache (6-68)
⏰ 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
🔇 Additional comments (7)
packages/router-core/src/router.ts (7)

38-38: Type imports match new dual-cache design — LGTM.


1225-1227: Consistent interpolation now uses both caches — good.
All interpolatePath call sites pass route- and path-caches, keeping parity and avoiding redundant parsing.

Also applies to: 1486-1488, 1513-1515


1387-1389: Plumbing caches into getMatchedRoutes — LGTM.


1606-1608: Mask matching uses caches — LGTM.


2287-2289: matchRoute correctly forwards caches to matchPathname — LGTM.


2623-2635: getMatchedRoutes signature extended with optional caches — LGTM.
Backward compatible; internal callers pass both caches.


2648-2650: Cache-aware matchPathname usage inside getMatchedRoutes — LGTM.

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: 1

Caution

Some comments are outside the diff and can’t be posted inline due to platform limitations.

⚠️ Outside diff range comments (1)
packages/router-core/src/path.ts (1)

798-807: Respect caseSensitive in optional-param lookahead

Lookahead equality uses strict case-sensitive compare regardless of caseSensitive, causing incorrect skips on case-insensitive routers.

Apply:

-            if (
-              futureRouteSegment?.type === SEGMENT_TYPE_PATHNAME &&
-              futureRouteSegment.value === baseSegment
-            ) {
+            if (futureRouteSegment?.type === SEGMENT_TYPE_PATHNAME) {
+              const fv = caseSensitive
+                ? futureRouteSegment.value
+                : futureRouteSegment.value.toLowerCase()
+              const bv = caseSensitive ? baseSegment : baseSegment.toLowerCase()
+              if (fv === bv) {
                 // The current base segment matches a future pathname segment,
                 // so we should skip this optional parameter
                 shouldMatchOptional = false
                 break
-            }
+              }
+            }
🧹 Nitpick comments (3)
packages/router-core/src/path.ts (3)

636-647: Simplify wildcard prefix/suffix checks

Using the in operator on optional fields is misleading (it’s always true for Segment). Check prefix/suffix values directly.

Apply:

-          // Check if the base segment starts with prefix and ends with suffix
-          if ('prefixSegment' in routeSegment) {
-            if (!baseSegment.startsWith(prefix)) {
-              return false
-            }
-          }
-          if ('suffixSegment' in routeSegment) {
-            if (!baseSegments[baseSegments.length - 1]?.endsWith(suffix)) {
-              return false
-            }
-          }
+          // Check if the base segment starts/ends with the given prefix/suffix
+          if (prefix && !baseSegment.startsWith(prefix)) return false
+          if (suffix && !baseSegments[baseSegments.length - 1]?.endsWith(suffix))
+            return false

116-125: Return canonical "{$param}" when no prefix/suffix

segmentToString for bare params currently returns "$param". For consistency with optional params and brace syntax, emit "{$param}" when there’s no prefix/suffix.

Apply:

   if (type === SEGMENT_TYPE_PARAM) {
     const param = value.substring(1)
     if (prefixSegment && suffixSegment) {
       return `${prefixSegment}{$${param}}${suffixSegment}`
     } else if (prefixSegment) {
       return `${prefixSegment}{$${param}}`
     } else if (suffixSegment) {
       return `{$${param}}${suffixSegment}`
     }
+    return `{${value}}`
   }

366-368: Unused parsePathCache in interpolatePath options

parsePathCache is accepted but unused. Remove to avoid API noise, or mark as deprecated for a staged removal.

Option A (remove now):

 interface InterpolatePathOptions {
   path?: string
   params: Record<string, unknown>
   leaveWildcards?: boolean
   leaveParams?: boolean
   // Map of encoded chars to decoded chars (e.g. '%40' -> '@') that should remain decoded in path params
   decodeCharMap?: Map<string, string>
-  parseRouteCache?: ParseRouteCache
-  parsePathCache?: ParsePathCache
+  parseRouteCache?: ParseRouteCache
 }
 ...
 export function interpolatePath({
   path,
   params,
   leaveWildcards,
   leaveParams,
   decodeCharMap,
-  parseRouteCache,
+  parseRouteCache,
 }: InterpolatePathOptions): InterPolatePathResult {

Option B (deprecate):

-  parsePathCache?: ParsePathCache
+  /** @deprecated Unused; will be removed in a future version. */
+  parsePathCache?: ParsePathCache

Also applies to: 381-384

📜 Review details

Configuration used: CodeRabbit UI

Review profile: CHILL

Plan: Pro

💡 Knowledge Base configuration:

  • MCP integration is disabled by default for public repositories
  • Jira integration is disabled by default for public repositories
  • Linear integration is disabled by default for public repositories

You can enable these sources in your CodeRabbit configuration.

📥 Commits

Reviewing files that changed from the base of the PR and between daf4e70 and 7816b7a.

📒 Files selected for processing (1)
  • packages/router-core/src/path.ts (21 hunks)
🧰 Additional context used
🧬 Code graph analysis (1)
packages/router-core/src/path.ts (3)
packages/router-core/src/route.ts (1)
  • AnyPathParams (45-45)
packages/router-core/src/lru-cache.ts (1)
  • LRUCache (1-4)
packages/router-core/src/utils.ts (1)
  • last (187-189)
🔇 Additional comments (2)
packages/router-core/src/path.ts (2)

648-666: Double-decoding risk for wildcard splat

decodeURI(joinPaths(remainingBaseSegments)) can undo the earlier %25 guard in baseSplitPathname, turning "%25" back into "%". Confirm this is intended for params. If not, consider decoding per-segment with decodeURIComponent and rejoining, or avoid decoding here and decode at consumption points.

Would you like a targeted test matrix (inputs with %, %2F, multi-byte chars) to lock behavior?


224-228: Dual-cache abstraction looks good

cacheMethod generalization and the split between ParsePathCache (strings) and ParseRouteCache (Segments) provide the intended perf win and cleaner semantics.

Comment on lines 572 to 579
// check basepath first
if (basepath !== '/' && !from.startsWith(basepath)) {
return undefined
}
// Remove the base path from the pathname
from = removeBasepath(basepath, from, caseSensitive)
// Default to to $ (wildcard)
// Default to `to = '$'` (wildcard)
to = removeBasepath(basepath, `${to ?? '$'}`, caseSensitive)
Copy link
Contributor

Choose a reason for hiding this comment

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

⚠️ Potential issue

Fix case-insensitive basepath check in matchByPath

Using startsWith ignores the caseSensitive flag and can reject valid matches (e.g., basepath "/App" vs pathname "/app/..."). Rely on removeBasepath for the check.

Apply:

-  // check basepath first
-  if (basepath !== '/' && !from.startsWith(basepath)) {
-    return undefined
-  }
-  // Remove the base path from the pathname
-  from = removeBasepath(basepath, from, caseSensitive)
+  // Remove the base path from the pathname (respects caseSensitive)
+  const strippedFrom = removeBasepath(basepath, from, caseSensitive)
+  if (basepath !== '/' && strippedFrom === from) {
+    return undefined
+  }
+  from = strippedFrom
📝 Committable suggestion

‼️ IMPORTANT
Carefully review the code before committing. Ensure that it accurately replaces the highlighted code, contains no missing lines, and has no issues with indentation. Thoroughly test & benchmark the code to ensure it meets the requirements.

Suggested change
// check basepath first
if (basepath !== '/' && !from.startsWith(basepath)) {
return undefined
}
// Remove the base path from the pathname
from = removeBasepath(basepath, from, caseSensitive)
// Default to to $ (wildcard)
// Default to `to = '$'` (wildcard)
to = removeBasepath(basepath, `${to ?? '$'}`, caseSensitive)
// Remove the base path from the pathname (respects caseSensitive)
const strippedFrom = removeBasepath(basepath, from, caseSensitive)
if (basepath !== '/' && strippedFrom === from) {
return undefined
}
from = strippedFrom
// Default to `to = '$'` (wildcard)
to = removeBasepath(basepath, `${to ?? '$'}`, caseSensitive)
🤖 Prompt for AI Agents
In packages/router-core/src/path.ts around lines 572 to 579, the current early
basepath check uses from.startsWith(basepath) which ignores the caseSensitive
flag and can incorrectly reject matches; remove that startsWith check and
instead call removeBasepath(basepath, from, caseSensitive) and return undefined
if it returns undefined, then set from to the returned value; likewise set to
using removeBasepath(basepath, `${to ?? '$'}`, caseSensitive) and handle
undefined there as before. Ensure both calls use the same caseSensitive
parameter.

@Sheraff Sheraff changed the title refactor(router-core): route matching only needs string parts for 'from', not full segments refactor(router-core): route matching only needs string parts for 'from', not full segments (WIP) Aug 30, 2025
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.

1 participant