Skip to content

Conversation

@schiller-manuel
Copy link
Contributor

@schiller-manuel schiller-manuel commented Jan 24, 2026

Summary by CodeRabbit

  • Chores
    • Improved server-side rendering performance by skipping client-only state subscriptions and deep-equality work on the server, reducing latency for server-rendered pages.
    • Added a lightweight server batching path to avoid unnecessary overhead while preserving client-side batching behavior.
    • Introduced an optimized server-side router state store to simplify and speed up server state handling and updates.

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

@schiller-manuel schiller-manuel changed the title performance: don't use replaceEqualDeep on the server performance: no reactivity on the server Jan 24, 2026
@coderabbitai
Copy link
Contributor

coderabbitai bot commented Jan 24, 2026

📝 Walkthrough

Walkthrough

Adds SSR-aware short‑circuits in framework useRouterState hooks and a lightweight server router store: server paths bypass deep-equality, batching, and subscriptions; client behavior and public APIs remain unchanged.

Changes

Cohort / File(s) Summary
Framework hooks
packages/react-router/src/useRouterState.tsx, packages/solid-router/src/useRouterState.tsx, packages/vue-router/src/useRouterState.tsx
Add isServer detection and an SSR short‑circuit that returns the current/selected router state directly (no subscription, structural sharing, or batching) when running on the server or when router.isServer is true.
Router core: server store & state utils
packages/router-core/src/router.ts, packages/router-core/src/utils.ts
Introduce RouterStateStore<TState> and createServerStore; use server store during build when isServer; short‑circuit replaceEqualDeep to return _next when isServer is true; adjust imports to use local batch util.
Router core: batching util
packages/router-core/src/utils/batch.ts
Add batch<T>(fn: () => T): T that no-ops (direct call) on server and delegates to @tanstack/store batching on client.
Router core: callsite import updates
packages/router-core/src/load-matches.ts, packages/router-core/src/ssr/ssr-client.ts
Replace batch import source from @tanstack/store to local ../utils/batch.
Manifest
package.json
Minor manifest edits (few lines changed).

Sequence Diagram(s)

sequenceDiagram
  participant Builder as RouterCore.build()
  participant Env as isServer
  participant ServerStore as createServerStore
  participant ClientStore as Store(onUpdate)
  participant Batch as batch()

  Builder->>Env: check isServer
  alt isServer == true
    Builder->>ServerStore: createServerStore(initialState)
    ServerStore-->>Builder: state holder (state + setState)
    note right of ServerStore: no subscriptions\nno batching
  else isServer == false
    Builder->>ClientStore: new Store(initialState, { onUpdate })
    ClientStore->>Batch: wrap updates with batch(fn)
    Batch-->>ClientStore: execute batched updates
    ClientStore-->>Subscribers: notify subscribers (useRouterState, etc.)
  end
Loading

Estimated code review effort

🎯 3 (Moderate) | ⏱️ ~20 minutes

Possibly related PRs

Poem

🐰 I nibble through branches, light and fleet,
On servers I skip the deep‑diving feat,
I hand you the state, tidy and small,
Clients still listen when updates call,
Hopping on — both fast and neat.

🚥 Pre-merge checks | ✅ 2 | ❌ 1
❌ Failed checks (1 warning)
Check name Status Explanation Resolution
Docstring Coverage ⚠️ Warning Docstring coverage is 50.00% which is insufficient. The required threshold is 80.00%. Write docstrings for the functions missing them to satisfy the coverage threshold.
✅ Passed checks (2 passed)
Check name Status Explanation
Description Check ✅ Passed Check skipped - CodeRabbit’s high-level summary is enabled.
Title check ✅ Passed The title clearly and concisely describes the main optimization: disabling reactivity on the server for performance.

✏️ Tip: You can configure your own custom pre-merge checks in the settings.

✨ Finishing touches
  • 📝 Generate docstrings

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

@nx-cloud
Copy link

nx-cloud bot commented Jan 24, 2026

View your CI Pipeline Execution ↗ for commit db465e1

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

☁️ Nx Cloud last updated this comment at 2026-01-25 00:21:37 UTC

@pkg-pr-new
Copy link

pkg-pr-new bot commented Jan 24, 2026

More templates

@tanstack/arktype-adapter

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

@tanstack/eslint-plugin-router

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

@tanstack/history

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

@tanstack/nitro-v2-vite-plugin

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

@tanstack/react-router

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

@tanstack/react-router-devtools

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

@tanstack/react-router-ssr-query

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

@tanstack/react-start

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

@tanstack/react-start-client

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

@tanstack/react-start-server

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

@tanstack/router-cli

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

@tanstack/router-core

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

@tanstack/router-devtools

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

@tanstack/router-devtools-core

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

@tanstack/router-generator

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

@tanstack/router-plugin

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

@tanstack/router-ssr-query-core

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

@tanstack/router-utils

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

@tanstack/router-vite-plugin

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

@tanstack/solid-router

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

@tanstack/solid-router-devtools

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

@tanstack/solid-router-ssr-query

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

@tanstack/solid-start

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

@tanstack/solid-start-client

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

@tanstack/solid-start-server

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

@tanstack/start-client-core

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

@tanstack/start-fn-stubs

npm i https://pkg.pr.new/TanStack/router/@tanstack/start-fn-stubs@6497

@tanstack/start-plugin-core

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

@tanstack/start-server-core

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

@tanstack/start-static-server-functions

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

@tanstack/start-storage-context

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

@tanstack/valibot-adapter

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

@tanstack/virtual-file-routes

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

@tanstack/vue-router

npm i https://pkg.pr.new/TanStack/router/@tanstack/vue-router@6497

@tanstack/vue-router-devtools

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

@tanstack/vue-router-ssr-query

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

@tanstack/vue-start

npm i https://pkg.pr.new/TanStack/router/@tanstack/vue-start@6497

@tanstack/vue-start-client

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

@tanstack/vue-start-server

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

@tanstack/zod-adapter

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

commit: db465e1

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

🤖 Fix all issues with AI agents
In `@packages/router-core/src/router.ts`:
- Around line 902-923: The server store is being unsafely cast to a generic
Store which weakens type safety; change the __store declaration to use the
concrete RouterStateStore type produced by createServerStore and parameterize it
with RouterState<TRouteTree> (i.e., use
RouterStateStore<RouterState<TRouteTree>> instead of casting to Store<any>),
remove the cast, and ensure usages rely on the strongly typed .state and
.setState provided by createServerStore so the compiler enforces correct types.

In `@packages/router-core/src/utils/batch.ts`:
- Around line 5-16: The current batch implementation uses the module-level
isServer flag and ignores any runtime override on the router, so calling batch
will use storeBatch even when router.options.isServer requests server behavior;
update batch to accept or consult a runtime override (e.g., an optional second
parameter or check a provided runtime flag) and prefer router.options.isServer
when present, or expose a wrapper used by callers that passes
router.options.isServer into batch; specifically, modify the export function
batch<T>(fn: () => T[, runtimeIsServer?: boolean]) to check runtimeIsServer ||
isServer before calling storeBatch (or change call sites to pass
router.options.isServer into batch), ensuring symbols batch, isServer,
storeBatch and router.options.isServer are used to determine the correct branch
at runtime.

Comment on lines +902 to +923
type RouterStateStore<TState> = {
state: TState
setState: (updater: (prev: TState) => TState) => void
}

function createServerStore<TState>(
initialState: TState,
): RouterStateStore<TState> {
let state = initialState

return {
get state() {
return state
},
set state(next) {
state = next
},
setState: (updater: (prev: TState) => TState) => {
state = updater(state)
},
} as RouterStateStore<TState>
}
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 | 🟠 Major

🧩 Analysis chain

🏁 Script executed:

# First, find the Store type definition and __store usage patterns
rg -n "type Store<|interface Store<" packages/router-core/src/ -A 10

Repository: TanStack/router

Length of output: 41


🏁 Script executed:

# Find all __store usages to see what methods are called
rg -n "\.__store\." packages/router-core/src/ -B 2 -A 2

Repository: TanStack/router

Length of output: 5763


🏁 Script executed:

# Check the RouterStateStore type definition and createServerStore function
cat -n packages/router-core/src/router.ts | sed -n '902,923p'

Repository: TanStack/router

Length of output: 667


🏁 Script executed:

# Look for the __store assignment and its type annotation
rg -n "__store" packages/router-core/src/router.ts -B 5 -A 5 | head -50

Repository: TanStack/router

Length of output: 1499


🏁 Script executed:

# Find the Store type/class definition
rg -n "class Store<|interface Store<|type Store<|export.*Store" packages/router-core/src/ -A 15 | head -100

Repository: TanStack/router

Length of output: 41


🏁 Script executed:

# Check if Store is imported from elsewhere
rg -n "^import.*Store|^export.*Store" packages/router-core/src/router.ts -B 2 -A 2

Repository: TanStack/router

Length of output: 230


🏁 Script executed:

# Search for Store definition in the entire packages/router-core
fd -e ts -e tsx . packages/router-core/src/ | xargs rg -l "class Store|interface Store" | head -5

Repository: TanStack/router

Length of output: 41


🏁 Script executed:

# Check if .subscribe is called on __store or router store
rg -n "\.subscribe\(|subscribe.*store|store.*subscribe" packages/router-core/src/ -i

Repository: TanStack/router

Length of output: 417


🏁 Script executed:

# Check what methods are actually called on __store across all usages
rg -n "\.__store\.[a-zA-Z_$]+" packages/router-core/src/ -o --replace '$0' | sort | uniq -c | sort -rn

Repository: TanStack/router

Length of output: 1183


🏁 Script executed:

# Search for Store method calls to identify the actual API
rg -n "subscribe|notify|getState|setState" packages/router-core/src/router.ts -i

Repository: TanStack/router

Length of output: 1191


🏁 Script executed:

# Check if Store is used elsewhere to understand its full API
rg -n "new Store\(" packages/router-core/src/ -B 2 -A 5 | head -40

Repository: TanStack/router

Length of output: 720


Unsound Store cast weakens type safety for the server store.

createServerStore returns RouterStateStore<TState> but is coerced to Store<any> via cast, which hides the type mismatch and violates TypeScript strict mode requirements. While current usage only calls .state and .setState() (both implemented in RouterStateStore), the cast removes compiler protections. Per coding guidelines requiring extensive type safety, change __store to RouterStateStore<RouterState<TRouteTree>> and remove the cast.

♻️ Proposed refactor
-  __store!: Store<RouterState<TRouteTree>>
+  __store!: RouterStateStore<RouterState<TRouteTree>>

-        this.__store = createServerStore(
-          getInitialRouterState(this.latestLocation),
-        ) as unknown as Store<any>
+        this.__store = createServerStore(
+          getInitialRouterState(this.latestLocation),
+        )
🤖 Prompt for AI Agents
In `@packages/router-core/src/router.ts` around lines 902 - 923, The server store
is being unsafely cast to a generic Store which weakens type safety; change the
__store declaration to use the concrete RouterStateStore type produced by
createServerStore and parameterize it with RouterState<TRouteTree> (i.e., use
RouterStateStore<RouterState<TRouteTree>> instead of casting to Store<any>),
remove the cast, and ensure usages rely on the strongly typed .state and
.setState provided by createServerStore so the compiler enforces correct types.

Comment on lines +5 to +16
// `@tanstack/store`'s `batch` is for reactive notification batching.
// On the server we don't subscribe/render reactively, so a lightweight
// implementation that just executes is enough.
export function batch<T>(fn: () => T): T {
if (isServer) {
return fn()
}

let result!: T
storeBatch(() => {
result = fn()
})
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 | 🟡 Minor

🧩 Analysis chain

🏁 Script executed:

fd -a -t f 'isServer\.(ts|tsx|js|mjs|cjs)$' | head -n 20

Repository: TanStack/router

Length of output: 41


🏁 Script executed:

rg -t ts -t tsx 'isServer' packages/router-core/src/utils/batch.ts -B 10 -A 5

Repository: TanStack/router

Length of output: 86


🏁 Script executed:

rg 'isServer' packages/router-core/src/utils/batch.ts -B 10 -A 5

Repository: TanStack/router

Length of output: 482


🏁 Script executed:

rg -l 'isServer' packages/router-core/src --type ts | head -20

Repository: TanStack/router

Length of output: 426


🏁 Script executed:

fd -t f . packages/router-core/src/isServer | sort

Repository: TanStack/router

Length of output: 195


🏁 Script executed:

cat packages/router-core/src/utils.ts | head -100

Repository: TanStack/router

Length of output: 2732


🏁 Script executed:

rg 'export.*isServer' packages/router-core/src/isServer/

Repository: TanStack/router

Length of output: 309


🏁 Script executed:

rg 'options.*isServer|isServer.*option' packages/router-core/src --type ts -B 3 -A 3

Repository: TanStack/router

Length of output: 956


🏁 Script executed:

rg 'batch\(' packages/router-core/src --type ts -B 3 -A 3 | head -60

Repository: TanStack/router

Length of output: 3336


🏁 Script executed:

cat packages/router-core/package.json | rg -A 20 'exports' | head -40

Repository: TanStack/router

Length of output: 584


🏁 Script executed:

rg 'isServer' packages/router-core/package.json

Repository: TanStack/router

Length of output: 1818


🏁 Script executed:

rg -A 50 '"./isServer"' packages/router-core/package.json | head -80

Repository: TanStack/router

Length of output: 1628


🏁 Script executed:

rg 'new Router\|createRouter' packages/router-core/src --type ts -B 3 -A 5 | head -40

Repository: TanStack/router

Length of output: 41


🏁 Script executed:

rg 'options\.isServer\s*[=:]|isServer\s*:\s*' packages/router-core/src --type ts -B 2 -A 2

Repository: TanStack/router

Length of output: 295


🏁 Script executed:

rg 'isServer.*undefined|development' packages/router-core/src -B 2 -A 2 | head -30

Repository: TanStack/router

Length of output: 1697


🏁 Script executed:

rg 'batch\(' packages/router-core/src -B 5 -A 5 | grep -A 5 -B 5 'this\.isServer\|router\.'

Repository: TanStack/router

Length of output: 3417


Verify that batch respects forced server mode.

batch branches only on module-level isServer, which is set at build time via conditional exports. If router.options.isServer is explicitly set to force server behavior at runtime (where module-level isServer is false), this will still route through @tanstack/store batching instead of the lightweight path. Either confirm that the module-level isServer definition already accounts for runtime overrides, or consider whether batch call sites should pass a runtime override flag to respect router.options.isServer.

🤖 Prompt for AI Agents
In `@packages/router-core/src/utils/batch.ts` around lines 5 - 16, The current
batch implementation uses the module-level isServer flag and ignores any runtime
override on the router, so calling batch will use storeBatch even when
router.options.isServer requests server behavior; update batch to accept or
consult a runtime override (e.g., an optional second parameter or check a
provided runtime flag) and prefer router.options.isServer when present, or
expose a wrapper used by callers that passes router.options.isServer into batch;
specifically, modify the export function batch<T>(fn: () => T[,
runtimeIsServer?: boolean]) to check runtimeIsServer || isServer before calling
storeBatch (or change call sites to pass router.options.isServer into batch),
ensuring symbols batch, isServer, storeBatch and router.options.isServer are
used to determine the correct branch at runtime.

@schiller-manuel schiller-manuel force-pushed the performance-reactivitiy branch from 5c5a9c8 to db465e1 Compare January 25, 2026 00:08
@schiller-manuel schiller-manuel merged commit 8f525a8 into main Jan 25, 2026
6 checks passed
@schiller-manuel schiller-manuel deleted the performance-reactivitiy branch January 25, 2026 01:34
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.

2 participants