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',