Skip to content

bench(client-nav): add React, Solid, and Vue hydration scenarios - #8439

Open
Sheraff wants to merge 4 commits into
mainfrom
bench/react-client-hydration
Open

Sheraff wants to merge 4 commits into
mainfrom
bench/react-client-hydration

Conversation

@Sheraff

@Sheraff Sheraff commented Sep 15, 2026

Copy link
Copy Markdown
Collaborator

🎯 Changes

Add React, Solid, and Vue hydration scenarios to the existing benchmarks/client-nav CodSpeed suite. These cover the gap between server rendering and client mounting/navigation, including post-hydration Link updates.

Workload and measurement

Each adapter restores three matched routes, three beforeLoad contexts, two loader results, 192 ordinary Links, and eight hash-sensitive Links. The server URL has no fragment; the client URL has #details, matching half of the hash-sensitive Links.

Static HTML and real Router SSR bootstrap scripts are generated during the build. Every invocation gets a fresh jsdom window and production client bundle instance. The measured region executes the payload, creates/restores the router through its public hydration API, and hydrates the existing DOM through the follow-up updates. Server rendering, HTML parsing, initial bundle evaluation, validation, and teardown are outside measurement.

  • React: await an explicit post-hydration completion signal and two idle scheduler turns, with a 60-second watchdog. Includes the delayed-commit, delayed-scheduler, and watchdog regressions added after the initial CI settlement issue.
  • Solid: execute the native hydration bootstrap, preserve component/key ordering, and await mount, rendered state, active-link effects, and idle turns. Check identity of every workload element to catch template fallback.
  • Vue: hydrate a createSSRApp container and flush post-mount updates with nextTick. Keep production hydration-mismatch diagnostics enabled so incorrect DOM fails rather than producing a misleading benchmark result.

Solid and Vue cap settlement at 100 turns. All scenarios support both CodSpeed suite hooks and Tinybench per-iteration hooks, with diagnostic render/reactivity counting confined to harness tests. Assertions cover restored data/context, zero client loader or beforeLoad calls, DOM reuse, hrefs/active state, event handlers, and fresh-state cleanup.

Wire all three scenarios into their framework aggregate builds and type-check graph; existing Vitest/CodSpeed discovery picks them up. Update the benchmark documentation and expected CPU result count. Generated HTML and bundles remain build artifacts.

Related fix and validation

The Vue workload exercises the hash-hydration mismatch fixed by #8440. That fix is required for the strict Vue hydration assertions to pass against main; the router fixes are kept in that separate PR.

Hydration workloads were exercised during development. No checks were rerun for this update; CI will validate the aggregate integration and instrumented runs. Local wall-time results were noisy and are not used to claim a performance gain.

✅ Checklist

  • I have followed the steps in the Contributing guide.
  • I have tested code changes locally with the relevant test commands, or tests do not apply to this pull request.
  • I fully understand the code in this pull request, including any code generated with AI assistance.

🚀 Release Impact

  • This change affects published code, and I have generated a changeset.
  • This change is docs/CI/dev-only (no release).

Summary by CodeRabbit

  • New Features

    • Expanded the client-navigation hydration benchmark to cover React, Solid, and Vue.
    • Added cross-framework hydration scenarios that measure restored route state, mixed links, rendering, and interaction behavior.
    • Added framework-specific setup, validation, and performance benchmark coverage.
  • Documentation

    • Updated benchmark documentation with hydration scope, lifecycle details, execution commands, and measurement conventions.
    • Increased the expected CPU result count per repetition from 133 to 135.

@nx-cloud

nx-cloud Bot commented Sep 15, 2026

Copy link
Copy Markdown
Contributor

View your CI Pipeline Execution ↗ for commit 540230a

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

☁️ Nx Cloud last updated this comment at 2026-09-15 21:48:22 UTC

@coderabbitai

coderabbitai Bot commented Sep 15, 2026

Copy link
Copy Markdown
Contributor

Review Change StackReview Change Stack

No actionable comments were generated in the recent review. 🎉

ℹ️ Recent review info
⚙️ Run configuration

Configuration used: Path: .coderabbit.yaml

Review profile: CHILL

Plan: Advanced

Run ID: 61282054-fba5-44f7-be52-552a67eeaaec

📥 Commits

Reviewing files that changed from the base of the PR and between 0cee3ff and 61e0ab3.

📒 Files selected for processing (27)
  • benchmarks/README.md
  • benchmarks/client-nav/README.md
  • benchmarks/client-nav/package.json
  • benchmarks/client-nav/scenarios/hydration/solid/fixture.ts
  • benchmarks/client-nav/scenarios/hydration/solid/generate-fixture.mjs
  • benchmarks/client-nav/scenarios/hydration/solid/hydration.test.ts
  • benchmarks/client-nav/scenarios/hydration/solid/project.json
  • benchmarks/client-nav/scenarios/hydration/solid/setup.ts
  • benchmarks/client-nav/scenarios/hydration/solid/speed.bench.ts
  • benchmarks/client-nav/scenarios/hydration/solid/src/app.tsx
  • benchmarks/client-nav/scenarios/hydration/solid/src/client.tsx
  • benchmarks/client-nav/scenarios/hydration/solid/src/server.tsx
  • benchmarks/client-nav/scenarios/hydration/solid/tsconfig.json
  • benchmarks/client-nav/scenarios/hydration/solid/vite.config.ts
  • benchmarks/client-nav/scenarios/hydration/solid/vite.server.config.ts
  • benchmarks/client-nav/scenarios/hydration/vue/fixture.ts
  • benchmarks/client-nav/scenarios/hydration/vue/generate-fixture.mjs
  • benchmarks/client-nav/scenarios/hydration/vue/hydration.test.ts
  • benchmarks/client-nav/scenarios/hydration/vue/project.json
  • benchmarks/client-nav/scenarios/hydration/vue/setup.ts
  • benchmarks/client-nav/scenarios/hydration/vue/speed.bench.ts
  • benchmarks/client-nav/scenarios/hydration/vue/src/app.tsx
  • benchmarks/client-nav/scenarios/hydration/vue/src/client.tsx
  • benchmarks/client-nav/scenarios/hydration/vue/src/server.tsx
  • benchmarks/client-nav/scenarios/hydration/vue/tsconfig.json
  • benchmarks/client-nav/scenarios/hydration/vue/vite.config.ts
  • benchmarks/client-nav/scenarios/hydration/vue/vite.server.config.ts

Included review availability: Your plan provides up to 10 included reviews per hour; 9 remain after this review.


📝 Walkthrough

Walkthrough

The PR adds React, Solid, and Vue client-navigation hydration benchmarks. Each scenario renders an SSR fixture, hydrates it in isolated jsdom samples, validates restored router state, and integrates with Nx, Vitest, and performance benchmarks.

Changes

Client-navigation hydration benchmarks

Layer / File(s) Summary
Scope and benchmark integration
benchmarks/README.md, benchmarks/client-nav/README.md, benchmarks/client-nav/package.json
Documentation now covers hydration for React, Solid, and Vue. Framework build and type-test targets include the hydration projects.
React fixture and hydration flow
benchmarks/client-nav/scenarios/hydration/react/*
The React scenario generates an SSR fixture, restores router state in jsdom, waits for hydration and scheduler settlement, validates DOM and route data, and runs unit and performance tests.
Solid hydration flow
benchmarks/client-nav/scenarios/hydration/solid/*
The Solid scenario generates streaming SSR output, restores router and Solid hydration state, tracks reactive evaluations, validates DOM identity and route data, and runs unit and performance tests.
Vue hydration flow
benchmarks/client-nav/scenarios/hydration/vue/*
The Vue scenario generates SSR output, restores router state before mounting the SSR app, settles Vue updates, validates rendered output, and runs unit and performance tests.

Priority: ⬇️ Low

Estimated code review effort: 4 (Complex) | ~60 minutes

Change: Other

Merge Risk: ⚪ Minimal · up to 61e0a

The development-only benchmark additions appear mergeable after normal checks, with no identified production or benchmark workflow risk requiring correction.

🚥 Pre-merge checks | ✅ 4 | ❌ 1

❌ Failed checks (1 warning)

Check name Status Explanation Resolution
Docstring Coverage ⚠️ Warning Docstring coverage is 0.00% which is insufficient. The required threshold is 80.00%. Docstring coverage is scoped to functions touched by this diff. Analyzed 57 functions across 31 files. (7 skipped: … Write docstrings for the functions missing them to satisfy the coverage threshold.
✅ Passed checks (4 passed)
Check name Status Explanation
Linked Issues check ✅ Passed Check skipped because no linked issues were found for this pull request.
Out of Scope Changes check ✅ Passed Check skipped because no linked issues were found for this pull request.
Title check ✅ Passed The title clearly and concisely identifies the main change: adding React, Solid, and Vue hydration scenarios to the client-nav benchmark.
Description check ✅ Passed The description covers the changes, workload, measurement boundaries, validation, release impact, and checklist. It is mostly complete, but the code-understanding checklist item remains unchecked and …
Full details: Docstring Coverage

Explanation

Docstring coverage is 0.00% which is insufficient. The required threshold is 80.00%. Docstring coverage is scoped to functions touched by this diff. Analyzed 57 functions across 31 files. (7 skipped: 7 unsupported.)

  • Fix all pre-merge checks with AI
✨ Finishing Touches 💡 1
📝 Generate docstrings 💡
  • Create stacked PR
  • Commit on current branch
🧪 Generate unit tests (beta)
  • Create PR with unit tests
  • Commit unit tests in branch bench/react-client-hydration

Thanks for using CodeRabbit! It's free for OSS, and your support helps us grow. If you like it, consider giving us a shout-out.

❤️ Share

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

@github-actions

Copy link
Copy Markdown
Contributor

🚀 Changeset Version Preview

4 package(s) bumped directly, 19 bumped as dependents.

🟩 Patch bumps

Package Version Reason
@tanstack/react-router 1.170.36 → 1.170.37 Changeset
@tanstack/router-core 1.171.30 → 1.171.31 Changeset
@tanstack/solid-router 1.170.34 → 1.170.35 Changeset
@tanstack/vue-router 1.170.33 → 1.170.34 Changeset
@tanstack/react-start 1.168.54 → 1.168.55 Dependent
@tanstack/react-start-client 1.168.34 → 1.168.35 Dependent
@tanstack/react-start-rsc 0.1.53 → 0.1.54 Dependent
@tanstack/react-start-server 1.167.41 → 1.167.42 Dependent
@tanstack/router-cli 1.167.36 → 1.167.37 Dependent
@tanstack/router-generator 1.167.36 → 1.167.37 Dependent
@tanstack/router-plugin 1.168.38 → 1.168.39 Dependent
@tanstack/router-vite-plugin 1.167.38 → 1.167.39 Dependent
@tanstack/solid-start 1.168.52 → 1.168.53 Dependent
@tanstack/solid-start-client 1.168.33 → 1.168.34 Dependent
@tanstack/solid-start-server 1.167.40 → 1.167.41 Dependent
@tanstack/start-client-core 1.170.30 → 1.170.31 Dependent
@tanstack/start-plugin-core 1.171.44 → 1.171.45 Dependent
@tanstack/start-server-core 1.169.35 → 1.169.36 Dependent
@tanstack/start-static-server-functions 1.167.35 → 1.167.36 Dependent
@tanstack/start-storage-context 1.167.32 → 1.167.33 Dependent
@tanstack/vue-start 1.168.51 → 1.168.52 Dependent
@tanstack/vue-start-client 1.167.36 → 1.167.37 Dependent
@tanstack/vue-start-server 1.167.40 → 1.167.41 Dependent

@github-actions

Copy link
Copy Markdown
Contributor

Bundle Size Benchmarks

This pull request does not affect bundle size in any measured scenario.

@pkg-pr-new

pkg-pr-new Bot commented Sep 15, 2026

Copy link
Copy Markdown
More templates

@tanstack/arktype-adapter

npm i https://pkg.pr.new/@tanstack/arktype-adapter@8439

@tanstack/eslint-plugin-router

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

@tanstack/eslint-plugin-start

npm i https://pkg.pr.new/@tanstack/eslint-plugin-start@8439

@tanstack/history

npm i https://pkg.pr.new/@tanstack/history@8439

@tanstack/nitro-v2-vite-plugin

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

@tanstack/react-router

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

@tanstack/react-router-devtools

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

@tanstack/react-router-ssr-query

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

@tanstack/react-start

npm i https://pkg.pr.new/@tanstack/react-start@8439

@tanstack/react-start-client

npm i https://pkg.pr.new/@tanstack/react-start-client@8439

@tanstack/react-start-rsc

npm i https://pkg.pr.new/@tanstack/react-start-rsc@8439

@tanstack/react-start-server

npm i https://pkg.pr.new/@tanstack/react-start-server@8439

@tanstack/router-cli

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

@tanstack/router-core

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

@tanstack/router-devtools

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

@tanstack/router-devtools-core

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

@tanstack/router-generator

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

@tanstack/router-plugin

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

@tanstack/router-ssr-query-core

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

@tanstack/router-utils

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

@tanstack/router-vite-plugin

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

@tanstack/solid-router

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

@tanstack/solid-router-devtools

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

@tanstack/solid-router-ssr-query

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

@tanstack/solid-start

npm i https://pkg.pr.new/@tanstack/solid-start@8439

@tanstack/solid-start-client

npm i https://pkg.pr.new/@tanstack/solid-start-client@8439

@tanstack/solid-start-server

npm i https://pkg.pr.new/@tanstack/solid-start-server@8439

@tanstack/start-client-core

npm i https://pkg.pr.new/@tanstack/start-client-core@8439

@tanstack/start-fn-stubs

npm i https://pkg.pr.new/@tanstack/start-fn-stubs@8439

@tanstack/start-plugin-core

npm i https://pkg.pr.new/@tanstack/start-plugin-core@8439

@tanstack/start-server-core

npm i https://pkg.pr.new/@tanstack/start-server-core@8439

@tanstack/start-static-server-functions

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

@tanstack/start-storage-context

npm i https://pkg.pr.new/@tanstack/start-storage-context@8439

@tanstack/valibot-adapter

npm i https://pkg.pr.new/@tanstack/valibot-adapter@8439

@tanstack/virtual-file-routes

npm i https://pkg.pr.new/@tanstack/virtual-file-routes@8439

@tanstack/vue-router

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

@tanstack/vue-router-devtools

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

@tanstack/vue-router-ssr-query

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

@tanstack/vue-start

npm i https://pkg.pr.new/@tanstack/vue-start@8439

@tanstack/vue-start-client

npm i https://pkg.pr.new/@tanstack/vue-start-client@8439

@tanstack/vue-start-server

npm i https://pkg.pr.new/@tanstack/vue-start-server@8439

@tanstack/zod-adapter

npm i https://pkg.pr.new/@tanstack/zod-adapter@8439

commit: 61e0ab3

@codspeed

codspeed Bot commented Sep 15, 2026

Copy link
Copy Markdown

Merging this PR will not alter performance

✅ 167 untouched benchmarks
🆕 2 new benchmarks
⏩ 13 skipped benchmarks1

Performance Changes

Mode Benchmark BASE HEAD Efficiency
🆕 Simulation hydrate restored route state and mixed Links (react) N/A 303.9 ms N/A
🆕 Simulation hydrate restored route state and mixed Links (solid) N/A 415.6 ms N/A

Comparing bench/react-client-hydration (61e0ab3) with main (83895f1)

Open in CodSpeed

Footnotes

  1. 13 benchmarks were skipped, so the baseline results were used instead. If they were deleted from the codebase, click here and archive them to remove them from the performance reports.

@codspeed

codspeed Bot commented Sep 15, 2026

Copy link
Copy Markdown

Merging this PR will not alter performance

✅ 180 untouched benchmarks
🆕 1 new benchmark

Performance Changes

Mode Benchmark BASE HEAD Efficiency
🆕 Simulation hydrate restored route state and mixed Links (react) N/A 289.3 ms N/A

Comparing bench/react-client-hydration (0cee3ff) with main (8e164d2)

Open in CodSpeed

@Sheraff Sheraff changed the title bench(client-nav): add React hydration scenario bench(client-nav): add React, Solid, and Vue hydration scenarios Sep 15, 2026
nx-cloud[bot]

This comment was marked as outdated.

@nx-cloud nx-cloud Bot left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

Nx Cloud is proposing a fix for your failed CI:

We fix the Vue hydration class mismatch by mirroring the existing React pattern: the includeHash active-state comparison in getIsActive is now gated behind an isHydrated flag, which is false during the initial hydration render and becomes true after onMounted fires. This ensures the Vue Link's first client-side render matches the server-rendered DOM (where the hash is unknown), eliminating the class="null" vs class="active" mismatch that caused the benchmark harness to throw on every iteration.

Tip

We verified this fix by re-running @benchmarks/client-nav-hydration-vue:test:unit.

Suggested Fix changes
diff --git a/packages/vue-router/src/link.tsx b/packages/vue-router/src/link.tsx
index 6d2fea69..21a8c69e 100644
--- a/packages/vue-router/src/link.tsx
+++ b/packages/vue-router/src/link.tsx
@@ -10,6 +10,7 @@ import {
 import { isServer } from '@tanstack/router-core/isServer'
 
 import { useSelector } from '@tanstack/vue-store'
+import { useHydrated } from './ClientOnly'
 import { useRouter } from './useRouter'
 import { useIntersectionObserver } from './utils'
 
@@ -136,6 +137,7 @@ function useLinkPropsImpl(
             next,
             options.activeOptions,
             router,
+            false,
           )
 
     const { resolvedProps, resolvedClass, resolvedStyle } = resolveStyleProps(
@@ -159,6 +161,8 @@ function useLinkPropsImpl(
 
   // Determine if the link is external or internal. This is client-only so
   // server renders do not allocate a computed wrapper for every link.
+  const isHydrated = useHydrated()
+
   const isExternal = Vue.computed(() => !!getUrlScheme(`${getOptions().to}`))
 
   const currentLocation: Vue.Ref<
@@ -234,6 +238,7 @@ function useLinkPropsImpl(
       next.value,
       options.activeOptions,
       router,
+      isHydrated.value,
     )
   })
 
@@ -663,6 +668,7 @@ function getIsActive(
   },
   activeOptions: LinkOptions['activeOptions'],
   router: AnyRouter,
+  isHydrated = true,
 ) {
   const currentPath = removeTrailingSlash(loc.pathname, router.basepath)
   const nextPath = removeTrailingSlash(nextLoc.pathname, router.basepath)
@@ -693,7 +699,7 @@ function getIsActive(
   }
 
   if (activeOptions?.includeHash) {
-    return loc.hash === nextLoc.hash
+    return isHydrated && loc.hash === nextLoc.hash
   }
   return true
 }

Apply fix via Nx Cloud  Reject fix via Nx Cloud


Or Apply changes locally with:

npx nx-cloud apply-locally wtYK-yid1

Apply fix locally with your editor ↗   View interactive diff ↗



🎓 Learn more about Self-Healing CI on nx.dev

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant