test: client-nav benchmarks use a more representative app#6812
test: client-nav benchmarks use a more representative app#6812
Conversation
📝 WalkthroughWalkthroughUpdates benchmark apps across React, Solid, and Vue frameworks to incorporate route lifecycle hooks (validateSearch, beforeLoad, loader, loaderDeps), introduce Match components using useMatch, and modify navigation cadence by deriving parameter IDs. Router configuration adds defaultPendingMinMs: 0. Performance loop iterations reduced from 100 to 50. Changes
Estimated code review effort🎯 3 (Moderate) | ⏱️ ~20 minutes Possibly related PRs
Suggested labels
Suggested reviewers
Poem
🚥 Pre-merge checks | ✅ 2 | ❌ 1❌ Failed checks (1 warning)
✅ Passed checks (2 passed)
✏️ Tip: You can configure your own custom pre-merge checks in the settings. ✨ Finishing Touches
🧪 Generate unit tests (beta)
Comment |
|
View your CI Pipeline Execution ↗ for commit 783512f
☁️ Nx Cloud last updated this comment at |
Bundle Size Benchmarks
Trend sparkline is historical gzip bytes ending with this PR measurement; lower is better. |
There was a problem hiding this comment.
💡 Codex Review
Here are some automated review suggestions for this pull request.
Reviewed commit: 11d0bd4a61
ℹ️ About Codex in GitHub
Your team has set up Codex to review pull requests in this repo. Reviews are triggered when you
- Open a pull request for review
- Mark a draft as ready
- Comment "@codex review".
If Codex has suggestions, it will comment; otherwise it will react with 👍.
Codex can also answer questions or update the PR. Try commenting "@codex address that feedback".
There was a problem hiding this comment.
🧹 Nitpick comments (4)
benchmarks/client-nav/vue/app.tsx (1)
100-104: Consider adding type annotations forsearchparameter.Per TypeScript strict mode guidelines, the
searchparameter invalidateSearchandloaderDepsis implicitly typed. While this works for a benchmark file, explicit typing would improve safety.♻️ Optional: Add explicit type annotation
const route = createRoute({ getParentRoute: () => root, path: '/$id', - validateSearch: (search) => ({ id: search.id }), + validateSearch: (search: Record<string, unknown>) => ({ id: search.id as string }), component: () => <div />, beforeLoad: () => Promise.resolve(), - loaderDeps: ({ search }) => ({ id: search.id }), + loaderDeps: ({ search }: { search: { id: string } }) => ({ id: search.id }), loader: () => Promise.resolve(), })🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed. In `@benchmarks/client-nav/vue/app.tsx` around lines 100 - 104, The validateSearch and loaderDeps functions use an implicitly typed search parameter; add an explicit type for search (e.g., define or import a Search type/interface) and annotate the parameter in validateSearch (validateSearch: (search: Search) => ...) and in loaderDeps (loaderDeps: ({ search }: { search: Search }) => ... or equivalent destructured typing) so TypeScript strict mode is satisfied; ensure any downstream returns ({ id: search.id }) remain unchanged and update any related types used by loader and beforeLoad if needed to keep signatures consistent with the new Search type.benchmarks/client-nav/solid/speed.bench.tsx (1)
41-42: Use explicit string conversion in navigation payload to avoid implicit coercion.For stricter typing and clearer intent, serialize both values at the callsite instead of relying on downstream coercion.
Proposed diff
return navigate({ to: '/$id', - params: { id: Math.floor((nextId + 1) / 2) }, - search: { id: Math.floor(nextId / 2) }, + params: { id: String(Math.floor((nextId + 1) / 2)) }, + search: { id: String(Math.floor(nextId / 2)) }, replace: true, })As per coding guidelines:
**/*.{ts,tsx}: Use TypeScript strict mode with extensive type safety.🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed. In `@benchmarks/client-nav/solid/speed.bench.tsx` around lines 41 - 42, The numeric values assigned to params and search are implicitly coerced from nextId; update the callsite to explicitly serialize both values to strings (e.g., use String(nextIdExpression) or .toString()) for params.id and search.id to satisfy strict TypeScript typing and avoid implicit coercion; locate the object with keys params and search that reference nextId and replace the numeric expressions (Math.floor((nextId + 1) / 2) and Math.floor(nextId / 2)) with their stringified equivalents.benchmarks/client-nav/solid/app.tsx (1)
78-84: Normalizesearch.idinvalidateSearchto keep downstream types stable.
validateSearchcurrently forwardssearch.idas-is, soloaderDepsinherits a weakly-typed value. Normalizing once here keeps search typing deterministic across the route lifecycle.Proposed diff
const route = createRoute({ getParentRoute: () => root, path: '/$id', - validateSearch: (search) => ({ id: search.id }), + validateSearch: (search) => ({ + id: + typeof search.id === 'string' + ? search.id + : String(search.id ?? '0'), + }), component: () => { return <div /> }, beforeLoad: () => Promise.resolve(), loaderDeps: ({ search }) => ({ id: search.id }), loader: () => Promise.resolve(), })As per coding guidelines:
**/*.{ts,tsx}: Use TypeScript strict mode with extensive type safety.🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed. In `@benchmarks/client-nav/solid/app.tsx` around lines 78 - 84, validateSearch is forwarding search.id raw which makes downstream types in loaderDeps unstable; update validateSearch to normalize id (e.g., return { id: String(search.id ?? '') } or another explicit normalized type your route expects) so the route lifecycle has a deterministic type for id; change the implementation of validateSearch (and confirm loaderDeps: ({ search }) => ({ id: search.id }) now receives the normalized/typed id) to keep types strict and consistent across validateSearch, loaderDeps, and loader.benchmarks/client-nav/react/app.tsx (1)
85-89: Consider type-safe validation in benchmarks only if consistency is preferred across frameworks.While stricter input validation for
search.idwould align with TypeScript's strict mode expectations, this is a performance benchmark where minimal overhead is intentional. The three framework implementations (React, Solid, Vue) all use identical pass-through validation, and the actual performance-sensitive code in the component already normalizes the value where it matters (line 37:Number(search.id ?? 0)). The loaders and component handlers are no-ops, so the validation doesn't affect benchmark accuracy. Adopt the suggested normalization only if cross-framework consistency or downstream type contracts require stricter schemas.🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed. In `@benchmarks/client-nav/react/app.tsx` around lines 85 - 89, The current pass-through validation in validateSearch and loaderDeps yields ({ id: search.id }) which is intentional for minimal overhead in benchmarks; if you decide to enforce type-safe normalization for cross-framework consistency, update validateSearch and loaderDeps to normalize id (e.g., convert to a number with Number(search.id ?? 0)) so downstream code (including component and loader) receives a predictable numeric id, and adjust any related loader or loaderDeps expectations accordingly; otherwise leave the existing pass-through implementations (validateSearch, loaderDeps, component, loader) as-is to avoid adding benchmark overhead.
🤖 Prompt for all review comments with AI agents
Verify each finding against the current code and only fix it if needed.
Nitpick comments:
In `@benchmarks/client-nav/react/app.tsx`:
- Around line 85-89: The current pass-through validation in validateSearch and
loaderDeps yields ({ id: search.id }) which is intentional for minimal overhead
in benchmarks; if you decide to enforce type-safe normalization for
cross-framework consistency, update validateSearch and loaderDeps to normalize
id (e.g., convert to a number with Number(search.id ?? 0)) so downstream code
(including component and loader) receives a predictable numeric id, and adjust
any related loader or loaderDeps expectations accordingly; otherwise leave the
existing pass-through implementations (validateSearch, loaderDeps, component,
loader) as-is to avoid adding benchmark overhead.
In `@benchmarks/client-nav/solid/app.tsx`:
- Around line 78-84: validateSearch is forwarding search.id raw which makes
downstream types in loaderDeps unstable; update validateSearch to normalize id
(e.g., return { id: String(search.id ?? '') } or another explicit normalized
type your route expects) so the route lifecycle has a deterministic type for id;
change the implementation of validateSearch (and confirm loaderDeps: ({ search
}) => ({ id: search.id }) now receives the normalized/typed id) to keep types
strict and consistent across validateSearch, loaderDeps, and loader.
In `@benchmarks/client-nav/solid/speed.bench.tsx`:
- Around line 41-42: The numeric values assigned to params and search are
implicitly coerced from nextId; update the callsite to explicitly serialize both
values to strings (e.g., use String(nextIdExpression) or .toString()) for
params.id and search.id to satisfy strict TypeScript typing and avoid implicit
coercion; locate the object with keys params and search that reference nextId
and replace the numeric expressions (Math.floor((nextId + 1) / 2) and
Math.floor(nextId / 2)) with their stringified equivalents.
In `@benchmarks/client-nav/vue/app.tsx`:
- Around line 100-104: The validateSearch and loaderDeps functions use an
implicitly typed search parameter; add an explicit type for search (e.g., define
or import a Search type/interface) and annotate the parameter in validateSearch
(validateSearch: (search: Search) => ...) and in loaderDeps (loaderDeps: ({
search }: { search: Search }) => ... or equivalent destructured typing) so
TypeScript strict mode is satisfied; ensure any downstream returns ({ id:
search.id }) remain unchanged and update any related types used by loader and
beforeLoad if needed to keep signatures consistent with the new Search type.
ℹ️ Review info
Configuration used: defaults
Review profile: CHILL
Plan: Pro
📒 Files selected for processing (6)
benchmarks/client-nav/react/app.tsxbenchmarks/client-nav/react/speed.bench.tsxbenchmarks/client-nav/solid/app.tsxbenchmarks/client-nav/solid/speed.bench.tsxbenchmarks/client-nav/vue/app.tsxbenchmarks/client-nav/vue/speed.bench.tsx
Merging this PR will degrade performance by 18.98%
Performance Changes
Comparing |
There was a problem hiding this comment.
Actionable comments posted: 1
🧹 Nitpick comments (1)
benchmarks/client-nav/solid/app.tsx (1)
78-78: Type safety:validateSearchaccessessearch.idwithout type narrowing.The
searchparameter invalidateSearchisRecord<string, unknown>by default. Accessingsearch.iddirectly may cause TypeScript errors in strict mode since the value isunknown. Consider adding type assertion or proper validation.✏️ Proposed fix with type assertion
- validateSearch: (search) => ({ id: search.id }), + validateSearch: (search: Record<string, unknown>) => ({ id: search.id as string }),🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed. In `@benchmarks/client-nav/solid/app.tsx` at line 78, The validateSearch implementation uses search.id on a parameter typed as Record<string, unknown>, causing TypeScript errors; update validateSearch (the function named validateSearch) to narrow or assert the type of search before accessing .id — either add a runtime guard that checks search is an object and that 'id' in search and typeof (search['id']) === 'string' (or the expected type) before returning { id: ... }, or change the function signature to accept a properly typed parameter (e.g., { id: string } or a typed interface) and adjust callers accordingly so access to search.id is type-safe.
🤖 Prompt for all review comments with AI agents
Verify each finding against the current code and only fix it if needed.
Inline comments:
In `@benchmarks/client-nav/solid/app.tsx`:
- Around line 43-49: The select callback in Match is accessing match.params.id
but with strict: false match.params can be undefined; update the select to use
optional chaining (e.g. select: (match) =>
runPerfSelectorComputation(Number(match.params?.id ?? 0))) so params is safely
accessed, and ensure any other direct uses of match.params in Match use the same
match.params?.id pattern; keep using runPerfSelectorComputation and the number()
call as-is.
---
Nitpick comments:
In `@benchmarks/client-nav/solid/app.tsx`:
- Line 78: The validateSearch implementation uses search.id on a parameter typed
as Record<string, unknown>, causing TypeScript errors; update validateSearch
(the function named validateSearch) to narrow or assert the type of search
before accessing .id — either add a runtime guard that checks search is an
object and that 'id' in search and typeof (search['id']) === 'string' (or the
expected type) before returning { id: ... }, or change the function signature to
accept a properly typed parameter (e.g., { id: string } or a typed interface)
and adjust callers accordingly so access to search.id is type-safe.
| function Match() { | ||
| const number = useMatch({ | ||
| strict: false, | ||
| select: (match) => runPerfSelectorComputation(Number(match.params.id ?? 0)), | ||
| }) | ||
|
|
||
| return null | ||
| return <>{runPerfSelectorComputation(number())}</> | ||
| } |
There was a problem hiding this comment.
🧩 Analysis chain
🏁 Script executed:
cat -n benchmarks/client-nav/solid/app.tsx | head -60Repository: TanStack/router
Length of output: 1748
🌐 Web query:
TanStack Router useMatch strict false select callback documentation
💡 Result:
TanStack Router documents useMatch’s strict and select options on the useMatch hook API page. (tanstack.com)
strict: false
- Default is
true. - If
strictis false, you must not passfrom(it must be unset), and the returned match type is loosened (aPartial<RouteMatch>-style shape) to reflect that it could be any active match. (tanstack.com) - This is intended for shared/reusable components where you don’t know which route you’re under (also described in the Type Safety guide). (tanstack.com)
Example:
const match = useMatch({ strict: false })select: (match) => TSelected (the “select callback”)
- Optional function called with the
RouteMatch. useMatchreturns whatever yourselectreturns.- The selected value is also used to decide whether to re-render (via shallow equality checks). (tanstack.com)
Example:
const pathname = useMatch({
strict: false,
select: (match) => match.pathname,
})(Example pattern referenced in community discussion; core behavior is in the docs.) (tanstack.com)
Citations:
- 1: https://tanstack.com/router/latest/docs/framework/react/api/router/useMatchHook?utm_source=openai
- 2: https://tanstack.com/router/latest/docs/framework/react/api/router/useMatchHook?utm_source=openai
- 3: https://tanstack.com/router/v1/docs/framework/react/guide/type-safety?utm_source=openai
- 4: https://tanstack.com/router/latest/docs/framework/react/api/router/useMatchHook?utm_source=openai
- 5: https://tanstack.com/router/latest/docs/framework/react/api/router/useMatchHook?utm_source=openai
- 6: https://tanstack.com/router/latest/docs/framework/react/api/router/useMatchHook?utm_source=openai
🏁 Script executed:
rg "strict.*false" benchmarks/client-nav/solid/app.tsx -A 3Repository: TanStack/router
Length of output: 528
🌐 Web query:
TanStack Solid Router useMatch RouteMatch Partial type strict false
💡 Result:
In TanStack Router for Solid (@tanstack/solid-router), this is expected behavior:
useMatchis strict by default (strict: true). In strict mode you should passfrom(a route id/path), and you get a fully-typedRouteMatch. (tanstack.com)- If you set
strict: false, you must NOT passfrom, and the return type becomesPartial<RouteMatch>(so properties likesearchare optional / possiblyundefined). This is by design for reusable/shared components. (tanstack.com)
What to do
1) Route-specific component (fully typed)
const match = useMatch({ from: Route.fullPath }) // or Route.id
// match.search is fully typed2) Reusable component across many routes (no invariant, looser types)
const match = useMatch({ strict: false })
// match is Partial<RouteMatch>, so match.search may be undefined3) If you don’t want it to throw when a specific match isn’t active
const match = useMatch({ from: '/posts', shouldThrow: false }) // RouteMatch | undefinedCitations:
- 1: https://tanstack.com/router/latest/docs/framework/react/api/router/useMatchHook?utm_source=openai
- 2: https://tanstack.com/router/latest/docs/framework/react/api/router/useMatchHook?utm_source=openai
- 3: https://tanstack.com/router/latest/docs/framework/react/api/router/useMatchHook?utm_source=openai
- 4: https://tanstack.com/router/latest/docs/framework/react/api/router/useMatchHook?utm_source=openai
- 5: https://tanstack.com/router/latest/docs/framework/react/api/router/useMatchHook?utm_source=openai
🏁 Script executed:
# Search for type definitions or documentation in the repo
fd -e ts -e tsx | xargs rg "Partial.*RouteMatch" | head -20Repository: TanStack/router
Length of output: 41
🏁 Script executed:
# Check for tsconfig and strict mode settings
fd -e json -name "tsconfig*" | xargs cat | grep -A 10 '"strict"' || echo "No strict setting found"Repository: TanStack/router
Length of output: 330
🏁 Script executed:
# Check for any type errors or eslint configs
fd -e json -name ".eslintrc*" -o -name "tsconfig*" benchmarks/ | head -5Repository: TanStack/router
Length of output: 289
🏁 Script executed:
# Look for type definition or implementation of useMatch in solid-router
fd "@tanstack/solid-router" -t d | head -3Repository: TanStack/router
Length of output: 41
🏁 Script executed:
find . -name "tsconfig*.json" -type f 2>/dev/null | head -10Repository: TanStack/router
Length of output: 435
🏁 Script executed:
# Look for TypeScript configuration
cat tsconfig.json 2>/dev/null | head -30 || echo "No root tsconfig found"Repository: TanStack/router
Length of output: 846
🏁 Script executed:
# Check if there's a type definition file in node_modules or packages
find . -path "*/node_modules/@tanstack/solid-router*" -name "*.d.ts" 2>/dev/null | head -5 || echo "Type definitions not found in sandbox"Repository: TanStack/router
Length of output: 41
🏁 Script executed:
# Search for type definitions within the repo itself
find . -path "*/packages/solid-router*" -type f \( -name "*.ts" -o -name "*.tsx" \) | grep -i "use-match\|useMatch" | head -10Repository: TanStack/router
Length of output: 197
🏁 Script executed:
cat benchmarks/client-nav/solid/tsconfig.jsonRepository: TanStack/router
Length of output: 315
🏁 Script executed:
cat packages/solid-router/src/useMatch.tsxRepository: TanStack/router
Length of output: 3495
Use optional chaining to safely access match.params with strict: false.
With strict: false, the useMatch hook types the match parameter in the select callback as a partial/union type (MakeRouteMatchUnion), making the params property optional. Accessing match.params.id directly will fail TypeScript strict mode type checking. Use optional chaining to safely handle the potentially undefined params property:
function Match() {
const number = useMatch({
strict: false,
- select: (match) => runPerfSelectorComputation(Number(match.params.id ?? 0)),
+ select: (match) => runPerfSelectorComputation(Number(match?.params?.id ?? 0)),
})
return <>{runPerfSelectorComputation(number())}</>
}📝 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.
| function Match() { | |
| const number = useMatch({ | |
| strict: false, | |
| select: (match) => runPerfSelectorComputation(Number(match.params.id ?? 0)), | |
| }) | |
| return null | |
| return <>{runPerfSelectorComputation(number())}</> | |
| } | |
| function Match() { | |
| const number = useMatch({ | |
| strict: false, | |
| select: (match) => runPerfSelectorComputation(Number(match?.params?.id ?? 0)), | |
| }) | |
| return <>{runPerfSelectorComputation(number())}</> | |
| } |
🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.
In `@benchmarks/client-nav/solid/app.tsx` around lines 43 - 49, The select
callback in Match is accessing match.params.id but with strict: false
match.params can be undefined; update the select to use optional chaining (e.g.
select: (match) => runPerfSelectorComputation(Number(match.params?.id ?? 0))) so
params is safely accessed, and ensure any other direct uses of match.params in
Match use the same match.params?.id pattern; keep using
runPerfSelectorComputation and the number() call as-is.
There was a problem hiding this comment.
Actionable comments posted: 2
♻️ Duplicate comments (1)
benchmarks/client-nav/solid/app.tsx (1)
43-47:⚠️ Potential issue | 🟡 MinorGuard
match.paramsaccess inMatch(Line 46).This still directly dereferences
match.params.idwhile usingstrict: false. Please make it null-safe to satisfy strict type-safety expectations.Suggested patch
function Match() { const number = useMatch({ strict: false, - select: (match) => runPerfSelectorComputation(Number(match.params.id ?? 0)), + select: (match) => + runPerfSelectorComputation(Number(match.params?.id ?? 0)), }) return <>{runPerfSelectorComputation(number())}</> }In `@tanstack/solid-router`, when useMatch is called with `strict: false` (and no `from`), is `params` guaranteed to exist in the `select` callback type, or should it be treated as optional?As per coding guidelines:
**/*.{ts,tsx}: Use TypeScript strict mode with extensive type safety.🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed. In `@benchmarks/client-nav/solid/app.tsx` around lines 43 - 47, In Match's useMatch select callback, guard against match.params being undefined before dereferencing id: update the select handler passed to useMatch (in the Match function) so it safely reads match?.params?.id (or checks match && match.params) and falls back to 0 when absent, then pass Number(...) into runPerfSelectorComputation; ensure this null-safe access is used since useMatch is called with strict: false.
🧹 Nitpick comments (1)
benchmarks/client-nav/react/app.tsx (1)
85-89: Add type annotation and normalization tovalidateSearchfor strict type safety.Line 85 currently forwards raw
search.idwithout validation. TanStack Router's recommended pattern is to treatvalidateSearchas a normalization step—apply type conversions (e.g., ensuring string values have a fallback), and match the shape used downstream inloaderDeps(line 88). This ensures the typed shape is guaranteed at the boundary rather than relying on runtime assumptions.♻️ Proposed type-safe normalization
const route = createRoute({ getParentRoute: () => root, path: '/$id', - validateSearch: (search) => ({ id: search.id }), + validateSearch: (search: Record<string, unknown>) => { + const id = typeof search.id === 'string' ? search.id : '0' + return { id } + }, component: () => <div />, beforeLoad: () => Promise.resolve(), loaderDeps: ({ search }) => ({ id: search.id }), loader: () => Promise.resolve(), })🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed. In `@benchmarks/client-nav/react/app.tsx` around lines 85 - 89, validateSearch currently returns raw search.id with no typing or normalization; update the validateSearch function to accept a typed parameter (e.g., the search/query shape) and return a normalized and typed object matching loaderDeps (same keys as loaderDeps: id), applying conversions/fallbacks (e.g., coerce to string, provide default when undefined/null, or parse numbers if expected) so callers receive a guaranteed shape; locate validateSearch and loaderDeps in the route config and ensure their return types align (same interface/type) and add the normalization logic to validateSearch to enforce the shape used downstream.
🤖 Prompt for all review comments with AI agents
Verify each finding against the current code and only fix it if needed.
Inline comments:
In `@benchmarks/client-nav/react/app.tsx`:
- Around line 42-47: The Match component is using useMatch without binding to
the route that contains the id param, so match.params.id is always undefined;
update the useMatch call in Match() to include the from option pointing to the
'/$id' route (e.g., useMatch({ from: '/$id', strict: false, select: ... })) so
it resolves the correct route params before passing Number(match.params.id ?? 0)
into runPerfSelectorComputation.
In `@benchmarks/client-nav/vue/app.tsx`:
- Around line 100-104: The validateSearch function lacks an explicit parameter
type and must narrow the id for strict mode; update validateSearch's signature
to annotate its parameter (e.g., validateSearch: (search: { id?: unknown }) =>
...) and perform a runtime narrowing/validation of search.id (e.g., check typeof
search.id === 'string' or another expected type and throw or return a default if
invalid) before returning { id: search.id } so the returned id has a concrete
type; mirror the same explicit typing/narrowing approach for loaderDeps: ({
search }) => ({ id: search.id }) so both validateSearch and loaderDeps use the
same declared search shape and well-typed id.
---
Duplicate comments:
In `@benchmarks/client-nav/solid/app.tsx`:
- Around line 43-47: In Match's useMatch select callback, guard against
match.params being undefined before dereferencing id: update the select handler
passed to useMatch (in the Match function) so it safely reads match?.params?.id
(or checks match && match.params) and falls back to 0 when absent, then pass
Number(...) into runPerfSelectorComputation; ensure this null-safe access is
used since useMatch is called with strict: false.
---
Nitpick comments:
In `@benchmarks/client-nav/react/app.tsx`:
- Around line 85-89: validateSearch currently returns raw search.id with no
typing or normalization; update the validateSearch function to accept a typed
parameter (e.g., the search/query shape) and return a normalized and typed
object matching loaderDeps (same keys as loaderDeps: id), applying
conversions/fallbacks (e.g., coerce to string, provide default when
undefined/null, or parse numbers if expected) so callers receive a guaranteed
shape; locate validateSearch and loaderDeps in the route config and ensure their
return types align (same interface/type) and add the normalization logic to
validateSearch to enforce the shape used downstream.
ℹ️ Review info
⚙️ Run configuration
Configuration used: defaults
Review profile: CHILL
Plan: Pro
Run ID: ba327672-dd12-4463-bf27-e7a12f22030f
📒 Files selected for processing (6)
benchmarks/client-nav/react/app.tsxbenchmarks/client-nav/react/setup.tsbenchmarks/client-nav/solid/app.tsxbenchmarks/client-nav/solid/setup.tsbenchmarks/client-nav/vue/app.tsxbenchmarks/client-nav/vue/setup.ts
| function Match() { | ||
| const number = useMatch({ | ||
| strict: false, | ||
| select: (match) => runPerfSelectorComputation(Number(match.params.id ?? 0)), | ||
| }) | ||
| return runPerfSelectorComputation(number) |
There was a problem hiding this comment.
🧩 Analysis chain
🏁 Script executed:
#!/bin/bash
set -euo pipefail
# First, understand the complete structure of the benchmark file
echo "=== BENCHMARK FILE STRUCTURE ==="
wc -l benchmarks/client-nav/react/app.tsxRepository: TanStack/router
Length of output: 131
🏁 Script executed:
#!/bin/bash
set -euo pipefail
# Get the full file to understand route structure and Match usage
echo "=== FULL BENCHMARK FILE ==="
cat -n benchmarks/client-nav/react/app.tsxRepository: TanStack/router
Length of output: 3066
🏁 Script executed:
#!/bin/bash
set -euo pipefail
# Find useMatch implementation and its behavior
echo "=== USEWATCH IMPLEMENTATION ==="
find . -type f \( -name "*.ts" -o -name "*.tsx" \) | xargs grep -l "export.*useMatch\|export const useMatch" | head -5Repository: TanStack/router
Length of output: 289
🏁 Script executed:
#!/bin/bash
set -euo pipefail
# Search for useMatch in the main source code
rg -nP 'export.*function useMatch|export const useMatch' --type=ts --type=tsxRepository: TanStack/router
Length of output: 86
🏁 Script executed:
#!/bin/bash
set -euo pipefail
# Get useMatch implementation
echo "=== USEWATCH IMPLEMENTATION ==="
cat -n packages/react-router/src/useMatch.tsxRepository: TanStack/router
Length of output: 4193
🏁 Script executed:
#!/bin/bash
set -euo pipefail
# Search for how useMatch resolves when from is not specified
rg -nP 'matchContext|nearestMatchId|from.*:' packages/react-router/src/useMatch.tsx -A5 -B2Repository: TanStack/router
Length of output: 1035
🏁 Script executed:
#!/bin/bash
set -euo pipefail
# Check what strict: false does in useMatch
rg -nP 'strict.*false|strict.*true|strict\s*:' packages/react-router/src/useMatch.tsx -B3 -A3Repository: TanStack/router
Length of output: 41
🏁 Script executed:
#!/bin/bash
set -euo pipefail
# Look at useSearch and useParams to understand the pattern
echo "=== COMPARISON: useParams IMPLEMENTATION ==="
rg -nP 'export function useParams|export const useParams' -A30 packages/react-router/src/ --type ts --type tsx | head -50Repository: TanStack/router
Length of output: 148
🏁 Script executed:
#!/bin/bash
set -euo pipefail
# Check matchContext to understand what nearestMatchId represents
cat -n packages/react-router/src/matchContext.tsxRepository: TanStack/router
Length of output: 434
🏁 Script executed:
#!/bin/bash
set -euo pipefail
# Verify useParams implementation to compare patterns
cat -n packages/react-router/src/useParams.tsx | head -100Repository: TanStack/router
Length of output: 3445
🏁 Script executed:
#!/bin/bash
set -euo pipefail
# Check how Root component would be rendered and what match context it would have
# Also verify the route structure initialization
cat -n benchmarks/client-nav/react/app.tsx | grep -A50 "const root\|const route\|createRouter"Repository: TanStack/router
Length of output: 2385
🏁 Script executed:
#!/bin/bash
set -euo pipefail
# Look for where matchContext is set/provided in the router
rg -nP 'matchContext\.Provider|matchContext.*value' packages/react-router/src/ -B2 -A2Repository: TanStack/router
Length of output: 1446
🏁 Script executed:
#!/bin/bash
set -euo pipefail
# Check the Match component to see how matchContext is set
cat -n packages/react-router/src/Match.tsx | head -100Repository: TanStack/router
Length of output: 4209
🏁 Script executed:
#!/bin/bash
set -euo pipefail
# Verify the root route setup - does it have params?
rg -nP 'createRootRoute|path.*root' benchmarks/client-nav/react/app.tsx -B2 -A5Repository: TanStack/router
Length of output: 347
Bind useMatch to the /$id route to access its parameters.
The Match() component is rendered inside the root route's match context. When useMatch({ strict: false }) is called without a from option, it resolves to the nearest match—the root route—which has no id parameter. This causes match.params.id to always be undefined, falling back to 0, which doesn't reflect actual navigation state and reduces performance variation in this benchmark.
Suggested fix
function Match() {
const number = useMatch({
- strict: false,
- select: (match) => runPerfSelectorComputation(Number(match.params.id ?? 0)),
+ from: '/$id',
+ shouldThrow: false,
+ select: (match) => runPerfSelectorComputation(Number(match?.params.id ?? 0)),
})
return runPerfSelectorComputation(number)
}📝 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.
| function Match() { | |
| const number = useMatch({ | |
| strict: false, | |
| select: (match) => runPerfSelectorComputation(Number(match.params.id ?? 0)), | |
| }) | |
| return runPerfSelectorComputation(number) | |
| function Match() { | |
| const number = useMatch({ | |
| from: '/$id', | |
| shouldThrow: false, | |
| select: (match) => runPerfSelectorComputation(Number(match?.params.id ?? 0)), | |
| }) | |
| return runPerfSelectorComputation(number) | |
| } |
🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.
In `@benchmarks/client-nav/react/app.tsx` around lines 42 - 47, The Match
component is using useMatch without binding to the route that contains the id
param, so match.params.id is always undefined; update the useMatch call in
Match() to include the from option pointing to the '/$id' route (e.g.,
useMatch({ from: '/$id', strict: false, select: ... })) so it resolves the
correct route params before passing Number(match.params.id ?? 0) into
runPerfSelectorComputation.
| validateSearch: (search) => ({ id: search.id }), | ||
| component: () => <div />, | ||
| beforeLoad: () => Promise.resolve(), | ||
| loaderDeps: ({ search }) => ({ id: search.id }), | ||
| loader: () => Promise.resolve(), |
There was a problem hiding this comment.
🧩 Analysis chain
🏁 Script executed:
#!/bin/bash
set -euo pipefail
echo "== Check if file exists and examine the code =="
if [ -f benchmarks/client-nav/vue/app.tsx ]; then
echo "File exists. Showing lines 95-110:"
sed -n '95,110p' benchmarks/client-nav/vue/app.tsx
else
echo "File not found at benchmarks/client-nav/vue/app.tsx"
fi
echo
echo "== List files in benchmarks/client-nav =="
fd . benchmarks/client-nav --max-depth 2 -t f | head -20Repository: TanStack/router
Length of output: 1372
🏁 Script executed:
#!/bin/bash
set -euo pipefail
echo "== Check TypeScript strict mode settings =="
find . -name "tsconfig.json" -type f | head -10 | while read f; do
echo "--- $f ---"
jq '.compilerOptions | {strict, noImplicitAny, noImplicitThis}' "$f" 2>/dev/null || cat "$f"
doneRepository: TanStack/router
Length of output: 1281
🏁 Script executed:
#!/bin/bash
set -euo pipefail
echo "== Search for validateSearch patterns =="
rg -n "validateSearch" benchmarks --type tsx --type ts -A 2 | head -50Repository: TanStack/router
Length of output: 144
🏁 Script executed:
#!/bin/bash
set -euo pipefail
echo "== Check root and parent tsconfigs =="
for f in ./tsconfig.json ./tsconfig.base.json ./benchmarks/tsconfig.json benchmarks/client-nav/tsconfig.json benchmarks/client-nav/vue/tsconfig.json; do
if [ -f "$f" ]; then
echo "--- $f ---"
head -30 "$f"
echo
fi
done
echo "== Check if benchmarks/client-nav/vue/tsconfig.json extends anything =="
cat benchmarks/client-nav/vue/tsconfig.jsonRepository: TanStack/router
Length of output: 1959
🏁 Script executed:
#!/bin/bash
set -euo pipefail
echo "== Search for validateSearch in tsx files =="
rg -n "validateSearch" benchmarks/client-nav --type-list | head
rg -n "validateSearch" benchmarks/client-nav -t ts -t tsx 2>/dev/null | head -20 || find benchmarks/client-nav -name "*.tsx" -exec rg -n "validateSearch" {} +Repository: TanStack/router
Length of output: 510
🏁 Script executed:
#!/bin/bash
set -euo pipefail
echo "== Check what the search parameter type is =="
sed -n '1,110p' benchmarks/client-nav/vue/app.tsx | tail -50Repository: TanStack/router
Length of output: 1258
🏁 Script executed:
#!/bin/bash
set -euo pipefail
echo "== Find createRoute definition and validateSearch typing =="
find . -path ./node_modules -prune -o -name "*.ts" -o -name "*.tsx" | xargs rg -l "createRoute" | grep -E "(src|packages)" | head -5
echo
echo "== Search for other validateSearch usages in non-benchmark code =="
rg -n "validateSearch" src packages --type ts --type tsx -B 1 -A 1 2>/dev/null | head -60 || find . -path ./node_modules -prune -o -path ./benchmarks -prune -o \( -name "*.ts" -o -name "*.tsx" \) -exec rg -l "validateSearch" {} +Repository: TanStack/router
Length of output: 367
🏁 Script executed:
#!/bin/bash
set -euo pipefail
echo "== Check actual React benchmark for comparison =="
if [ -f benchmarks/client-nav/react/app.tsx ]; then
echo "=== React benchmark validateSearch ==="
rg -n "validateSearch" benchmarks/client-nav/react/app.tsx -B 2 -A 2
fi
echo
echo "== Check if there are type definitions or examples for validateSearch =="
rg -n "validateSearch.*search\|search.*validateSearch" packages src e2e --type ts --type tsx -A 3 2>/dev/null | head -80 || find . -path ./node_modules -prune -o \( -name "*.ts" -o -name "*.tsx" \) -exec rg "validateSearch" {} + | grep -v benchmarks | head -20Repository: TanStack/router
Length of output: 3321
🏁 Script executed:
#!/bin/bash
set -euo pipefail
echo "== Look at the e2e example that uses explicit typing =="
if [ -f e2e/vue-router/basic-file-based-jsx/src/routes/remountDeps.tsx ]; then
rg -n "validateSearch" e2e/vue-router/basic-file-based-jsx/src/routes/remountDeps.tsx -B 2 -A 5
fi
echo
echo "== Check if TypeScript compilation would fail for the benchmark =="
echo "Looking for any build or type-check scripts..."
cat benchmarks/client-nav/package.json | grep -A 20 '"scripts"' || echo "No package.json found"Repository: TanStack/router
Length of output: 2040
🏁 Script executed:
#!/bin/bash
set -euo pipefail
echo "== Check if benchmarks are included in any type-checking or build =="
cat package.json | jq '.scripts' | head -30
echo
echo "== Look at what README says about benchmarks =="
head -50 benchmarks/client-nav/README.mdRepository: TanStack/router
Length of output: 3999
🏁 Script executed:
#!/bin/bash
set -euo pipefail
echo "== Verify the exact pattern used in e2e tests =="
cat e2e/vue-router/basic-file-based-jsx/src/routes/remountDeps.tsx | sed -n '1,80p'Repository: TanStack/router
Length of output: 548
🏁 Script executed:
#!/bin/bash
set -euo pipefail
echo "== Check what happens when running type check on benchmarks =="
echo "Trying to type-check the vue benchmark..."
tsc -p benchmarks/client-nav/vue/tsconfig.json --noEmit 2>&1 | head -30 || true
echo
echo "== Check if the pattern in the code is correct given the input =="
echo "What type does TypeScript infer for validateSearch parameter?"
cat > /tmp/check.ts << 'EOF'
import { createRoute } from '@tanstack/router'
// Checking the implicit type of search parameter
const route = createRoute({
path: '/$id',
validateSearch: (search) => ({ id: search.id }),
})
EOF
tsc --noEmit --strict /tmp/check.ts 2>&1 | head -20 || echo "Type check completed"Repository: TanStack/router
Length of output: 691
🏁 Script executed:
#!/bin/bash
set -euo pipefail
echo "== Check if React benchmark has the same issue =="
rg -n "validateSearch" benchmarks/client-nav/react/app.tsx -B 1 -A 1
echo
echo "== Do both benchmarks have identical validateSearch patterns? =="
echo "Vue:"
rg -n "validateSearch:" benchmarks/client-nav/vue/app.tsx -A 1
echo
echo "React:"
rg -n "validateSearch:" benchmarks/client-nav/react/app.tsx -A 1Repository: TanStack/router
Length of output: 463
Add explicit type annotation and narrowing to validateSearch for strict mode compliance.
The search parameter lacks explicit typing, which causes an implicit any type under strict mode. Align with e2e test patterns by annotating the parameter and narrowing the id value.
Suggested refactor
const route = createRoute({
getParentRoute: () => root,
path: '/$id',
- validateSearch: (search) => ({ id: search.id }),
+ validateSearch: (search: Record<string, unknown>) => ({
+ id: typeof search.id === 'string' ? search.id : '0',
+ }),
component: () => <div />,
beforeLoad: () => Promise.resolve(),
loaderDeps: ({ search }) => ({ id: search.id }),
loader: () => Promise.resolve(),
})📝 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.
| validateSearch: (search) => ({ id: search.id }), | |
| component: () => <div />, | |
| beforeLoad: () => Promise.resolve(), | |
| loaderDeps: ({ search }) => ({ id: search.id }), | |
| loader: () => Promise.resolve(), | |
| validateSearch: (search: Record<string, unknown>) => ({ | |
| id: typeof search.id === 'string' ? search.id : '0', | |
| }), | |
| component: () => <div />, | |
| beforeLoad: () => Promise.resolve(), | |
| loaderDeps: ({ search }) => ({ id: search.id }), | |
| loader: () => Promise.resolve(), |
🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.
In `@benchmarks/client-nav/vue/app.tsx` around lines 100 - 104, The validateSearch
function lacks an explicit parameter type and must narrow the id for strict
mode; update validateSearch's signature to annotate its parameter (e.g.,
validateSearch: (search: { id?: unknown }) => ...) and perform a runtime
narrowing/validation of search.id (e.g., check typeof search.id === 'string' or
another expected type and throw or return a default if invalid) before returning
{ id: search.id } so the returned id has a concrete type; mirror the same
explicit typing/narrowing approach for loaderDeps: ({ search }) => ({ id:
search.id }) so both validateSearch and loaderDeps use the same declared search
shape and well-typed id.
make the client-nav benchmark more representative of a "full Router app" by
useMatchtooSummary by CodeRabbit
Release Notes