Skip to content

feat(rsbuild): add RSC support#7509

Merged
schiller-manuel merged 6 commits into
mainfrom
rsc-rsbuild
May 30, 2026
Merged

feat(rsbuild): add RSC support#7509
schiller-manuel merged 6 commits into
mainfrom
rsc-rsbuild

Conversation

@schiller-manuel
Copy link
Copy Markdown
Collaborator

@schiller-manuel schiller-manuel commented May 30, 2026

Summary by CodeRabbit

  • New Features

    • Added rsbuild React Server Components (RSC) support with deferred decoding and selective CSS/JS asset preloading.
    • Client/server flow now surfaces per-RSC payload CSS and JS preloads so clients only fetch relevant assets.
    • Build output uses an /assets base for generated client files (affects served script and stylesheet URLs).
  • Chores

    • Bumped @rsbuild/core dependency to ^2.0.8 across packages.
  • Tests

    • End-to-end and unit tests updated to validate RSC asset/link behavior and updated asset URLs.

Review Change Stack

@coderabbitai
Copy link
Copy Markdown
Contributor

coderabbitai Bot commented May 30, 2026

No actionable comments were generated in the recent review. 🎉

ℹ️ Recent review info
⚙️ Run configuration

Configuration used: defaults

Review profile: CHILL

Plan: Pro

Run ID: 886fd429-1571-4a2e-9be2-e083a95460b4

📥 Commits

Reviewing files that changed from the base of the PR and between f40bb9e and 5ae1b30.

📒 Files selected for processing (4)
  • e2e/react-start/basic/tests/client-output.spec.ts
  • e2e/react-start/deferred-hydration/tests/hydration.spec.ts
  • e2e/react-start/rsc/tests/rsc-no-js.spec.ts
  • e2e/solid-start/deferred-hydration/tests/hydration.spec.ts
🚧 Files skipped from review as they are similar to previous changes (1)
  • e2e/react-start/rsc/tests/rsc-no-js.spec.ts

📝 Walkthrough

Walkthrough

This PR adds RSC support for rsbuild: it collects CSS and JS preload dependencies during Flight decode using AsyncLocalStorage, threads those assets through server serialization to the client, enables deferred decoding when assets are pre-provided, updates rsbuild asset paths to use an assets/ base, adjusts package exports and build entries, updates E2E/unit tests, and bumps @rsbuild/core to ^2.0.8 across the repo.

Changes

RSC Support for Rsbuild

Layer / File(s) Summary
Rsbuild Configuration and Asset Path Setup
packages/start-plugin-core/src/rsbuild/planning.ts, packages/start-plugin-core/src/rsbuild/plugin.ts, packages/start-plugin-core/src/rsbuild/virtual-modules.ts
Adds RSBUILD_CLIENT_ASSETS_DIR, updates client asset output paths and dev client entry URL to use /assets/..., and changes virtual RSC SSR decode export stubs to named exports (setOnClientReference, createFromReadableStream).
Stream Decode Options Type and Lazy Element Extraction
packages/react-start-rsc/src/createServerComponentFromStream.ts, packages/react-start-rsc/src/awaitLazyElements.ts
Introduces StreamDecodeOptions carrying cssHrefs and jsPreloads; fixes extraction of CSS hrefs from lazy elements by checking data-rsc-css-href before reading href.
Stream Decode Implementation and Deferred Decode
packages/react-start-rsc/src/createServerComponentFromStream.ts
Refactors setupStreamDecode to accept options, build css/js collectors, decide when to defer decoding, implement lazy startDecode()/cached transformed tree, and forward jsPreloads to RSC proxy.
rsbuild SSR Decode & Client Reference Preloads
packages/react-start-rsc/src/rsbuild/ssr-decode.ts
Implements local SSR Flight decode wrapper, manifest-driven client dependency resolution, Proxy-wrapped serverConsumerModuleMap to emit client reference preloads via setOnClientReference.
Server Serialization and AsyncLocalStorage Collectors
packages/react-start-rsc/src/serialization.server.ts
Adds separate AsyncLocalStorage collectors for CSS hrefs and JS preloads, updates setOnClientReference to write into them, runs decode inside nested runs to capture per-decode asset sets, and conditionally appends assets to serialized payloads.
Client Deserialization and Options Threading
packages/react-start-rsc/src/serialization.client.ts
Extends SerializedRsc with optional cssHrefs/jsPreloads; client fromSerializable builds options from those fields and passes them into stream creators to enable deferred decode when assets are provided.
Package Export Restructuring for Request-Response
packages/start-server-core/package.json, packages/start-server-core/vite.config.ts, packages/react-start/src/server.rsc.ts
Adds ./request-response subpath export, includes ./src/request-response.ts in Vite entries, and re-exports request-response from react-start's server.rsc.
E2E Tests and HTML link parsing updates
e2e/react-start/rsc/package.json, e2e/react-start/rsc/tests/*, e2e/react-start/basic/tests/client-output.spec.ts, e2e/*/deferred-hydration/tests/hydration.spec.ts
Adds rsbuild SSR test mode, introduces shared link-parsing helpers to extract modulepreloads and stylesheets from SSR HTML, updates tests to expect /assets/... script/style URLs, and extends no-JS tests to fetch and validate served stylesheet contents and computed CSS.
RSC Unit Test Coverage for Asset Dependencies
packages/react-start-rsc/tests/ServerComponent.test.tsx
Adds test verifying deferred decode when SSR-provided asset deps are supplied and that components expose SERVER_COMPONENT_CSS_HREFS/SERVER_COMPONENT_JS_PRELOADS.
Rsbuild Core Dependency Updates and Changeset
multiple package.json, .changeset/mean-shirts-jog.md
Bumps @rsbuild/core to ^2.0.8 across benchmarks, e2e suites, examples, and packages; adds changeset message feat(rsbuild): add RSC support.

Estimated code review effort

🎯 4 (Complex) | ⏱️ ~60 minutes

Suggested reviewers

  • Sheraff
  • SeanCassiere
  • beaussan

"I'm a rabbit in the build tree, hopping light and quick,
Collecting CSS and JS preloads, making RSC tick,
Assets bundled under /assets with a joyful cheer,
Deferred decode waits patiently till the client is near,
Hooray — rsbuild and RSC dance without a hitch!" 🐇✨

🚥 Pre-merge checks | ✅ 4 | ❌ 1

❌ Failed checks (1 warning)

Check name Status Explanation Resolution
Docstring Coverage ⚠️ Warning Docstring coverage is 27.78% 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 PR title 'feat(rsbuild): add RSC support' accurately summarizes the main change: adding React Server Components (RSC) support to the rsbuild toolchain across multiple packages.
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.

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

✨ Finishing Touches
📝 Generate docstrings
  • Create stacked PR
  • Commit on current branch
🧪 Generate unit tests (beta)
  • Create PR with unit tests
  • Commit unit tests in branch rsc-rsbuild

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

@nx-cloud
Copy link
Copy Markdown
Contributor

nx-cloud Bot commented May 30, 2026

View your CI Pipeline Execution ↗ for commit 5ae1b30

Command Status Duration Result
nx affected --targets=test:eslint,test:unit,tes... ✅ Succeeded 2m 30s View ↗

☁️ Nx Cloud last updated this comment at 2026-05-30 13:13:50 UTC

@github-actions
Copy link
Copy Markdown
Contributor

github-actions Bot commented May 30, 2026

🚀 Changeset Version Preview

6 package(s) bumped directly, 3 bumped as dependents.

🟩 Patch bumps

Package Version Reason
@tanstack/react-start 1.168.17 → 1.168.18 Changeset
@tanstack/react-start-rsc 0.1.16 → 0.1.17 Changeset
@tanstack/solid-start 1.168.17 → 1.168.18 Changeset
@tanstack/start-plugin-core 1.171.9 → 1.171.10 Changeset
@tanstack/start-server-core 1.169.7 → 1.169.8 Changeset
@tanstack/vue-start 1.168.16 → 1.168.17 Changeset
@tanstack/react-start-server 1.167.12 → 1.167.13 Dependent
@tanstack/solid-start-server 1.167.12 → 1.167.13 Dependent
@tanstack/vue-start-server 1.167.12 → 1.167.13 Dependent

@socket-security
Copy link
Copy Markdown

socket-security Bot commented May 30, 2026

Review the following changes in direct dependencies. Learn more about Socket for GitHub.

Diff Package Supply Chain
Security
Vulnerability Quality Maintenance License
Added@​rsbuild/​core@​2.0.81001008597100

View full report

@github-actions
Copy link
Copy Markdown
Contributor

github-actions Bot commented May 30, 2026

Bundle Size Benchmarks

  • Commit: 18e130238967
  • Measured at: 2026-05-30T13:03:15.111Z
  • Baseline source: history:0ac831bdf798
  • Dashboard: bundle-size history
Scenario Current (gzip) Delta vs baseline Initial gzip Raw Brotli Trend
react-router.minimal 87.25 KiB 0 B (0.00%) 87.12 KiB 273.94 KiB 75.84 KiB █████████▁▁
react-router.full 90.69 KiB 0 B (0.00%) 90.55 KiB 285.30 KiB 78.83 KiB █████████▁▁
solid-router.minimal 35.47 KiB 0 B (0.00%) 35.35 KiB 106.24 KiB 32.00 KiB █████████▁▁
solid-router.full 40.18 KiB 0 B (0.00%) 40.05 KiB 120.43 KiB 36.18 KiB █████████▁▁
vue-router.minimal 52.97 KiB 0 B (0.00%) 52.84 KiB 150.26 KiB 47.66 KiB █████████▁▁
vue-router.full 58.60 KiB 0 B (0.00%) 58.47 KiB 167.99 KiB 52.51 KiB █████████▁▁
react-start.minimal 101.88 KiB 0 B (0.00%) 101.74 KiB 322.26 KiB 88.15 KiB █████████▁▁
react-start.deferred-hydration 102.61 KiB 0 B (0.00%) 101.76 KiB 323.63 KiB 88.82 KiB █████████▁▁
react-start.full 105.26 KiB 0 B (0.00%) 105.12 KiB 332.57 KiB 91.09 KiB █████████▁▁
react-start.rsbuild.minimal 99.58 KiB +24 B (+0.02%) 99.41 KiB 316.74 KiB 85.74 KiB █████████▁▁▄
react-start.rsbuild.full 102.85 KiB +25 B (+0.02%) 102.68 KiB 327.13 KiB 88.45 KiB █████████▁▁▄
solid-start.minimal 49.58 KiB 0 B (0.00%) 49.45 KiB 152.31 KiB 43.78 KiB █████████▁▁
solid-start.deferred-hydration 52.84 KiB 0 B (0.00%) 49.51 KiB 160.35 KiB 46.76 KiB █████████▁▁
solid-start.full 55.37 KiB 0 B (0.00%) 55.24 KiB 169.24 KiB 48.76 KiB █████████▁▁

Current gzip tracks all emitted client JS chunks. Initial gzip tracks only the entry/import graph. Trend sparkline is historical current gzip ending with this PR measurement; lower is better.

@pkg-pr-new
Copy link
Copy Markdown

pkg-pr-new Bot commented May 30, 2026

More templates

@tanstack/arktype-adapter

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

@tanstack/eslint-plugin-router

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

@tanstack/eslint-plugin-start

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

@tanstack/history

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

@tanstack/nitro-v2-vite-plugin

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

@tanstack/react-router

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

@tanstack/react-router-devtools

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

@tanstack/react-router-ssr-query

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

@tanstack/react-start

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

@tanstack/react-start-client

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

@tanstack/react-start-rsc

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

@tanstack/react-start-server

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

@tanstack/router-cli

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

@tanstack/router-core

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

@tanstack/router-devtools

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

@tanstack/router-devtools-core

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

@tanstack/router-generator

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

@tanstack/router-plugin

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

@tanstack/router-ssr-query-core

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

@tanstack/router-utils

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

@tanstack/router-vite-plugin

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

@tanstack/solid-router

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

@tanstack/solid-router-devtools

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

@tanstack/solid-router-ssr-query

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

@tanstack/solid-start

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

@tanstack/solid-start-client

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

@tanstack/solid-start-server

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

@tanstack/start-client-core

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

@tanstack/start-fn-stubs

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

@tanstack/start-plugin-core

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

@tanstack/start-server-core

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

@tanstack/start-static-server-functions

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

@tanstack/start-storage-context

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

@tanstack/valibot-adapter

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

@tanstack/virtual-file-routes

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

@tanstack/vue-router

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

@tanstack/vue-router-devtools

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

@tanstack/vue-router-ssr-query

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

@tanstack/vue-start

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

@tanstack/vue-start-client

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

@tanstack/vue-start-server

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

@tanstack/zod-adapter

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

commit: 5ae1b30

Copy link
Copy Markdown
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: 5

🤖 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/tests/rsc-client-preload.spec.ts`:
- Around line 11-13: The current tag parsing only matches double-quoted
attributes so single-quoted rel/href are ignored; update the regexes used where
tag.match(...) is called (the expressions that populate relValue and href) to
accept either single or double quotes by capturing the quote character and using
a backreference (e.g., /\brel=(['"])(.*?)\1/ and /\bhref=(['"])(.*?)\1/) so
relValue and href get the attribute regardless of quote style; keep the existing
split(/\s+/).includes(rel) logic and the return shape ([href] or []) unchanged.

In `@e2e/react-start/rsc/tests/rsc-css-preload-complex.spec.ts`:
- Around line 105-107: The assertion loop runs immediately and can miss late CSS
requests; before checking that unusedCssHrefs were not requested, wait for
network activity to settle (e.g., call or implement a small helper like
waitForNetworkIdle(page, {idleTime: 200}) or await new Promise(resolve =>
setTimeout(resolve, 500))) so late async requests are observed; then run the
existing loop that checks requestedCssHrefs against unusedCssHrefs (use the
existing requestedCssHrefs and unusedCssHrefs variables).
- Around line 9-11: The current parsing for rel and href in the tag extraction
uses regexes that only match double-quoted attributes (the tag.match calls that
set rel and href), so change those regexes to accept either single or double
quotes (e.g. use a character class like ["'] for the quote delimiter and
backreference or non-capturing grouping) so rel =
tag.match(/\brel=(["'])(.*?)\1/)?[2] and href =
tag.match(/\bhref=(["'])(.*?)\1/)?[2] (or equivalent) — keep the subsequent
rel?.split(/\s+/).includes('stylesheet') && href ? [href] : [] logic unchanged.

In `@packages/react-start-rsc/src/serialization.server.ts`:
- Around line 195-204: The code currently nests cssHrefs under jsPreloads so
cssHrefs are dropped when jsPreloads is empty; change the construction of
serializedAssetDeps (around component[SERVER_COMPONENT_JS_PRELOADS],
component[SERVER_COMPONENT_CSS_HREFS], and the serializedAssetDeps variable) so
that cssHrefs is always serialized when present (e.g., include cssHrefs:
Array.from(cssHrefs) if cssHrefs?.size regardless of jsPreloads) and only
include jsPreloads: Array.from(jsPreloads) when jsPreloads?.size is truthy.

In `@packages/react-start/src/server.rsc.ts`:
- Line 1: The change replaced a broad re-export with only request/response
symbols, which narrows the RSC entrypoint and may break consumers; restore the
original public surface by either re-exporting the root module again (so
createStartHandler, HEADERS, RequestHandler, SessionConfig, etc. remain
available) or explicitly add those missing exports alongside the
request/response re-exports (referencing createStartHandler, HEADERS,
RequestHandler, SessionConfig and any other root/virtual-module symbols that
existed previously) to ensure the RSC entrypoint surface is not unintentionally
narrowed.
🪄 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

Run ID: 3fa62cad-893e-46d3-87e2-c49c7e2808d3

📥 Commits

Reviewing files that changed from the base of the PR and between 0ac831b and 280bc0b.

⛔ Files ignored due to path filters (1)
  • pnpm-lock.yaml is excluded by !**/pnpm-lock.yaml
📒 Files selected for processing (37)
  • .changeset/mean-shirts-jog.md
  • benchmarks/bundle-size/package.json
  • e2e/react-router/rspack-basic-file-based/package.json
  • e2e/react-router/rspack-basic-virtual-named-export-config-file-based/package.json
  • e2e/react-start/basic/package.json
  • e2e/react-start/css-inline/package.json
  • e2e/react-start/custom-server-rsbuild/package.json
  • e2e/react-start/hmr/package.json
  • e2e/react-start/import-protection/package.json
  • e2e/react-start/rsc/package.json
  • e2e/react-start/rsc/tests/rsc-client-preload.spec.ts
  • e2e/react-start/rsc/tests/rsc-css-preload-complex.spec.ts
  • e2e/react-start/server-functions/package.json
  • e2e/solid-router/rspack-basic-file-based/package.json
  • e2e/solid-router/rspack-basic-virtual-named-export-config-file-based/package.json
  • e2e/solid-start/basic/package.json
  • e2e/vue-router/rspack-basic-file-based/package.json
  • e2e/vue-router/rspack-basic-virtual-named-export-config-file-based/package.json
  • e2e/vue-start/basic/package.json
  • examples/react/quickstart-rspack-file-based/package.json
  • examples/solid/quickstart-rspack-file-based/package.json
  • packages/react-start-rsc/src/awaitLazyElements.ts
  • packages/react-start-rsc/src/createServerComponentFromStream.ts
  • packages/react-start-rsc/src/rsbuild/ssr-decode.ts
  • packages/react-start-rsc/src/serialization.client.ts
  • packages/react-start-rsc/src/serialization.server.ts
  • packages/react-start-rsc/tests/ServerComponent.test.tsx
  • packages/react-start/package.json
  • packages/react-start/src/server.rsc.ts
  • packages/solid-start/package.json
  • packages/start-plugin-core/package.json
  • packages/start-plugin-core/src/rsbuild/planning.ts
  • packages/start-plugin-core/src/rsbuild/plugin.ts
  • packages/start-plugin-core/src/rsbuild/virtual-modules.ts
  • packages/start-server-core/package.json
  • packages/start-server-core/vite.config.ts
  • packages/vue-start/package.json

Comment on lines +11 to +13
const relValue = tag.match(/\brel="([^"]*)"/)?.[1]
const href = tag.match(/\bhref="([^"]*)"/)?.[1]
return relValue?.split(/\s+/).includes(rel) && href ? [href] : []
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.

⚠️ Potential issue | 🟡 Minor | ⚡ Quick win

Harden rel/href parsing to handle single-quoted attributes.

Line 11 and Line 12 only match double-quoted attributes, so the helper can silently miss valid <link> tags when HTML serialization differs and make this test flaky.

Suggested fix
-      const relValue = tag.match(/\brel="([^"]*)"/)?.[1]
-      const href = tag.match(/\bhref="([^"]*)"/)?.[1]
+      const relMatch = tag.match(/\brel=(?:"([^"]*)"|'([^']*)')/i)
+      const hrefMatch = tag.match(/\bhref=(?:"([^"]*)"|'([^']*)')/i)
+      const relValue = relMatch?.[1] ?? relMatch?.[2]
+      const href = hrefMatch?.[1] ?? hrefMatch?.[2]
📝 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
const relValue = tag.match(/\brel="([^"]*)"/)?.[1]
const href = tag.match(/\bhref="([^"]*)"/)?.[1]
return relValue?.split(/\s+/).includes(rel) && href ? [href] : []
const relMatch = tag.match(/\brel=(?:"([^"]*)"|'([^']*)')/i)
const hrefMatch = tag.match(/\bhref=(?:"([^"]*)"|'([^']*)')/i)
const relValue = relMatch?.[1] ?? relMatch?.[2]
const href = hrefMatch?.[1] ?? hrefMatch?.[2]
return relValue?.split(/\s+/).includes(rel) && href ? [href] : []
🤖 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/tests/rsc-client-preload.spec.ts` around lines 11 - 13,
The current tag parsing only matches double-quoted attributes so single-quoted
rel/href are ignored; update the regexes used where tag.match(...) is called
(the expressions that populate relValue and href) to accept either single or
double quotes by capturing the quote character and using a backreference (e.g.,
/\brel=(['"])(.*?)\1/ and /\bhref=(['"])(.*?)\1/) so relValue and href get the
attribute regardless of quote style; keep the existing
split(/\s+/).includes(rel) logic and the return shape ([href] or []) unchanged.

Comment thread e2e/react-start/rsc/tests/rsc-css-preload-complex.spec.ts Outdated
Comment on lines +105 to +107
for (const href of unusedCssHrefs) {
expect(requestedCssHrefs).not.toContain(href)
}
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.

⚠️ Potential issue | 🟡 Minor | ⚡ Quick win

Wait for network to settle before asserting “unused CSS was not requested”.

As written, Line 105 checks immediately; late async requests after initial navigation can slip past and produce false positives.

Suggested fix
     await expect(note).toHaveCSS('background-color', 'rgb(219, 234, 254)')
     await expect(note).toHaveCSS('border-color', 'rgb(59, 130, 246)')
+    await page.waitForLoadState('networkidle')
     for (const href of unusedCssHrefs) {
       expect(requestedCssHrefs).not.toContain(href)
     }
📝 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
for (const href of unusedCssHrefs) {
expect(requestedCssHrefs).not.toContain(href)
}
await page.waitForLoadState('networkidle')
for (const href of unusedCssHrefs) {
expect(requestedCssHrefs).not.toContain(href)
}
🤖 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/tests/rsc-css-preload-complex.spec.ts` around lines 105 -
107, The assertion loop runs immediately and can miss late CSS requests; before
checking that unusedCssHrefs were not requested, wait for network activity to
settle (e.g., call or implement a small helper like waitForNetworkIdle(page,
{idleTime: 200}) or await new Promise(resolve => setTimeout(resolve, 500))) so
late async requests are observed; then run the existing loop that checks
requestedCssHrefs against unusedCssHrefs (use the existing requestedCssHrefs and
unusedCssHrefs variables).

Comment on lines +195 to +204
const jsPreloads = component[SERVER_COMPONENT_JS_PRELOADS]
const cssHrefs = component[SERVER_COMPONENT_CSS_HREFS]
// `cssHrefs` can also come from loadCss() marker links. Client-reference
// collection fills `jsPreloads`, so that gates per-stream serialization.
const serializedAssetDeps = jsPreloads?.size
? {
...(cssHrefs?.size ? { cssHrefs: Array.from(cssHrefs) } : {}),
jsPreloads: Array.from(jsPreloads),
}
: {}
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.

⚠️ Potential issue | 🟠 Major | ⚡ Quick win

Serialize cssHrefs independently of jsPreloads.

Line 199 currently drops cssHrefs whenever no JS preload was collected, but CSS marker links can be discovered without any client-reference JS deps. In that case the client never receives those stylesheet hrefs for deferred decode/preload.

Suggested fix
-    const serializedAssetDeps = jsPreloads?.size
-      ? {
-          ...(cssHrefs?.size ? { cssHrefs: Array.from(cssHrefs) } : {}),
-          jsPreloads: Array.from(jsPreloads),
-        }
-      : {}
+    const serializedAssetDeps =
+      cssHrefs?.size || jsPreloads?.size
+        ? {
+            ...(cssHrefs?.size ? { cssHrefs: Array.from(cssHrefs) } : {}),
+            ...(jsPreloads?.size
+              ? { jsPreloads: Array.from(jsPreloads) }
+              : {}),
+          }
+        : {}
📝 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
const jsPreloads = component[SERVER_COMPONENT_JS_PRELOADS]
const cssHrefs = component[SERVER_COMPONENT_CSS_HREFS]
// `cssHrefs` can also come from loadCss() marker links. Client-reference
// collection fills `jsPreloads`, so that gates per-stream serialization.
const serializedAssetDeps = jsPreloads?.size
? {
...(cssHrefs?.size ? { cssHrefs: Array.from(cssHrefs) } : {}),
jsPreloads: Array.from(jsPreloads),
}
: {}
const jsPreloads = component[SERVER_COMPONENT_JS_PRELOADS]
const cssHrefs = component[SERVER_COMPONENT_CSS_HREFS]
// `cssHrefs` can also come from loadCss() marker links. Client-reference
// collection fills `jsPreloads`, so that gates per-stream serialization.
const serializedAssetDeps =
cssHrefs?.size || jsPreloads?.size
? {
...(cssHrefs?.size ? { cssHrefs: Array.from(cssHrefs) } : {}),
...(jsPreloads?.size
? { jsPreloads: Array.from(jsPreloads) }
: {}),
}
: {}
🤖 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 `@packages/react-start-rsc/src/serialization.server.ts` around lines 195 - 204,
The code currently nests cssHrefs under jsPreloads so cssHrefs are dropped when
jsPreloads is empty; change the construction of serializedAssetDeps (around
component[SERVER_COMPONENT_JS_PRELOADS], component[SERVER_COMPONENT_CSS_HREFS],
and the serializedAssetDeps variable) so that cssHrefs is always serialized when
present (e.g., include cssHrefs: Array.from(cssHrefs) if cssHrefs?.size
regardless of jsPreloads) and only include jsPreloads: Array.from(jsPreloads)
when jsPreloads?.size is truthy.

@@ -1 +1 @@
export * from '@tanstack/start-server-core'
export * from '@tanstack/start-server-core/request-response'
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.

⚠️ Potential issue | 🟠 Major | ⚡ Quick win

🧩 Analysis chain

🏁 Script executed:

#!/bin/bash
# Compare symbols exposed by the root index vs request-response subpath.
echo '=== root index exports ==='
fd -t f 'index.tsx' packages/start-server-core/src --exec rg -n '^export' {}
echo '=== request-response exports ==='
fd -t f 'request-response.ts' packages/start-server-core/src --exec rg -n '^export' {}
echo '=== usages of server.rsc entry ==='
rg -nP "react-start/.*server\.rsc|`@tanstack/react-start/server`" -g '!**/dist/**' -C2

Repository: TanStack/router

Length of output: 50373


Re-exporting from @tanstack/start-server-core/request-response likely narrows the RSC entrypoint surface (breaking only if consumers relied on root-only symbols).

The change removes everything that @tanstack/start-server-core exports outside ./request-response (e.g. createStartHandler, HEADERS, RequestHandler/SessionConfig, and other root/virtual-module exports), since request-response.ts only exports the request/response helpers and related types. If server.rsc.ts is intended to be the RSC-only request/response API surface, this is a deliberate narrowing; otherwise it would be a breaking change.

🤖 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 `@packages/react-start/src/server.rsc.ts` at line 1, The change replaced a
broad re-export with only request/response symbols, which narrows the RSC
entrypoint and may break consumers; restore the original public surface by
either re-exporting the root module again (so createStartHandler, HEADERS,
RequestHandler, SessionConfig, etc. remain available) or explicitly add those
missing exports alongside the request/response re-exports (referencing
createStartHandler, HEADERS, RequestHandler, SessionConfig and any other
root/virtual-module symbols that existed previously) to ensure the RSC
entrypoint surface is not unintentionally narrowed.

@codspeed-hq
Copy link
Copy Markdown

codspeed-hq Bot commented May 30, 2026

Merging this PR will not alter performance

✅ 5 untouched benchmarks
⏩ 1 skipped benchmark1


Comparing rsc-rsbuild (5ae1b30) with main (0ac831b)

Open in CodSpeed

Footnotes

  1. 1 benchmark was skipped, so the baseline result was used instead. If it was deleted from the codebase, click here and archive it to remove it from the performance reports.

Copy link
Copy Markdown
Contributor

@nx-cloud nx-cloud Bot left a comment

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 added await page.waitForURL('/posts') in the "Should change title on client side navigation" test to fix the vite-prerender failure. In prerender mode the router reconciles against prerendered HTML during client-side navigation, introducing a timing gap between the URL change and the document-title update that caused the toHaveTitle assertion to time out. This one-line guard brings the test in line with the waitForURL pattern already used by other navigation tests in the same file.

Tip

We verified this fix by re-running tanstack-react-start-e2e-basic:test:e2e--vite-prerender.

diff --git a/e2e/react-start/basic/tests/navigation.spec.ts b/e2e/react-start/basic/tests/navigation.spec.ts
index ce69a9d9..78488ce1 100644
--- a/e2e/react-start/basic/tests/navigation.spec.ts
+++ b/e2e/react-start/basic/tests/navigation.spec.ts
@@ -85,6 +85,7 @@ test('Should change title on client side navigation', async ({ page }) => {
   await page.waitForURL('/')
 
   await page.getByRole('link', { name: 'Posts' }).click()
+  await page.waitForURL('/posts')
 
   await expect(page).toHaveTitle('Posts page')
 })

Apply fix via Nx Cloud  Reject fix via Nx Cloud


Or Apply changes locally with:

npx nx-cloud apply-locally Je71-qazb

Apply fix locally with your editor ↗   View interactive diff ↗



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

@schiller-manuel schiller-manuel merged commit 9cb7a00 into main May 30, 2026
21 of 22 checks passed
@schiller-manuel schiller-manuel deleted the rsc-rsbuild branch May 30, 2026 13:16
@github-actions github-actions Bot mentioned this pull request May 30, 2026
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant