codingstairs
NotesEDULifeContact
⌕Search⌘K
koen

Navigation

  • Intro
  • Blog
  • Life

Get in touch

Send without signing in. Add your email if you'd like a reply.

  • Leave a message anonymously →
  • ✉ warragon112@gmail.com
  • KakaoTalk Open Chat ↗

© 2026 codingstairs

  • Notes
  • EDU
  • Search
  • Life
  • Contact
  • Legal
  • RSS
  • GitHub
EDU›Build Your First Fullstack App with Next.js 16›Step 2

Step 2

Step 2 — Server vs Client components

0 views

Step 2 — Server vs Client components

The core App Router concept. Server Component by default, opt in to 'use client'.

1. Why split?

  • Most code doesn't need to ship to the browser
  • Server fetches DB/APIs, sends only HTML
  • Smaller client bundle, faster initial load
  • Secrets (API keys) stay on the server

2. Server Component (default)

async function getPosts() {
  const res = await fetch("https://api.example.com/posts", {
    next: { revalidate: 60 },
  });
  return res.json();
}

export default async function PostsPage() {
  const posts = await getPosts();
  return (
    <ul className="space-y-2 p-6">
      {posts.map((p: any) => <li key={p.id}>{p.title}</li>)}
    </ul>
  );
}
  • async components allowed
  • fetch results cached with revalidate
  • No useState, no event handlers
  • No document/window

3. Client Component

"use client";
import { useState } from "react";

export default function Counter() {
  const [n, setN] = useState(0);
  return (
    <button onClick={() => setN(n + 1)}
      className="rounded bg-blue-600 px-4 py-2 text-white">
      {n}
    </button>
  );
}

'use client' at the very top.

4. When to use 'use client'

  • useState, useEffect, useRef
  • Event handlers (onClick, onChange)
  • Browser APIs (window, localStorage)
  • Browser-only libraries

5. Composing

import Counter from "./counter";

async function getUser() { return { name: "Alice" }; }

export default async function Home() {
  const user = await getUser();
  return (
    <main className="p-6 space-y-4">
      <h1 className="text-2xl">Hello, {user.name}</h1>
      <Counter />
    </main>
  );
}

Server imports Client. The reverse (Client importing Server) is indirect — pass Server components as children.

6. Props are serializable

<Counter initial={42} />

Only JSON-serializable values cross the boundary — no functions, no class instances.

7. Keep the boundary shallow

Bad: 'use client' at the root → everything client. Good: keep 'use client' scoped to interactive leaf components.

8. cookies() · headers()

Server-only.

import { cookies, headers } from "next/headers";

export default async function Page() {
  const cookieStore = await cookies();
  const theme = cookieStore.get("theme")?.value ?? "light";
  return <div>theme: {theme}</div>;
}

Next 15+ returns Promises.

9. Server Action — form submit

async function createPost(formData: FormData) {
  "use server";
  const title = formData.get("title") as string;
  // save to DB
}

export default function NewPostPage() {
  return (
    <form action={createPost} className="p-6 space-y-2">
      <input name="title" className="border p-2" />
      <button type="submit" className="bg-blue-600 text-white px-4 py-2">Save</button>
    </form>
  );
}

No API route needed.

10. Streaming + Suspense

import { Suspense } from "react";
import PostList from "./post-list";

export default function Page() {
  return (
    <main>
      <h1>Posts</h1>
      <Suspense fallback={<p>loading...</p>}>
        <PostList />
      </Suspense>
    </main>
  );
}

11. Cache / revalidate

fetch(url, { next: { revalidate: 3600 } });
fetch(url, { cache: "no-store" });
fetch(url, { next: { tags: ["posts"] } });

After mutation: revalidateTag("posts").

12. force-dynamic

export const dynamic = "force-dynamic";

Bypasses caching. Use only when real-time data is essential.

13. Gotchas

  • useState without 'use client' — build error
  • window in Server Component — runtime error
  • Non-serializable props
  • DB access from Client — use Server Actions or API routes
  • cookies() in Client — server-only

Closing

Server by default, Client by opt-in. That one sentence summarizes App Router thinking.

Next

  • 03-api-drizzle

References: Next.js — Server and Client Components · React 19 Server Components.

← Step 1

Step 1 — Project setup

Step 3 →

Step 3 — API Route + Drizzle ORM