Skip to content

feat(start-plugin-core): support Rsbuild preview SSR middleware#7372

Open
elecmonkey wants to merge 2 commits intoTanStack:mainfrom
elecmonkey:main
Open

feat(start-plugin-core): support Rsbuild preview SSR middleware#7372
elecmonkey wants to merge 2 commits intoTanStack:mainfrom
elecmonkey:main

Conversation

@elecmonkey
Copy link
Copy Markdown

@elecmonkey elecmonkey commented May 9, 2026

Summary

Support Rsbuild preview SSR middleware.

Previously, because the output generated by rsbuild build included server-side code for SSR, rsbuild preview treated it as static assets, which caused the preview to malfunction. The middleware code shared between dev mode and preview mode has now been extracted.

Details

This updates the Rsbuild integration so Start’s server middleware is no longer dev-only.

Because the option now controls both dev and preview middleware installation, the Rsbuild option has been renamed:

rsbuild.installDevServerMiddleware

to:

rsbuild.installServerMiddleware

Summary by CodeRabbit

  • New Features

    • Added preview mode support for RSBuild with environment-based toolchain selection for end-to-end testing.
  • Improvements

    • Server-side rendering middleware now functions in both development and preview modes.
    • Updated server middleware configuration option naming for improved clarity.

Review Change Stack

@coderabbitai
Copy link
Copy Markdown
Contributor

coderabbitai Bot commented May 9, 2026

📝 Walkthrough

Walkthrough

This PR extends Rsbuild integration with preview mode support by refactoring dev-specific SSR middleware into a unified server-middleware layer, updating configuration schemas, adding E2E tests for preview mode, and optimizing React dependency resolution.

Changes

Rsbuild Preview Mode Support

Layer / File(s) Summary
Configuration Schema
packages/start-plugin-core/src/rsbuild/schema.ts
Schema updated to use installServerMiddleware option (replacing installDevServerMiddleware) to reflect support for both dev and preview modes.
E2E Test Configuration
e2e/react-start/basic/package.json, e2e/react-start/basic/playwright.config.ts, e2e/react-start/custom-server-rsbuild/rsbuild.config.ts
Added preview:rsbuild script, extended Playwright modes with rsbuild preview configuration, introduced E2E_TOOLCHAIN environment variable for conditional preview command selection (rsbuild vs vite), and updated plugin configuration option names.
Unified SSR Middleware
packages/start-plugin-core/src/rsbuild/server-middleware.ts
New implementation of createServerSetup factory with type definitions, fetch handler resolution, URL utilities for preview routing, mode-gated bundle loading (dev from in-memory environment, preview from build output), and dual middleware registration for server-function interception and SSR fallback with error handling.
Plugin Wiring
packages/start-plugin-core/src/rsbuild/plugin.ts
Updated to import createServerSetup from new server-middleware module, introduced isDev and isPreview flags to gate middleware installation for both modes, added conditional server.printUrls normalization, and wired server.setup with updated configuration option names.
Legacy Code Removal
packages/start-plugin-core/src/rsbuild/dev-server.ts
Deleted file containing dev-only createServerSetup implementation, including handleSSR middleware logic, type definitions, and middleware registrations now unified in server-middleware.ts.
React Start Plugin Optimization
packages/react-start/src/plugin/rsbuild.ts
Added reactStartRsbuildEnvironmentOverrides constant specifying module resolution deduplication for react and react-dom, wired into corePluginOpts under rsbuild.environments to apply by default.

Estimated code review effort

🎯 4 (Complex) | ⏱️ ~45 minutes

Poem

🐰 A preview mode hops in with style,
Rsbuild now runs mile after mile,
Middleware unified, dev and preview dance,
React's dependencies get their second chance!
The old dev-server takes its final bow,
As the new server-middleware takes the crown now. 🌟

🚥 Pre-merge checks | ✅ 4 | ❌ 1

❌ Failed checks (1 warning)

Check name Status Explanation Resolution
Docstring Coverage ⚠️ Warning Docstring coverage is 12.50% 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 and specifically describes the main change: adding support for Rsbuild preview SSR middleware.
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 unit tests (beta)
  • Create PR with unit tests

Tip

💬 Introducing Slack Agent: The best way for teams to turn conversations into code.

Slack Agent is built on CodeRabbit's deep understanding of your code, so your team can collaborate across the entire SDLC without losing context.

  • Generate code and open pull requests
  • Plan features and break down work
  • Investigate incidents and troubleshoot customer tickets together
  • Automate recurring tasks and respond to alerts with triggers
  • Summarize progress and report instantly

Built for teams:

  • Shared memory across your entire org—no repeating context
  • Per-thread sandboxes to safely plan and execute work
  • Governance built-in—scoped access, auditability, and budget controls

One agent for your entire SDLC. Right inside Slack.

👉 Get started


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 and usage tips.

@github-actions
Copy link
Copy Markdown
Contributor

github-actions Bot commented May 9, 2026

Bundle Size Benchmarks

  • Commit: 4eed408f127b
  • Measured at: 2026-05-09T10:39:44.199Z
  • Baseline source: history:35e88f04996d
  • Dashboard: bundle-size history
Scenario Current (gzip) Delta vs baseline Initial gzip Raw Brotli Trend
react-router.minimal 87.29 KiB +139 B (+0.16%) 87.15 KiB 274.07 KiB 75.81 KiB ▁▁▁▁▁▁▁▁▁▁▁█
react-router.full 90.82 KiB +141 B (+0.15%) 90.68 KiB 285.58 KiB 78.82 KiB ▁▁▁▁▁▁▁▁▁▁▁█
solid-router.minimal 35.51 KiB +126 B (+0.35%) 35.38 KiB 106.36 KiB 31.91 KiB ▁▁▁▁▁▁▁▁▁▁▁█
solid-router.full 40.23 KiB +127 B (+0.31%) 40.10 KiB 120.58 KiB 36.14 KiB ▁▁▁▁▁▁▁▁▁▁▁█
vue-router.minimal 53.28 KiB +131 B (+0.24%) 53.15 KiB 151.51 KiB 47.83 KiB ▁▁▁▁▁▁▁▁▁▁▁█
vue-router.full 58.41 KiB +133 B (+0.22%) 58.28 KiB 167.68 KiB 52.30 KiB ▁▁▁▁▁▁▁▁▁▁▁█
react-start.minimal 101.97 KiB +141 B (+0.14%) 101.84 KiB 322.51 KiB 88.13 KiB ▁▁▁▁▁▁▁▁▁▁▃█
react-start.full 105.41 KiB +140 B (+0.13%) 105.27 KiB 332.84 KiB 91.10 KiB ▁▁▁▁▁▁▁▁▁▁▄█
react-start.rsbuild.minimal 99.60 KiB +174 B (+0.17%) 99.43 KiB 316.97 KiB 85.65 KiB ▁▁▁▁▁▁▁▁▁▁▄█
react-start.rsbuild.full 102.89 KiB +174 B (+0.17%) 102.72 KiB 327.41 KiB 88.45 KiB ▁▁▁▁▁▁▁▁▁▁▃█
solid-start.minimal 49.61 KiB +131 B (+0.26%) 49.48 KiB 152.48 KiB 43.79 KiB ▁▁▁▁▁▁▁▁▁▁▄█
solid-start.full 55.40 KiB +133 B (+0.24%) 55.27 KiB 169.39 KiB 48.70 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.

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

🤖 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 `@packages/start-plugin-core/src/rsbuild/schema.ts`:
- Around line 11-13: The rsbuild schema currently only accepts
installServerMiddleware which breaks configs using the old
installDevServerMiddleware key; update the rsbuild zod object to accept both
installServerMiddleware and installDevServerMiddleware as optional booleans,
then normalize to a single canonical property (prefer installServerMiddleware
when both present) by adding a transform/preprocess so downstream code uses only
installServerMiddleware; mark installDevServerMiddleware as deprecated in the
schema/comments/types so it remains supported for one release.

In `@packages/start-plugin-core/src/rsbuild/server-middleware.ts`:
- Around line 185-193: The HTML response in the error path of
server-middleware.ts currently injects the raw exception message (e.message)
into the <pre>, causing reflected XSS; update the error rendering inside the new
Response to never output unescaped user-controlled text by either replacing the
displayed message with a generic string (e.g., "Internal server error") or by
HTML-escaping the value first (add a small helper like escapeHtml and call it
where e or e.message is interpolated). Ensure you reference the same Response
construction site and the exception variable e so the template uses the
escaped/generic text instead of e.message directly.
🪄 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: 42518d46-d3b2-436c-8ce5-3d554a2f3e68

📥 Commits

Reviewing files that changed from the base of the PR and between 4eed408 and e4d0a4e.

📒 Files selected for processing (8)
  • e2e/react-start/basic/package.json
  • e2e/react-start/basic/playwright.config.ts
  • e2e/react-start/custom-server-rsbuild/rsbuild.config.ts
  • packages/react-start/src/plugin/rsbuild.ts
  • packages/start-plugin-core/src/rsbuild/dev-server.ts
  • packages/start-plugin-core/src/rsbuild/plugin.ts
  • packages/start-plugin-core/src/rsbuild/schema.ts
  • packages/start-plugin-core/src/rsbuild/server-middleware.ts
💤 Files with no reviewable changes (1)
  • packages/start-plugin-core/src/rsbuild/dev-server.ts

Comment on lines 11 to 13
rsbuild: z
.object({ installDevServerMiddleware: z.boolean().optional() })
.object({ installServerMiddleware: z.boolean().optional() })
.optional(),
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

Keep the old option as a deprecated alias for one release.

This rename is currently a breaking change. Existing configs that still set rsbuild.installDevServerMiddleware will silently re-enable the middleware after upgrade, because only installServerMiddleware is recognized now. Please accept both keys for now and prefer the new one when both are present.

🤖 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/start-plugin-core/src/rsbuild/schema.ts` around lines 11 - 13, The
rsbuild schema currently only accepts installServerMiddleware which breaks
configs using the old installDevServerMiddleware key; update the rsbuild zod
object to accept both installServerMiddleware and installDevServerMiddleware as
optional booleans, then normalize to a single canonical property (prefer
installServerMiddleware when both present) by adding a transform/preprocess so
downstream code uses only installServerMiddleware; mark
installDevServerMiddleware as deprecated in the schema/comments/types so it
remains supported for one release.

Comment on lines +185 to +193
new Response(
`<!DOCTYPE html>
<html lang="en">
<head><meta charset="UTF-8" /><title>Error</title></head>
<body>
<h1>Internal Server Error</h1>
<pre>${e instanceof Error ? e.message : String(e)}</pre>
</body>
</html>`,
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

Escape the reflected error text before returning HTML.

The <pre> currently embeds e.message verbatim. If the thrown message includes request-controlled text, this turns SSR failures into reflected HTML injection/XSS. Please HTML-escape the value or replace it with a generic message.

Possible fix
+function escapeHtml(value: string): string {
+  return value.replace(/[&<>"']/g, (char) => {
+    switch (char) {
+      case '&':
+        return '&amp;'
+      case '<':
+        return '&lt;'
+      case '>':
+        return '&gt;'
+      case '"':
+        return '&quot;'
+      case "'":
+        return '&#39;'
+      default:
+        return char
+    }
+  })
+}
-    <pre>${e instanceof Error ? e.message : String(e)}</pre>
+    <pre>${escapeHtml(e instanceof Error ? e.message : String(e))}</pre>
📝 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
new Response(
`<!DOCTYPE html>
<html lang="en">
<head><meta charset="UTF-8" /><title>Error</title></head>
<body>
<h1>Internal Server Error</h1>
<pre>${e instanceof Error ? e.message : String(e)}</pre>
</body>
</html>`,
function escapeHtml(value: string): string {
return value.replace(/[&<>"']/g, (char) => {
switch (char) {
case '&':
return '&amp;'
case '<':
return '&lt;'
case '>':
return '&gt;'
case '"':
return '&quot;'
case "'":
return '&#39;'
default:
return char
}
})
}
new Response(
`<!DOCTYPE html>
<html lang="en">
<head><meta charset="UTF-8" /><title>Error</title></head>
<body>
<h1>Internal Server Error</h1>
<pre>${escapeHtml(e instanceof Error ? e.message : String(e))}</pre>
</body>
</html>`,
🤖 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/start-plugin-core/src/rsbuild/server-middleware.ts` around lines 185
- 193, The HTML response in the error path of server-middleware.ts currently
injects the raw exception message (e.message) into the <pre>, causing reflected
XSS; update the error rendering inside the new Response to never output
unescaped user-controlled text by either replacing the displayed message with a
generic string (e.g., "Internal server error") or by HTML-escaping the value
first (add a small helper like escapeHtml and call it where e or e.message is
interpolated). Ensure you reference the same Response construction site and the
exception variable e so the template uses the escaped/generic text instead of
e.message directly.

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