AiTechWorlds
AiTechWorlds
"use client" is a directive that opts a component out of the server-only default and into the browser, and it should be added only where interactivity is actually needed.
Think of it like sending an employee to work on-site versus remote. Most work — reading files, running reports — doesn't need anyone physically present and can stay server-side. Only the tasks that require being in the room (clicking a button, watching a screen update) need the on-site (client) worker. The most common mistake Next.js developers make is sending everyone on-site by default, treating the App Router like a regular React app. The opposite mistake — trying to make everything a Server Component — runs into walls just as fast.
Without "use client", a component is a Server Component. Server Components:
async and await data directlyClient Components ("use client"):
useState, useEffect, useRef, and all React hookswindow, document, localStorageDoes this component need:
│
├── onClick, onChange, onSubmit, or any user interaction?
│ └── YES → "use client"
│
├── useState, useEffect, useRef, useReducer, or any hook?
│ └── YES → "use client"
│
├── window, document, localStorage, or any browser API?
│ └── YES → "use client"
│
├── A third-party library that uses browser APIs?
│ (charting libraries, rich text editors, date pickers)
│ └── YES → "use client"
│
├── Database query, server-side API call, or secret env var?
│ └── Server Component (no "use client")
│
├── Static markup that never changes based on user interaction?
│ └── Server Component
│
└── A component that just receives props and returns JSX?
└── Could be either — start Server, add "use client" if needed// Static blog post — no interactivity needed
// src/app/blog/[slug]/page.tsx
import { getPost } from "@/lib/posts";
export default async function BlogPost({ params }: { params: { slug: string } }) {
const post = await getPost(params.slug); // database query
return <article>{post.content}</article>;
}
// Navigation that's purely rendered server-side
// (if it has no interactive dropdown, active state tracking, etc.)
async function Nav() {
const categories = await getCategories();
return (
<nav>
{categories.map(c => (
<a key={c.id} href={`/courses?category=${c.slug}`}>{c.name}</a>
))}
</nav>
);
}
// Price display — just formats and shows a number
function PriceTag({ price, currency = "USD" }: { price: number; currency?: string }) {
return (
<span className="text-2xl font-bold">
{new Intl.NumberFormat("en-US", { style: "currency", currency }).format(price)}
</span>
);
}"use client";
// Shopping cart — state, interaction
function CartButton() {
const { count, open } = useCart();
return (
<button onClick={open} className="relative">
Cart
{count > 0 && <span className="badge">{count}</span>}
</button>
);
}
// Any form with validation
"use client";
function EnrollmentForm({ courseId }: { courseId: string }) {
const [loading, setLoading] = useState(false);
// ...
}
// Dark mode toggle
"use client";
function ThemeToggle() {
const { theme, setTheme } = useTheme();
return <button onClick={() => setTheme(t => t === "dark" ? "light" : "dark")}>Toggle</button>;
}
// Third-party chart library
"use client";
import { LineChart } from "recharts"; // uses browser DOM
function RevenueChart({ data }: { data: Point[] }) {
return <LineChart data={data} />;
}Push "use client" as deep as possible. Keep the data fetching in Server Components and only make the interactive pieces Client:
// Correct: Server Component fetches data
// src/app/courses/[slug]/page.tsx
export default async function CoursePage({ params }: Props) {
const course = await getCourse(params.slug);
const user = await getSession();
return (
<div>
<CourseHeader course={course} /> {/* Server Component */}
<CourseLessons lessons={course.lessons} /> {/* Server Component */}
{/* Only the enroll button needs client interactivity */}
<EnrollButton
courseId={course.id}
price={course.price}
isEnrolled={user?.enrollments.includes(course.id) ?? false}
/>
</div>
);
}
// Correct: Client Component is just the interactive part
"use client";
function EnrollButton({ courseId, price, isEnrolled }: Props) {
const [loading, setLoading] = useState(false);
async function handleEnroll() {
setLoading(true);
await fetch("/api/enroll", { method: "POST", body: JSON.stringify({ courseId }) });
setLoading(false);
}
if (isEnrolled) return <span>✓ Enrolled</span>;
return (
<button onClick={handleEnroll} disabled={loading}>
{loading ? "Processing..." : `Enroll for $${price}`}
</button>
);
}Mistake 1: Adding "use client" to avoid Server Component limitations
// Wrong: Don't do this just to avoid the async component pattern
"use client";
import { useEffect, useState } from "react";
function CourseList() {
const [courses, setCourses] = useState([]);
useEffect(() => {
fetch("/api/courses").then(r => r.json()).then(setCourses);
}, []);
return courses.map(c => <CourseCard key={c.id} course={c} />);
}
// Correct: Use a Server Component
async function CourseList() {
const courses = await db.course.findMany();
return courses.map(c => <CourseCard key={c.id} course={c} />);
}Mistake 2: Making providers "use client" and putting Server Component children inside them
This is actually fine — Server Components can be children of Client Components:
// Correct: This works correctly
"use client";
export function ThemeProvider({ children }: { children: React.ReactNode }) {
// ...
return <ThemeContext.Provider value={...}>{children}</ThemeContext.Provider>;
}
// layout.tsx — Server Component
import { ThemeProvider } from "@/components/ThemeProvider";
export default function Layout({ children }: { children: React.ReactNode }) {
return (
<ThemeProvider>
{children} {/* children are Server Components — this is fine */}
</ThemeProvider>
);
}Mistake 3: Importing Server Components into Client Components
"use client";
// Wrong: Can't import a Server Component into a Client Component
import { ServerOnlyComponent } from "./ServerOnly";
// Correct: Pass it as a prop (children) from a Server ComponentNext lesson: Server Actions and form handling — calling server code directly from your forms.
Get this course's notes on Telegram!
Free cheat sheets, summaries & practice exercises