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