"Use Server Components and it'll be faster" isn't enough advice
Many projects that move to the Next.js App Router don't use Server Components properly. Either there's no performance gain at all, or "use client" gets pasted everywhere and the feature is wasted. Here's the approach I followed when building this site (canturkcelikok.com).
The default: Server Component
If a component fetches data, holds no state and needs no user interaction, it should stay a Server Component. On this site, every component that loads the project list, blog posts or services is rendered on the server and sends no JavaScript to the client.
// Server Component: talks to the database directly, sends no JS to the client
export async function CaseStudies() {
const projects = await prisma.project.findMany({ where: { published: true } });
return <div>{/* ... */}</div>;
}Drawing the "use client" boundary in the right place
Animation (Framer Motion), form state (React Hook Form) and interactive menus really do need to run on the client. But most of the mistakes come from making a whole page a client component. The right approach is to isolate the interactive part in a small component and leave the rest on the server.
// Wrong: the whole page becomes a client component
"use client";
export default function Page() {
const [open, setOpen] = useState(false);
return <div>{/* all of the page content here */}</div>;
}
// Right: only the menu is a client component, the rest stays on the server
export default async function Page() {
const data = await getData(); // runs on the server
return (
<div>
<MobileMenu /> {/* small, isolated client component */}
<Content data={data} /> {/* server component */}
</div>
);
}Fetching data in parallel
A chain of sequential awaits is the most common reason a page is slower than it needs to be. Parallel fetching with Promise.all is standard on every page of this site:
const [settings, projects, posts] = await Promise.all([
getSiteSettings(),
prisma.project.findMany(),
prisma.blogPost.findMany(),
]);Don't skip next/image
For static images such as project screenshots, next/image gives you automatic sizing and lazy loading. Using a plain <img> drags your Core Web Vitals scores down for no reason.
Don't avoid static generation
For pages that rarely change (project details, blog posts), generating static HTML at build time with generateStaticParams is much faster than going to the database on every request. On this site, all 4 projects and every blog post are generated this way.
Conclusion
Server Components aren't an on/off switch. They're an architectural decision you make for each component. Done right, the amount of JavaScript sent to the user drops significantly, and that's a speed difference real users can feel.
Cantürk Çelikok
Freelance full stack developer based in Ankara, Türkiye. He built live products such as PlusTurk, Nerdesin, Ayda POS and Songnaria Coffee end to end, and shares the notes from those projects here.
