From 69c3eb36431f7e6d19dc8a326954b4efbca5c5d2 Mon Sep 17 00:00:00 2001 From: Afonso Jorge Ramos Date: Thu, 9 Apr 2026 23:12:54 +0200 Subject: [PATCH] fix(start-plugin-core): strip middleware chain on 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. --- .../handleCreateServerFn.ts | 9 +++- .../start-plugin-core/tests/compiler.test.ts | 51 +++++++++++++++++++ 2 files changed, 59 insertions(+), 1 deletion(-) diff --git a/packages/start-plugin-core/src/start-compiler-plugin/handleCreateServerFn.ts b/packages/start-plugin-core/src/start-compiler-plugin/handleCreateServerFn.ts index ddad1cf0611..9709f2c7188 100644 --- a/packages/start-plugin-core/src/start-compiler-plugin/handleCreateServerFn.ts +++ b/packages/start-plugin-core/src/start-compiler-plugin/handleCreateServerFn.ts @@ -227,7 +227,7 @@ export function handleCreateServerFn( for (const candidate of candidates) { const { path: candidatePath, methodChain } = candidate - const { inputValidator, handler } = methodChain + const { inputValidator, handler, middleware } = methodChain // Check if the call is assigned to a variable if (!candidatePath.parentPath.isVariableDeclarator()) { @@ -284,6 +284,13 @@ export function handleCreateServerFn( } } + // Handle middleware - remove on client to prevent server-only + // dependencies (auth, DB, etc.) from leaking into the client bundle. + // Middleware only executes server-side; the client RPC stub skips it. + if (middleware && context.env === 'client') { + stripMethodCall(middleware.callPath) + } + const handlerFnPath = handler?.firstArgPath if (!handler || !handlerFnPath?.node) { diff --git a/packages/start-plugin-core/tests/compiler.test.ts b/packages/start-plugin-core/tests/compiler.test.ts index a063ea3ffe0..99fb067823e 100644 --- a/packages/start-plugin-core/tests/compiler.test.ts +++ b/packages/start-plugin-core/tests/compiler.test.ts @@ -434,6 +434,57 @@ describe('edge cases for detectedKinds', () => { }) }) +describe('middleware stripping on client', () => { + test('strips .middleware() from createServerFn on client builds', async () => { + const compiler = createFullCompiler('client') + + const result = await compiler.compile({ + code: ` + import { createServerFn } from '@tanstack/react-start' + import { authMiddleware } from './server-only-auth' + export const fetchData = createServerFn() + .middleware([authMiddleware]) + .handler(async () => { + return { data: 'hello' } + }) + `, + id: 'with-middleware.ts', + + detectedKinds: new Set(['ServerFn']), + }) + + expect(result).not.toBeNull() + expect(result!.code).toContain('createClientRpc') + // Middleware should be stripped on client — server-only imports + // like authMiddleware must not survive into the client bundle + expect(result!.code).not.toContain('.middleware(') + expect(result!.code).not.toContain('authMiddleware') + }) + + test('preserves .middleware() on server builds', async () => { + const compiler = createFullCompiler('server') + + const result = await compiler.compile({ + code: ` + import { createServerFn } from '@tanstack/react-start' + import { authMiddleware } from './server-only-auth' + export const fetchData = createServerFn() + .middleware([authMiddleware]) + .handler(async () => { + return { data: 'hello' } + }) + `, + id: 'with-middleware-server.ts', + + detectedKinds: new Set(['ServerFn']), + }) + + expect(result).not.toBeNull() + // Server should keep middleware intact + expect(result!.code).toContain('authMiddleware') + }) +}) + test('ingestModule handles empty code gracefully', () => { const compiler = new StartCompiler({ env: 'client',