# 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 | - |