Skip to content

fix(start-plugin-core): strip middleware chain on client builds#7131

Closed
afonsojramos wants to merge 1 commit intoTanStack:mainfrom
afonsojramos:repro/middleware-client-leak
Closed

fix(start-plugin-core): strip middleware chain on client builds#7131
afonsojramos wants to merge 1 commit intoTanStack:mainfrom
afonsojramos:repro/middleware-client-leak

Conversation

@afonsojramos
Copy link
Copy Markdown

@afonsojramos afonsojramos commented Apr 9, 2026

Summary

The createServerFn compiler correctly replaces .handler(fn) with .handler(createClientRpc('hash')) for client builds, and strips .inputValidator() — but leaves the .middleware([...]) chain intact. This keeps server-only imports (auth middleware, DB connections, etc.) alive in the client bundle.

With esbuild (Vite 7/rolldown-vite), these dead references were tree-shaken. With Rolldown (Vite 8), the middleware functions remain as live symbol references, pulling the entire server dependency tree into the client bundle. This causes runtime crashes like:

ReferenceError: Buffer is not defined
    at server-BPubewzM.js:2:19645

...when Node.js-only packages (e.g. postgres) end up in the browser, preventing hydrateRoot from completing.

Fix

Strip .middleware() calls on client builds using stripMethodCall, matching the existing pattern for .inputValidator(). Since middleware only executes server-side and the client RPC stub skips it entirely, the middleware chain is dead code on the client.

Before (client build output):

const fn = createServerFn({method: 'GET'})
  .middleware([requireAuth, requirePermission('academic', 'read')])
  .handler(createClientRpc('hash'))

After (client build output):

const fn = createServerFn({method: 'GET'})
  .handler(createClientRpc('hash'))

This makes the middleware imports (requireAuth, requirePermission) unreferenced, allowing the bundler to tree-shake the entire server dependency chain.

Tests

Added two tests to compiler.test.ts:

  • strips .middleware() from createServerFn on client builds — verifies .middleware() and the middleware import are removed from client output
  • preserves .middleware() on server builds — verifies server builds keep middleware intact

Context

Summary by CodeRabbit

  • Improvements

    • Enhanced the compiler to refine how middleware is handled during the build process, ensuring proper code separation between client and server environments.
  • Tests

    • Added test coverage to verify middleware management across compilation environments. Tests confirm middleware is correctly preserved in server builds and properly removed from client builds.

The `createServerFn` compiler correctly replaces `.handler(fn)` with
`.handler(createClientRpc('hash'))` for client builds, but leaves the
`.middleware([...])` chain intact. This keeps server-only imports
(auth middleware, DB connections, etc.) alive in the client bundle.

With esbuild (Vite 7), these dead references were tree-shaken. With
Rolldown (Vite 8), the middleware functions remain as live references,
pulling the entire server dependency tree into the client bundle. This
causes runtime crashes like `ReferenceError: Buffer is not defined`
when Node.js-only packages (e.g. `postgres`) end up in the browser.

The fix strips `.middleware()` calls on client builds, matching the
existing pattern used for `.inputValidator()`. Since middleware only
executes server-side and the client RPC stub skips it entirely, the
middleware chain is dead code on the client.
@coderabbitai
Copy link
Copy Markdown
Contributor

coderabbitai bot commented Apr 9, 2026

Caution

Review failed

The pull request is closed.

ℹ️ Recent review info
⚙️ Run configuration

Configuration used: defaults

Review profile: CHILL

Plan: Pro

Run ID: 36a13cc3-e48a-451e-a46b-f80847fefec3

📥 Commits

Reviewing files that changed from the base of the PR and between 6ad41ea and 69c3eb3.

📒 Files selected for processing (2)
  • packages/start-plugin-core/src/start-compiler-plugin/handleCreateServerFn.ts
  • packages/start-plugin-core/tests/compiler.test.ts

📝 Walkthrough

Walkthrough

The compiler now extracts middleware from server function method chains and strips middleware calls during client-side builds while preserving them server-side. Handler and input validator behavior remains unchanged. New tests verify middleware removal in client compilations and preservation in server compilations.

Changes

Cohort / File(s) Summary
Middleware Stripping Logic
packages/start-plugin-core/src/start-compiler-plugin/handleCreateServerFn.ts
Extended methodChain destructuring to include middleware, added conditional AST stripping via stripMethodCall() when compiling for client environment.
Middleware Test Suite
packages/start-plugin-core/tests/compiler.test.ts
New describe('middleware stripping on client') test suite verifying middleware and related imports are removed from client builds and preserved in server builds.

Estimated code review effort

🎯 2 (Simple) | ⏱️ ~10 minutes

Poem

🐰 With a hop and a skip, middleware takes flight,
From client-side builds—stripped clean, stripped light,
But server-side? Keep it! That's where it thrives,
A rabbit's delight in conditional lives! 🌙✨

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

@afonsojramos
Copy link
Copy Markdown
Author

Duplicate, reopening #7125.

@github-actions
Copy link
Copy Markdown
Contributor

github-actions bot commented Apr 9, 2026

Bundle Size Benchmarks

  • Commit: 6ad41eaf646f
  • Measured at: 2026-04-09T21:15:55.346Z
  • Baseline source: history:796406da66cf
  • Dashboard: bundle-size history
Scenario Current (gzip) Delta vs baseline Raw Brotli Trend
react-router.minimal 87.48 KiB 0 B (0.00%) 275.76 KiB 75.97 KiB ████▁▁▁▁▁▂▃
react-router.full 90.78 KiB 0 B (0.00%) 286.95 KiB 78.95 KiB ▆▆▆█▁▁▁▁▁▂▂
solid-router.minimal 35.56 KiB 0 B (0.00%) 107.26 KiB 31.94 KiB ████▁▁▁▁▁▂█
solid-router.full 40.03 KiB 0 B (0.00%) 120.79 KiB 35.94 KiB ████▁▁▁▁▁▂▆
vue-router.minimal 53.38 KiB 0 B (0.00%) 153.07 KiB 47.94 KiB ████▁▁▁▁▁▂▄
vue-router.full 58.25 KiB 0 B (0.00%) 168.53 KiB 52.18 KiB ████▁▁▁▁▁▂▄
react-start.minimal 102.01 KiB 0 B (0.00%) 324.00 KiB 88.21 KiB ▅▅▅█▁▂▂▂▂▃▃
react-start.full 105.35 KiB -35 B (-0.03%) 334.27 KiB 91.05 KiB ▆▆▆█▂▂▂▃▃▃▄▁
solid-start.minimal 49.66 KiB 0 B (0.00%) 153.51 KiB 43.84 KiB ▇▇▇▇▁▁▁▂▂▂█
solid-start.full 55.14 KiB -37 B (-0.07%) 169.66 KiB 48.48 KiB ▇▇▇▇▁▂▂▂▂▃█▅

Trend sparkline is historical gzip bytes ending with this PR measurement; lower is better.

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