Skip to content

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

Open
afonsojramos wants to merge 1 commit intoTanStack:mainfrom
afonsojramos:fix/strip-middleware-client-build
Open

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

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

  • Bug Fixes

    • Ensure middleware method calls are removed from client-side compiled output so middleware isn't executed or referenced in browser bundles.
  • Tests

    • Added tests verifying middleware is stripped for client builds and preserved for server builds to prevent regressions.

@coderabbitai
Copy link
Copy Markdown
Contributor

coderabbitai bot commented Apr 9, 2026

📝 Walkthrough

Walkthrough

handleCreateServerFn now extracts middleware from method chains and, when compiling for client, strips middleware method calls from the client AST. A new test suite verifies middleware is removed for env: 'client' and preserved for env: 'server'.

Changes

Cohort / File(s) Summary
Middleware Stripping Logic
packages/start-plugin-core/src/start-compiler-plugin/handleCreateServerFn.ts
Destructures middleware from methodChain entries and conditionally calls stripMethodCall(middleware.callPath) when context.env === 'client'. Existing inputValidator stripping and subsequent handler/rpc transformations unchanged.
Tests: client/server middleware behavior
packages/start-plugin-core/tests/compiler.test.ts
Adds middleware stripping on client test suite asserting .middleware() is removed from compiled client output and remains present for server compilation.

Estimated code review effort

🎯 2 (Simple) | ⏱️ ~8 minutes

Poem

🐇 I nibbled code beneath the moonlight,

Stripped a middleware call, soft and slight,
Client hops tidy, server keeps the key,
A rabbit's tweak — small, and spry as can be. 🥕

🚥 Pre-merge checks | ✅ 3
✅ Passed checks (3 passed)
Check name Status Explanation
Description Check ✅ Passed Check skipped - CodeRabbit’s high-level summary is enabled.
Title check ✅ Passed The title clearly and specifically describes the main change: stripping middleware chains during client builds to fix a compilation issue.
Docstring Coverage ✅ Passed Docstring coverage is 100.00% which is sufficient. The required threshold is 80.00%.

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

✨ 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.

@github-actions
Copy link
Copy Markdown
Contributor

github-actions bot commented Apr 9, 2026

Bundle Size Benchmarks

  • Commit: 6ad41eaf646f
  • Measured at: 2026-04-09T21:18:26.355Z
  • 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.

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

🧹 Nitpick comments (1)
packages/start-plugin-core/tests/compiler.test.ts (1)

464-485: Strengthen server assertion to verify middleware chain preservation.

Checking only authMiddleware can still pass if the import remains but .middleware() is accidentally stripped. Add an explicit assertion for the middleware call shape.

Suggested test tightening
   expect(result).not.toBeNull()
   // Server should keep middleware intact
+  expect(result!.code).toContain('.middleware(')
   expect(result!.code).toContain('authMiddleware')
🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@packages/start-plugin-core/tests/compiler.test.ts` around lines 464 - 485,
The test currently only checks for the imported symbol authMiddleware which can
remain even if the .middleware() call was removed; update the assertion in the
'preserves .middleware() on server builds' test (which uses createFullCompiler
and compiler.compile for createServerFn) to explicitly verify the middleware
call shape is preserved in result!.code (for example assert the string
'.middleware(' or the exact '.middleware([authMiddleware])' appears), so the
test fails if .middleware() is stripped while the import remains.
🤖 Prompt for all review comments with AI agents
Verify each finding against the current code and only fix it if needed.

Nitpick comments:
In `@packages/start-plugin-core/tests/compiler.test.ts`:
- Around line 464-485: The test currently only checks for the imported symbol
authMiddleware which can remain even if the .middleware() call was removed;
update the assertion in the 'preserves .middleware() on server builds' test
(which uses createFullCompiler and compiler.compile for createServerFn) to
explicitly verify the middleware call shape is preserved in result!.code (for
example assert the string '.middleware(' or the exact
'.middleware([authMiddleware])' appears), so the test fails if .middleware() is
stripped while the import remains.

ℹ️ Review info
⚙️ Run configuration

Configuration used: defaults

Review profile: CHILL

Plan: Pro

Run ID: 212ee0df-7ba8-4bbc-9bab-80b61e622879

📥 Commits

Reviewing files that changed from the base of the PR and between 289c86d 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
🚧 Files skipped from review as they are similar to previous changes (1)
  • packages/start-plugin-core/src/start-compiler-plugin/handleCreateServerFn.ts

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