Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
Original file line number Diff line number Diff line change
Expand Up @@ -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()) {
Expand Down Expand Up @@ -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) {
Expand Down
51 changes: 51 additions & 0 deletions packages/start-plugin-core/tests/compiler.test.ts
Original file line number Diff line number Diff line change
Expand Up @@ -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',
Expand Down