Files
lcbp3/.agents/skills/next-best-practices/rsc-boundaries.md
T
admin 11984bfa29
CI Pipeline / build (push) Failing after 12m41s
Build and Deploy / deploy (push) Failing after 2m44s
260322:1648 Correct Coresspondence / Doing RFA / Correct CI
2026-03-22 16:48:12 +07:00

4.6 KiB

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

// Bad: async client component
'use client';
export default async function UserProfile() {
  const user = await getUser(); // Cannot await in client component
  return <div>{user.name}</div>;
}

// 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 user={user} />;
}

// UserProfile.tsx (client component)
('use client');
export function UserProfile({ user }: { user: User }) {
  return <div>{user.name}</div>;
}
// Bad: async arrow function client component
'use client';
const Dashboard = async () => {
  const data = await fetchDashboard();
  return <div>{data}</div>;
};

// 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
// Bad: Function prop
// page.tsx (server)
export default function Page() {
  const handleClick = () => console.log('clicked');
  return <ClientButton onClick={handleClick} />;
}

// Good: Define function inside client component
// ClientButton.tsx
('use client');
export function ClientButton() {
  const handleClick = () => console.log('clicked');
  return <button onClick={handleClick}>Click</button>;
}
// Bad: Date object (silently becomes string, then crashes)
// page.tsx (server)
export default async function Page() {
  const post = await getPost();
  return <PostCard createdAt={post.createdAt} />; // Date object
}

// PostCard.tsx (client) - will crash on .getFullYear()
('use client');
export function PostCard({ createdAt }: { createdAt: Date }) {
  return <span>{createdAt.getFullYear()}</span>; // Runtime error!
}

// Good: Serialize to string on server
// page.tsx (server)
export default async function Page() {
  const post = await getPost();
  return <PostCard createdAt={post.createdAt.toISOString()} />;
}

// PostCard.tsx (client)
('use client');
export function PostCard({ createdAt }: { createdAt: string }) {
  const date = new Date(createdAt);
  return <span>{date.getFullYear()}</span>;
}
// Bad: Class instance
const user = new UserModel(data)
<ClientProfile user={user} /> // Methods will be stripped

// Good: Pass plain object
const user = await getUser()
<ClientProfile user={{ id: user.id, name: user.name }} />
// Bad: Map/Set
<ClientComponent items={new Map([['a', 1]])} />

// Good: Convert to array/object
<ClientComponent items={Object.fromEntries(map)} />
<ClientComponent items={Array.from(set)} />

3. Server Actions Are the Exception

Functions marked with 'use server' CAN be passed to client components.

// 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 <ClientForm onSubmit={submitForm} />; // OK!
}

// ClientForm.tsx (client)
('use client');
export function ClientForm({ onSubmit }: { onSubmit: (data: FormData) => Promise<void> }) {
  return <form action={onSubmit}>...</form>;
}

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 -