# Server Components React Server Components (RSC), Server Actions, Next.js App Router patterns, caching, and streaming. --- ## RSC Architecture Server Components render on the server and send HTML (and a serialized React tree) to the client. They never ship their code to the browser. ``` Request │ ▼ Server Component Tree (renders on server) │ ├─ Async data fetching (db, fs, fetch) ├─ Heavy dependencies (never in client bundle) └─ Client Component boundaries (marked 'use client') │ ▼ Hydration (client takes over interactive parts only) ``` **Serialization rules — what can cross the server→client boundary:** - Strings, numbers, booleans, null, undefined - Arrays and plain objects of the above - Promises (unwrapped by `use()` on client) - JSX / React elements - **NOT**: functions, class instances, Date objects, Maps, Sets, RegExp (must be serialized or passed differently) --- ## Server Components ```tsx // app/users/page.tsx — Server Component (default, no directive needed) import { db } from '@/lib/db'; import { cache } from 'react'; // cache() deduplicates calls within a single render pass const getUser = cache(async (id: string) => { return db.query.users.findFirst({ where: eq(users.id, id) }); }); // Top-level async component — no useEffect, no loading state needed export default async function UsersPage() { // Fetch in parallel — both start simultaneously const [users, stats] = await Promise.all([ db.query.users.findMany({ limit: 50 }), db.query.stats.findFirst(), ]); return (

Users ({stats?.total ?? 0})

); } ``` ### What You Can Do in Server Components ```tsx // 1. Database queries (Drizzle, Prisma, raw SQL) const posts = await db.select().from(postsTable).where(eq(postsTable.published, true)); // 2. File system access import { readFile } from 'fs/promises'; const content = await readFile('./data/content.md', 'utf8'); // 3. Server-only secrets (never sent to client) const apiData = await fetch('https://api.example.com/data', { headers: { Authorization: `Bearer ${process.env.SECRET_API_KEY}` }, }); // 4. Import heavy libraries without bundle cost import { parse } from 'some-huge-parser'; // 2MB — never in client bundle const result = parse(rawData); // 5. Conditional rendering based on server state/permissions const session = await auth(); if (!session?.user) redirect('/login'); ``` --- ## Client Components ```tsx // components/counter.tsx 'use client'; // marks this module and all its imports as client code import { useState, useEffect } from 'react'; // Anything requiring hooks, browser APIs, or interactivity export function Counter({ initialCount = 0 }: { initialCount?: number }) { const [count, setCount] = useState(initialCount); useEffect(() => { document.title = `Count: ${count}`; }, [count]); return (

{count}

); } ``` ### Passing Server Data to Client Components ```tsx // Server Component (parent) async function ProductPage({ id }: { id: string }) { const product = await db.products.findUnique({ where: { id } }); // Pass serializable data as props return (
{/* Server Component */}
); } // Pattern: pass Server Component output as children to Client Component async function Layout({ children }: { children: React.ReactNode }) { const nav = await buildNavigation(); // server-only fetch return ( }> {/* Shell is Client Component */} {children} ); } ``` --- ## Server Actions ```tsx // app/actions.ts 'use server'; // all exports are server actions import { revalidatePath, revalidateTag } from 'next/cache'; import { redirect } from 'next/navigation'; import { z } from 'zod'; const createPostSchema = z.object({ title: z.string().min(1).max(200), content: z.string().min(10), published: z.coerce.boolean().default(false), }); export async function createPost(formData: FormData) { // Validate const parsed = createPostSchema.safeParse(Object.fromEntries(formData)); if (!parsed.success) { return { error: parsed.error.flatten().fieldErrors }; } // Auth check const session = await auth(); if (!session?.user) throw new Error('Unauthorized'); // Persist const post = await db.posts.create({ data: { ...parsed.data, authorId: session.user.id }, }); // Invalidate cache revalidatePath('/posts'); revalidateTag('posts'); // Redirect (throws internally, not caught by try/catch) redirect(`/posts/${post.id}`); } // Progressive enhancement: works without JS, enhanced with JS export async function deletePost(id: string) { await db.posts.delete({ where: { id } }); revalidatePath('/posts'); } ``` ### Form with Server Action ```tsx // app/posts/new/page.tsx import { createPost } from '../actions'; // Server Component — no 'use client' needed export default function NewPostPage() { return (