Skip to content

test(rsc): add dev-mode HMR e2e for RSC routes with a co-located server fn - #7901

Open
sreetamdas wants to merge 3 commits into
TanStack:mainfrom
sreetamdas:test/rsc-hmr-e2e
Open

test(rsc): add dev-mode HMR e2e for RSC routes with a co-located server fn#7901
sreetamdas wants to merge 3 commits into
TanStack:mainfrom
sreetamdas:test/rsc-hmr-e2e

Conversation

@sreetamdas

@sreetamdas sreetamdas commented Jul 25, 2026

Copy link
Copy Markdown
Contributor

Description

Adds e2e/react-start/rsc-hmr: a dev-mode HMR e2e project for TanStack Start RSC routes, covering route files that co-locate a createServerFn with the route component.

Replaces #7621, which was a deliberate CI canary for a bug that has since been fixed upstream (vitejs/vite-plugin-react#1248, released in @vitejs/plugin-rsc@0.5.30). This is the regression suite @schiller-manuel asked for on that PR instead.

Built per his review there: a separate project rather than bolting onto e2e/react-start/rsc, following e2e/react-start/hmr's CI hardening, multiple routes including one with a CSS module and one with no CSS, and both the vite and rsbuild adapters.

Why a Start-level test, given the fix was upstream

Upstream's own regression test (plugin-rsc's examples/client-first) exercises the plugin in isolation. It does not go through Start's ?tss-serverfn-split transform, which is what puts a co-located route file into the rsc module graph in the first place, and it cannot cover the rsbuild adapter at all.

Worth being precise about the two lanes: @vitejs/plugin-rsc is Vite-only, so the vite lane is what guards against this specific bug regressing. The rsbuild lane goes through Rspack / react-server-dom-rspack and is adapter-parity coverage — valuable, but a different code path.

Layout

rsc-hmr = hmr's harness + rsc's configs:

  • playwright.config.ts, tests/setup/global.{setup,teardown}.ts — from e2e/react-start/hmr, so it inherits waitForServer + preOptimizeDevServer (cold compile and Vite's dep-optimize reload storm happen outside any test's timeout) and createHmrFileEditor with normalizeSource, so a run killed mid-edit self-heals instead of cascading.
  • vite.config.ts / rsbuild.config.ts — from e2e/react-start/rsc, both with rsc: { enabled: true }.
  • playwrightModes: vite-ssr and rsbuild-ssr. workers: 1 and no shards, since the tests mutate fixture files on disk.
  • Declares mode: 'ssr' and then runs a dev server, matching what hmr does — the nx playwright plugin's mode allowlist is ['ssr','spa','prerender','preview'] with no dev. Happy to wire this differently if you'd prefer.

Fixtures

Route What it covers
/co-located createServerFn + component in one file, no CSS — the original bug
/co-located-css-module same, plus a CSS module import
/separate-file control: server fn extracted to its own module (the documented workaround)

Each test seeds client state (a counter plus a window sentinel) before editing, then asserts the new text renders and the state survived — so a full reload that happens to render the right text fails rather than passes.

The CSS cases are the ones I'd most want kept. The upstream guard decides whether to suppress client HMR by classifying importers as CSS vs non-CSS, so the interaction most at risk is a route with both kinds — in both directions: the component must still Fast Refresh, and a CSS-only edit must still hot-swap without a full reload. Both are asserted.

Verification

Run locally against both adapters:

  • E2E_TOOLCHAIN=vite — 4/4 pass
  • E2E_TOOLCHAIN=rsbuild — 4/4 pass
  • tsc --noEmit — clean

And, more usefully, the suite was checked against the broken plugin version. Pinning this project to @vitejs/plugin-rsc@0.5.27 and re-running gives:

2 failed
  › Fast Refreshes a co-located route component and preserves state
  › Fast Refreshes when the route also imports a CSS module
2 passed

with the failure mode from the original report — Received: "co-located-baseline", and only (rsc) hmr update /src/routes/co-located.tsx?tss-serverfn-split in the terminal with no client update. The CSS-only edit and the separate-file control stay green, since neither was ever affected. So the suite fails specifically on this bug rather than merely passing on a fixed version.

Note 0.5.27 is inside the >=0.5.20 peer range that @tanstack/react-start-rsc currently declares, which is what #7900 addresses.

Notes

  • No changeset — rsc-hmr is private: true and unpublished.
  • No changes needed to nx.json, pnpm-workspace.yaml, or any workflow: e2e/react-start/* is globbed as a workspace, and scripts/nx/playwright-plugin.ts synthesises targets from the nx.metadata.playwrightModes block in the project's own package.json.
  • The extracted module in the control fixture is -separateFile.tsx, deliberately without a .server. suffix — import protection treats those as server-only and swaps in a client mock, which breaks the route instead of exercising the workaround. Flagging it because TanStack Start (RSC): route component does not HMR when its file has a co-located createServerFn #7618's workaround wording suggests *.server.ts, which is misleading for a module a route imports directly.

Summary by CodeRabbit

  • New Features
    • Added a React Server Components HMR end-to-end test app with multiple routes demonstrating server-rendered content, client state preservation, and CSS-module updates.
    • Enabled running the suite against both Vite and Rsbuild toolchains.
    • Added Playwright coverage that verifies UI updates happen via HMR without full-page reloads.
  • Chores
    • Added new E2E suite configuration (Vite/Rsbuild), strict TypeScript settings, global test setup/teardown, and updated gitignore rules for development artifacts.

@coderabbitai

coderabbitai Bot commented Jul 25, 2026

Copy link
Copy Markdown
Contributor

Review Change Stack

No actionable comments were generated in the recent review. 🎉

ℹ️ Recent review info
⚙️ Run configuration

Configuration used: defaults

Review profile: CHILL

Plan: Pro Plus

Run ID: 88081c2b-53c3-4bb6-86ed-c07b3396f48a

📥 Commits

Reviewing files that changed from the base of the PR and between 5fab4fa and ad1d72f.

⛔ Files ignored due to path filters (1)
  • pnpm-lock.yaml is excluded by !**/pnpm-lock.yaml
📒 Files selected for processing (18)
  • e2e/react-start/rsc-hmr/.gitignore
  • e2e/react-start/rsc-hmr/package.json
  • e2e/react-start/rsc-hmr/playwright.config.ts
  • e2e/react-start/rsc-hmr/rsbuild.config.ts
  • e2e/react-start/rsc-hmr/src/routeTree.gen.ts
  • e2e/react-start/rsc-hmr/src/router.tsx
  • e2e/react-start/rsc-hmr/src/routes/-separateFile.tsx
  • e2e/react-start/rsc-hmr/src/routes/__root.tsx
  • e2e/react-start/rsc-hmr/src/routes/co-located-css-module.tsx
  • e2e/react-start/rsc-hmr/src/routes/co-located.tsx
  • e2e/react-start/rsc-hmr/src/routes/coLocatedCssModule.module.css
  • e2e/react-start/rsc-hmr/src/routes/index.tsx
  • e2e/react-start/rsc-hmr/src/routes/separate-file.tsx
  • e2e/react-start/rsc-hmr/tests/rsc-hmr.spec.ts
  • e2e/react-start/rsc-hmr/tests/setup/global.setup.ts
  • e2e/react-start/rsc-hmr/tests/setup/global.teardown.ts
  • e2e/react-start/rsc-hmr/tsconfig.json
  • e2e/react-start/rsc-hmr/vite.config.ts
🚧 Files skipped from review as they are similar to previous changes (18)
  • e2e/react-start/rsc-hmr/rsbuild.config.ts
  • e2e/react-start/rsc-hmr/src/routes/coLocatedCssModule.module.css
  • e2e/react-start/rsc-hmr/tsconfig.json
  • e2e/react-start/rsc-hmr/tests/setup/global.setup.ts
  • e2e/react-start/rsc-hmr/src/router.tsx
  • e2e/react-start/rsc-hmr/.gitignore
  • e2e/react-start/rsc-hmr/src/routes/-separateFile.tsx
  • e2e/react-start/rsc-hmr/src/routes/index.tsx
  • e2e/react-start/rsc-hmr/src/routes/__root.tsx
  • e2e/react-start/rsc-hmr/src/routes/separate-file.tsx
  • e2e/react-start/rsc-hmr/src/routes/co-located-css-module.tsx
  • e2e/react-start/rsc-hmr/vite.config.ts
  • e2e/react-start/rsc-hmr/tests/rsc-hmr.spec.ts
  • e2e/react-start/rsc-hmr/playwright.config.ts
  • e2e/react-start/rsc-hmr/src/routes/co-located.tsx
  • e2e/react-start/rsc-hmr/src/routeTree.gen.ts
  • e2e/react-start/rsc-hmr/package.json
  • e2e/react-start/rsc-hmr/tests/setup/global.teardown.ts

📝 Walkthrough

Walkthrough

Adds a React Start RSC HMR E2E fixture supporting Vite and Rsbuild, with routed server-component examples, client state, CSS-module updates, isolated test-server lifecycle management, and Playwright tests covering state-preserving hot updates.

Changes

RSC HMR fixture

Layer / File(s) Summary
Tooling and test runtime
e2e/react-start/rsc-hmr/package.json, e2e/react-start/rsc-hmr/*config.ts, e2e/react-start/rsc-hmr/tests/setup/*, e2e/react-start/rsc-hmr/tsconfig.json, e2e/react-start/rsc-hmr/.gitignore
Defines Vite/Rsbuild scripts and dependencies, enables RSC in both toolchains, configures Playwright server orchestration, manages test ports, and excludes generated artifacts.
Router and application shell
e2e/react-start/rsc-hmr/src/routeTree.gen.ts, e2e/react-start/rsc-hmr/src/router.tsx, e2e/react-start/rsc-hmr/src/routes/__root.tsx, e2e/react-start/rsc-hmr/src/routes/index.tsx
Creates the typed route tree, router factory, document shell, navigation links, hydration marker, and index route.
RSC route fixtures
e2e/react-start/rsc-hmr/src/routes/co-located.tsx, e2e/react-start/rsc-hmr/src/routes/co-located-css-module.tsx, e2e/react-start/rsc-hmr/src/routes/separate-file.tsx, e2e/react-start/rsc-hmr/src/routes/-separateFile.tsx, e2e/react-start/rsc-hmr/src/routes/coLocatedCssModule.module.css
Adds co-located and separate server functions, route loaders, client counters, server-rendered markers, and CSS-module output.
HMR E2E coverage
e2e/react-start/rsc-hmr/tests/rsc-hmr.spec.ts
Edits route and CSS fixtures during Playwright tests and verifies updated output, preserved client state, and absence of full-page reloads.

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

Sequence Diagram(s)

sequenceDiagram
  participant Playwright
  participant HMRFileEditor
  participant DevServer
  participant Browser
  Playwright->>Browser: Navigate and wait for hydration
  Playwright->>Browser: Seed counter and reload sentinel
  Playwright->>HMRFileEditor: Edit route or CSS fixture
  HMRFileEditor->>DevServer: Write changed source
  DevServer->>Browser: Deliver HMR update
  Browser-->>Playwright: Report updated content and preserved state
Loading

Possibly related PRs

  • TanStack/router#7900 — Adds the RSC HMR E2E suite that exercises the co-located createServerFn route-component behavior covered here.

Suggested labels: package: react-start-rsc, package: react-start, package: react-router

🚥 Pre-merge checks | ✅ 4 | ❌ 1

❌ Failed checks (1 warning)

Check name Status Explanation Resolution
Docstring Coverage ⚠️ Warning Docstring coverage is 8.33% which is insufficient. The required threshold is 80.00%. Write docstrings for the functions missing them to satisfy the coverage threshold.
✅ Passed checks (4 passed)
Check name Status Explanation
Description Check ✅ Passed Check skipped - CodeRabbit’s high-level summary is enabled.
Title check ✅ Passed The title accurately summarizes the main change: adding dev-mode HMR E2E tests for RSC routes with a co-located server function.
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.
✨ Finishing Touches
🧪 Generate unit tests (beta)
  • Create PR with unit tests

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.

@nx-cloud

nx-cloud Bot commented Jul 25, 2026

Copy link
Copy Markdown
Contributor

View your CI Pipeline Execution ↗ for commit 5fab4fa

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

☁️ Nx Cloud last updated this comment at 2026-07-25 21:34:36 UTC

@coderabbitai coderabbitai 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.

Actionable comments posted: 1

🧹 Nitpick comments (1)
e2e/react-start/rsc-hmr/package.json (1)

17-18: 📐 Maintainability & Code Quality | 🔵 Trivial | ⚡ Quick win

Use workspace:* for internal dependencies.

The internal packages currently use workspace:^, but the repository guideline requires workspace:*. Update @tanstack/react-router, @tanstack/react-start, and @tanstack/router-e2e-utils.

Proposed change
-    "`@tanstack/react-router`": "workspace:^",
-    "`@tanstack/react-start`": "workspace:^",
+    "`@tanstack/react-router`": "workspace:*",
+    "`@tanstack/react-start`": "workspace:*",
...
-    "`@tanstack/router-e2e-utils`": "workspace:^",
+    "`@tanstack/router-e2e-utils`": "workspace:*",

Also applies to: 26-26

🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@e2e/react-start/rsc-hmr/package.json` around lines 17 - 18, Update the
internal dependency entries for `@tanstack/react-router`, `@tanstack/react-start`,
and `@tanstack/router-e2e-utils` in package.json to use the workspace:* protocol
instead of workspace:^, preserving all other dependency declarations.

Source: Coding guidelines

🤖 Prompt for all review comments with AI agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

Inline comments:
In `@e2e/react-start/rsc-hmr/tests/rsc-hmr.spec.ts`:
- Around line 69-76: Remove the any casts used for __hmrReloadSentinel in the
setup and expectNoReloadHappened flows. Extend the Window type once with the
sentinel property, then access window.__hmrReloadSentinel directly while
preserving the existing sentinel behavior.

---

Nitpick comments:
In `@e2e/react-start/rsc-hmr/package.json`:
- Around line 17-18: Update the internal dependency entries for
`@tanstack/react-router`, `@tanstack/react-start`, and `@tanstack/router-e2e-utils` in
package.json to use the workspace:* protocol instead of workspace:^, preserving
all other dependency declarations.
🪄 Autofix (Beta)

Fix all unresolved CodeRabbit comments on this PR:

  • Push a commit to this branch (recommended)
  • Create a new PR with the fixes

ℹ️ Review info
⚙️ Run configuration

Configuration used: defaults

Review profile: CHILL

Plan: Pro Plus

Run ID: 077b829f-ab94-4e34-bd46-33b92439816b

📥 Commits

Reviewing files that changed from the base of the PR and between 88f366a and 5fab4fa.

⛔ Files ignored due to path filters (1)
  • pnpm-lock.yaml is excluded by !**/pnpm-lock.yaml
📒 Files selected for processing (18)
  • e2e/react-start/rsc-hmr/.gitignore
  • e2e/react-start/rsc-hmr/package.json
  • e2e/react-start/rsc-hmr/playwright.config.ts
  • e2e/react-start/rsc-hmr/rsbuild.config.ts
  • e2e/react-start/rsc-hmr/src/routeTree.gen.ts
  • e2e/react-start/rsc-hmr/src/router.tsx
  • e2e/react-start/rsc-hmr/src/routes/-separateFile.tsx
  • e2e/react-start/rsc-hmr/src/routes/__root.tsx
  • e2e/react-start/rsc-hmr/src/routes/co-located-css-module.tsx
  • e2e/react-start/rsc-hmr/src/routes/co-located.tsx
  • e2e/react-start/rsc-hmr/src/routes/coLocatedCssModule.module.css
  • e2e/react-start/rsc-hmr/src/routes/index.tsx
  • e2e/react-start/rsc-hmr/src/routes/separate-file.tsx
  • e2e/react-start/rsc-hmr/tests/rsc-hmr.spec.ts
  • e2e/react-start/rsc-hmr/tests/setup/global.setup.ts
  • e2e/react-start/rsc-hmr/tests/setup/global.teardown.ts
  • e2e/react-start/rsc-hmr/tsconfig.json
  • e2e/react-start/rsc-hmr/vite.config.ts

Comment on lines +69 to +76
await page.evaluate(() => {
;(window as any).__hmrReloadSentinel = 'alive'
})
}

async function expectNoReloadHappened(page: Page) {
await expect
.poll(() => page.evaluate(() => (window as any).__hmrReloadSentinel), {

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.

📐 Maintainability & Code Quality | 🟡 Minor | ⚡ Quick win

Remove the any escape from the reload sentinel.

Declare Window.__hmrReloadSentinel once and access it directly so this strict TypeScript fixture remains type-safe.

Proposed fix
+declare global {
+  interface Window {
+    __hmrReloadSentinel?: string
+  }
+}
+
 async function seedClientState(page: Page, testIdPrefix: string) {
   // ...
   await page.evaluate(() => {
-    ;(window as any).__hmrReloadSentinel = 'alive'
+    window.__hmrReloadSentinel = 'alive'
   })
 }
 
 async function expectNoReloadHappened(page: Page) {
   await expect
-    .poll(() => page.evaluate(() => (window as any).__hmrReloadSentinel), {
+    .poll(() => page.evaluate(() => window.__hmrReloadSentinel), {
📝 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
await page.evaluate(() => {
;(window as any).__hmrReloadSentinel = 'alive'
})
}
async function expectNoReloadHappened(page: Page) {
await expect
.poll(() => page.evaluate(() => (window as any).__hmrReloadSentinel), {
declare global {
interface Window {
__hmrReloadSentinel?: string
}
}
async function seedClientState(page: Page, testIdPrefix: string) {
// ...
await page.evaluate(() => {
window.__hmrReloadSentinel = 'alive'
})
}
async function expectNoReloadHappened(page: Page) {
await expect
.poll(() => page.evaluate(() => window.__hmrReloadSentinel), {
🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@e2e/react-start/rsc-hmr/tests/rsc-hmr.spec.ts` around lines 69 - 76, Remove
the any casts used for __hmrReloadSentinel in the setup and
expectNoReloadHappened flows. Extend the Window type once with the sentinel
property, then access window.__hmrReloadSentinel directly while preserving the
existing sentinel behavior.

Source: Coding guidelines

@pkg-pr-new

pkg-pr-new Bot commented Jul 25, 2026

Copy link
Copy Markdown
More templates

@tanstack/arktype-adapter

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

@tanstack/eslint-plugin-router

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

@tanstack/eslint-plugin-start

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

@tanstack/history

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

@tanstack/nitro-v2-vite-plugin

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

@tanstack/react-router

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

@tanstack/react-router-devtools

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

@tanstack/react-router-ssr-query

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

@tanstack/react-start

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

@tanstack/react-start-client

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

@tanstack/react-start-rsc

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

@tanstack/react-start-server

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

@tanstack/router-cli

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

@tanstack/router-core

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

@tanstack/router-devtools

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

@tanstack/router-devtools-core

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

@tanstack/router-generator

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

@tanstack/router-plugin

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

@tanstack/router-ssr-query-core

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

@tanstack/router-utils

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

@tanstack/router-vite-plugin

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

@tanstack/solid-router

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

@tanstack/solid-router-devtools

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

@tanstack/solid-router-ssr-query

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

@tanstack/solid-start

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

@tanstack/solid-start-client

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

@tanstack/solid-start-server

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

@tanstack/start-client-core

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

@tanstack/start-fn-stubs

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

@tanstack/start-plugin-core

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

@tanstack/start-server-core

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

@tanstack/start-static-server-functions

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

@tanstack/start-storage-context

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

@tanstack/valibot-adapter

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

@tanstack/virtual-file-routes

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

@tanstack/vue-router

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

@tanstack/vue-router-devtools

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

@tanstack/vue-router-ssr-query

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

@tanstack/vue-start

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

@tanstack/vue-start-client

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

@tanstack/vue-start-server

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

@tanstack/zod-adapter

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

commit: 5fab4fa

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