# RSC Boundaries Detect and prevent invalid patterns when crossing Server/Client component boundaries. ## Detection Rules ### 1. Async Client Components Are Invalid Client components **cannot** be async functions. Only Server Components can be async. **Detect:** File has `'use client'` AND component is `async function` or returns `Promise` ```tsx // Bad: async client component 'use client' export default async function UserProfile() { const user = await getUser() // Cannot await in client component return
{user.name}
} // Good: Remove async, fetch data in parent server component // page.tsx (server component - no 'use client') export default async function Page() { const user = await getUser() return } // UserProfile.tsx (client component) 'use client' export function UserProfile({ user }: { user: User }) { return
{user.name}
} ``` ```tsx // Bad: async arrow function client component 'use client' const Dashboard = async () => { const data = await fetchDashboard() return
{data}
} // Good: Fetch in server component, pass data down ``` ### 2. Non-Serializable Props to Client Components Props passed from Server → Client must be JSON-serializable. **Detect:** Server component passes these to a client component: - Functions (except Server Actions with `'use server'`) - `Date` objects - `Map`, `Set`, `WeakMap`, `WeakSet` - Class instances - `Symbol` (unless globally registered) - Circular references ```tsx // Bad: Function prop // page.tsx (server) export default function Page() { const handleClick = () => console.log('clicked') return } // Good: Define function inside client component // ClientButton.tsx 'use client' export function ClientButton() { const handleClick = () => console.log('clicked') return } ``` ```tsx // Bad: Date object (silently becomes string, then crashes) // page.tsx (server) export default async function Page() { const post = await getPost() return // Date object } // PostCard.tsx (client) - will crash on .getFullYear() 'use client' export function PostCard({ createdAt }: { createdAt: Date }) { return {createdAt.getFullYear()} // Runtime error! } // Good: Serialize to string on server // page.tsx (server) export default async function Page() { const post = await getPost() return } // PostCard.tsx (client) 'use client' export function PostCard({ createdAt }: { createdAt: string }) { const date = new Date(createdAt) return {date.getFullYear()} } ``` ```tsx // Bad: Class instance const user = new UserModel(data) // Methods will be stripped // Good: Pass plain object const user = await getUser() ``` ```tsx // Bad: Map/Set // Good: Convert to array/object ``` ### 3. Server Actions Are the Exception Functions marked with `'use server'` CAN be passed to client components. ```tsx // Valid: Server Action can be passed // actions.ts 'use server' export async function submitForm(formData: FormData) { // server-side logic } // page.tsx (server) import { submitForm } from './actions' export default function Page() { return // OK! } // ClientForm.tsx (client) 'use client' export function ClientForm({ onSubmit }: { onSubmit: (data: FormData) => Promise }) { return
...
} ``` ## Quick Reference | Pattern | Valid? | Fix | |---------|--------|-----| | `'use client'` + `async function` | No | Fetch in server parent, pass data | | Pass `() => {}` to client | No | Define in client or use server action | | Pass `new Date()` to client | No | Use `.toISOString()` | | Pass `new Map()` to client | No | Convert to object/array | | Pass class instance to client | No | Pass plain object | | Pass server action to client | Yes | - | | Pass `string/number/boolean` | Yes | - | | Pass plain object/array | Yes | - |