963 markdown files reformatted with the repository's pinned prettier so pnpm format:check covers the folded tree like every other repo file. The formatter's embedded-language pass also normalized code fences (TS semicolons, closed HTML tags in examples, lowercased CSS hex colors, one renumbered list that skipped an index). Alphanumeric token deltas vs the fold commit were audited file-by-file; all are formatter-equivalent markup normalizations plus the four sanitized skills.
4.6 KiB
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') DateobjectsMap,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 | - |