How to Fix: Props must be serializable for components in “use client” file – Issue for #46795

6 min read

The error “Props must be serializable for components in ‘use client’ file” is usually not about your state updater being invalid. It happens because Next.js is enforcing the React Server Components boundary, and one of your client-marked modules is being treated as part of a public boundary that must only accept serializable props.

Understanding the Root Cause

In the Next.js App Router, the ‘use client’ directive creates a client component entry boundary. That boundary matters because server components can import client components, but any data crossing from server to client must be serializable.

Functions such as setState, event handlers, class instances, database connections, and many custom objects are not serializable. If Next.js believes a component could be rendered from a server component boundary, it validates the component’s props and throws this error when it finds a function prop.

This is why the bug feels confusing: you may have a parent client component passing a state setter to a child client component, which is perfectly valid in plain React. But if the child file also has ‘use client’, Next.js can interpret that file as an exported boundary that must remain safe for server usage. In that context, a function prop is rejected.

The key technical point is this: not every client component needs its own ‘use client’ directive. Only the files that must be imported directly by a server component need to define that boundary.

For deeper background, see the original GitHub discussion for issue #46795.

Step-by-Step Solution

The most reliable fix is to place ‘use client’ only at the top-level client entry component, then keep nested child components as regular modules unless they are imported directly by a server component.

1. Identify the real client boundary

If a parent component is already marked with ‘use client’, all components imported beneath it are already part of the client bundle. They do not need their own directive unless they are also used independently from a server component.

2. Remove redundant ‘use client’ from nested child files

Problematic setup:

'use client'

import { useState } from 'react'
import MessageInput from './MessageInput'

export default function MessagesContainer() {
  const [messages, setMessages] = useState([])

  return <MessageInput messages={messages} setMessages={setMessages} />
}
'use client'

export default function MessageInput({ messages, setMessages }) {
  return (
    <button onClick={() => setMessages([...messages, 'hello'])}>
      Add message
    </button>
  )
}

Even though both are client components, the child file is unnecessarily declaring itself as a separate boundary. That can trigger the serialization rule on setMessages.

Corrected setup:

'use client'

import { useState } from 'react'
import MessageInput from './MessageInput'

export default function MessagesContainer() {
  const [messages, setMessages] = useState([])

  return <MessageInput messages={messages} setMessages={setMessages} />
}
export default function MessageInput({ messages, setMessages }) {
  return (
    <button onClick={() => setMessages([...messages, 'hello'])}>
      Add message
    </button>
  )
}

3. Keep function props inside the client subtree

Passing a function from one client component to another is fine inside the same client subtree. The error appears when that function is exposed across a boundary Next.js expects to be serializable.

4. If a component must be imported by a server component, redesign its API

If the child component truly needs to stay as a standalone ‘use client’ entry file, then its props should be serializable. Instead of passing functions, pass plain data and move interaction logic inside the component.

'use client'

import { useState } from 'react'

export default function MessageInput({ initialMessages }) {
  const [messages, setMessages] = useState(initialMessages)

  return (
    <button onClick={() => setMessages([...messages, 'hello'])}>
      Add message
    </button>
  )
}

In this version, initialMessages is serializable, so the component is safe to render from a server component.

5. Verify imports from server files

Check whether your component is imported into files such as:

  • app/page.tsx
  • app/layout.tsx
  • Any file without ‘use client’

If yes, that component is crossing the server/client boundary and must keep its public props serializable.

Working Example

Here is a practical pattern that works well in Next.js App Router.

Server component

import ChatShell from './ChatShell'

export default async function Page() {
  const initialMessages = ['welcome']

  return <ChatShell initialMessages={initialMessages} />
}

Client entry component

'use client'

import { useState } from 'react'
import MessageList from './MessageList'
import MessageInput from './MessageInput'

export default function ChatShell({ initialMessages }) {
  const [messages, setMessages] = useState(initialMessages)

  return (
    <>
      <MessageList messages={messages} />
      <MessageInput setMessages={setMessages} />
    </>
  )
}

Nested client-only child without its own boundary

export default function MessageInput({ setMessages }) {
  return (
    <button onClick={() => setMessages(prev => [...prev, 'new'])}>
      Add
    </button>
  )
}

This works because only ChatShell is the client boundary exposed to the server. The nested child receives a function, but that function never crosses a server serialization boundary.

Common Edge Cases

1. Passing callbacks through multiple layers

If a callback is passed through several components, any intermediate component marked with ‘use client’ and used as a boundary can trigger the same error. Remove redundant directives in deeply nested files.

2. Non-serializable custom objects

The issue is not limited to functions. These can also fail:

  • Date objects in some transfer scenarios
  • Map and Set
  • Class instances
  • Objects with methods
  • Database models or ORM entities

Convert them to plain JSON-friendly structures before passing them into a client boundary.

3. Importing a client component into a server component by accident

A reusable UI component may work fine under one client parent, then suddenly fail when imported directly into a server file elsewhere. Once that happens, its props must satisfy boundary rules.

4. Barrel exports can hide the boundary

If you re-export components through an index.ts file, it can become harder to see where a client component is being imported. Trace the actual import path carefully.

5. Server Actions are different

In newer Next.js patterns, Server Actions can be passed in specific supported ways, but that is a separate feature and should not be confused with ordinary client callback props like setState.

FAQ

Can I pass setState to another client component in Next.js?

Yes, as long as both components live within the same client subtree and the receiving component is not being treated as a separate server-consumable boundary.

Why does removing ‘use client’ from the child fix the error?

Because the child no longer declares a separate client entry boundary. It becomes an implementation detail inside the parent client component, so function props like callbacks and state setters are allowed normally.

Should I add ‘use client’ to every interactive component?

No. Add it only where needed. Overusing the directive creates unnecessary boundaries, increases confusion around serializable props, and can make your component API harder to reuse correctly.

The safest rule is simple: mark the highest necessary client entry with ‘use client’, keep nested components unmarked unless they must be imported directly by a server component, and only pass serializable props across the server-client boundary.

Leave a Reply

Your email address will not be published. Required fields are marked *