Fixing \"Functions cannot be passed directly to Client Components\"

See the exact error Next.js throws when a Server Component passes a plain function as a prop, why it happens, and the two real fixes: move the handler into the Client Component or use a Server Action.

6 min read

A Server Component that hands a plain function down to a Client Component as a prop fails with this exact message, either during the build's prerender step or the first time the route renders.

texttext
Error: Functions cannot be passed directly to Client Components unless you explicitly expose it by marking it with "use server". Or maybe you meant to call this function rather than return it.

Next.js also prints the specific prop and value it choked on, something like {onAddToCart: function}, right after the message above. This error means a Server Component tried to hand a plain JavaScript function down to a Client Component as a prop, and React had no way to send that function across the network to the browser.

The smallest reproduction

This page is a Server Component. It defines an inline arrow function and passes it straight into a button component as a prop named onAddToCart.

App.tsxApp.tsx
// app/products/[id]/page.tsx
import AddToCartButton from '@/app/ui/add-to-cart-button'
 
export default function ProductPage() {
  return (
    <AddToCartButton onAddToCart={() => console.log('added')} />
  )
}

The button component below marks itself as a Client Component because it needs to respond to a click event, and it just reads whatever function it receives through the onAddToCart prop without knowing where that function came from.

App.tsxApp.tsx
// app/ui/add-to-cart-button.tsx
'use client'
 
export default function AddToCartButton({
  onAddToCart,
}: {
  onAddToCart: () => void
}) {
  return <button onClick={onAddToCart}>Add to cart</button>
}

Rendering ProductPage throws the error at the top of this article, either during the build's static prerender or at request time for a dynamic route. Nothing here is a typo, and the button component is written correctly.

The problem is the arrow function defined inside the Server Component. React tries to include it in the props it sends to the button and finds no way to turn a live function reference into serialized data.

Why this happens

A Client Component's props travel to the browser inside the React Server Component payload, a serialized data format built from the tree of Server Components above it. React can only put values into that payload that it knows how to reconstruct on the other side, such as strings, numbers, plain objects, and arrays.

A plain function is a live reference into the server's memory. It cannot be turned into data, so React refuses to include it and throws instead of silently sending a broken prop. A Server Action is the one exception, because the "use server" directive tells Next.js to compile that function into a callable reference the client can invoke over the network, not the function body itself.

For the complete list of which prop types serialize and which do not, see What Props Can You Pass from Server to Client Components in Next.js. This article only covers the one failure and its two fixes.

Fix A: move the handler into the Client Component

Most of the time the click behavior belongs on the client anyway, since it is reacting to a browser event with no server logic involved. Define the function where it runs instead of trying to send it in from the server.

App.tsxApp.tsx
// app/ui/add-to-cart-button.tsx
'use client'
import { useState } from 'react'
 
export default function AddToCartButton() {
  const [added, setAdded] = useState(false)
  const label = added ? 'Added' : 'Add to cart'
  return <button onClick={() => setAdded(true)}>{label}</button>
}

The "use client" directive stays because this component now owns state and an event handler, which only a Client Component can do. The click handler is defined right where it is used, so nothing has to travel across the boundary as a prop.

App.tsxApp.tsx
// app/products/[id]/page.tsx
import AddToCartButton from '@/app/ui/add-to-cart-button'
 
export default function ProductPage() {
  return <AddToCartButton />
}

The page component goes back to being a plain Server Component with no function props to serialize. Clicking the button now updates added entirely in the browser, with no server round trip.

Fix B: use a Server Action

Reach for this fix when the click actually needs to run something on the server, such as writing to a database or reading a secret. Mark the function with "use server" and Next.js compiles it into a reference the client can call safely.

typescripttypescript
// app/actions/cart.ts
'use server'
 
export async function addToCart(productId: string) {
  await fetch(`https://api.example.com/cart/${productId}`, {
    method: 'POST',
  })
}

The "use server" directive marks this function as a Server Action, so calling it from the client sends a request back to the server instead of trying to run the function body in the browser. Pass it into the Client Component the same way you would pass any other prop.

App.tsxApp.tsx
// app/products/[id]/page.tsx
import AddToCartButton from '@/app/ui/add-to-cart-button'
import { addToCart } from '@/app/actions/cart'
 
export default function ProductPage() {
  return <AddToCartButton onAddToCart={() => addToCart('sku_123')} />
}

This still passes a function-shaped prop, but Next.js recognizes it as a Server Action reference rather than a raw function, so it serializes fine. The button component itself does not need any changes from the original broken version, since it only calls whatever function it receives.

How to confirm the error is gone

Reload the page or rerun the build and the error text should no longer appear in the terminal or the browser overlay. Click the button and confirm the expected behavior happens, either the local state change from Fix A or the network request from Fix B, by checking the Network tab for the Server Action's POST request.

Which fix to choose

SituationUse
The click only updates UI state, like a toggle or a counterFix A, plain client handler
The click needs to write data, call an authenticated API, or read a server-only secretFix B, Server Action

Pick Fix A first if you are not sure, since most interactive UI only needs local state and adds an unnecessary network request with a Server Action. Reach for Fix B only when the behavior genuinely cannot happen without server code. For more on writing Server Actions from scratch, see The use server Directive Explained and Server Actions in Next.js: A Practical Introduction.

Rune AI

Rune AI

Key Insights

  • The error means a Server Component passed a plain function as a prop to a Client Component.
  • Plain functions only exist in server memory and cannot be serialized into the RSC payload.
  • A Server Action marked with "use server" is the one exception, because Next.js compiles it into a callable reference.
  • Fix A moves the handler into the Client Component itself when the logic is purely client-side.
  • Fix B passes a Server Action when the handler needs to run server logic in response to a client event.
  • Choose based on where the logic actually needs to run, not on which fix is shorter to write.
RunePowered by Rune AI

Frequently Asked Questions

Why does Next.js allow a Server Action to cross the boundary but not a normal function?

A function marked with "use server" is compiled into a callable reference that the client can invoke over the network. A plain function only exists in server memory, so React has nothing it can serialize and send.

Does this error only happen in production builds?

No. It can throw during a static prerender at build time or at request time for a dynamic route, depending on when Next.js actually tries to serialize the props.

Can I fix this by wrapping the function in useCallback?

No. useCallback only works inside a Client Component and only memoizes a function that already lives on the client. It does not make a server-defined function serializable.

What if the function needs to read a database or a secret before responding to a click?

That is exactly what a Server Action is for. Mark the function with "use server" and call it from the Client Component's event handler.

Conclusion

This error means a plain, non-serializable function tried to cross the server and client boundary as a prop. Move the handler into the Client Component when the logic is purely client-side, or mark it with "use server" and pass it as a Server Action when it needs to run on the server.