Build Next.js components using server and client rendering strategies for performance.
// app/components/BlogPost.tsx
export default async function BlogPost({ slug }) {
const post = await fetchPost(slug);
return <article>{post.content}</article>;
}export default async function UserProfile({ id }) {
const user = await db.user.findUnique({ where: { id } });
return <p>{user.name}</p>;
}export default async function Page() {
const settings = await getSettings();
return <ThemeProvider theme={settings.theme} />;
}"use client";
import { useState } from "react";
export default function Counter() {
const [count, setCount] = useState(0);
return <button onClick={() => setCount(count + 1)}>{count}</button>;
}"use client";
import { useEffect, useState } from "react";
export function Clock() {
const [time, setTime] = useState("");
useEffect(() => {
setTime(new Date().toLocaleTimeString());
}, []);
return <p>{time}</p>;
}"use client";
export function SaveButton({ data }) {
function save() {
localStorage.setItem("draft", JSON.stringify(data));
}
return <button onClick={save}>Save Draft</button>;
}"use client";
export default function Layout({ children }) {
return <div>{children}</div>;
}
// children can be server components
<Layout>
<ServerComponent />
</Layout>// Server component fetches and sanitizes data
export default async function Page() {
const post = await getPost(); // has private fields
return <PostCard title={post.title} body={post.body} />;
}"use client";
export function ThemeProvider({ children }) {
const [theme, setTheme] = useState("light");
return (
<ThemeContext.Provider value={{ theme, setTheme }}>
{children}
</ThemeContext.Provider>
);
}// app/page.tsx (server)
import LikeButton from "./LikeButton"; // only this is client
export default async function Post() {
const post = await getPost();
return (
<article>
<h1>{post.title}</h1>
<LikeButton postId={post.id} />
</article>
);
}import "server-only"; // throws if imported by a client bundleexport default async function Products() {
const res = await fetch('/api/products');
const products = await res.json();
return (
<ul>
{products.map(p => <li key={p.id}>{p.name}</li>)}
</ul>
);
}"use client";
import { useEffect, useState } from "react";
export function LiveFeed() {
const [data, setData] = useState(null);
useEffect(() => {
fetch('/api/live-data')
.then(r => r.json())
.then(setData);
}, []);
return <div>{data?.message}</div>;
}"use client";
import useSWR from "swr";
const fetcher = (url: string) => fetch(url).then(r => r.json());
export function Profile({ id }) {
const { data, error } = useSWR(`/api/users/${id}`, fetcher);
if (error) return <p>Error</p>;
return <p>{data?.name}</p>;
}export default async function Page() {
const [user, posts] = await Promise.all([getUser(), getPosts()]);
return <div><UserCard user={user} /><PostList posts={posts} /></div>;
}// Both components call getUser() — fetched only once per request
async function Header() { const u = await getUser(); return <p>{u.name}</p>; }
async function Sidebar() { const u = await getUser(); return <p>{u.role}</p>; }app/
components/
server/
BlogPost.tsx # Server component
Header.tsx # Server component
client/
Counter.tsx # Client component
Modal.tsx # Client component"use client";
// Only interactive part is client
export function Favorite({ postId }) {
const [liked, setLiked] = useState(false);
return <button onClick={() => setLiked(!liked)}>{liked ? "Liked" : "Like"}</button>;
}app/
blog/
page.tsx
components/
PostCard.tsx
PostList.tsx// app/ui/Button.tsx
export function Button({ children, onClick }) {
return <button className="btn" onClick={onClick}>{children}</button>;
}// app/ui/index.ts
export { Button } from "./Button";
export { Card } from "./Card";
// import { Button, Card } from "@/app/ui";Use Client Components when you need browser APIs, event listeners, useState, or useEffect. If your component is purely for rendering data without interactivity, keep it as a Server Component to avoid shipping unnecessary JavaScript to the browser.
Yes — you can import and render Client Components inside Server Components. The boundary only goes one way: Client Components cannot import Server Components, but you can pass Server Components as children or props to Client Components.
The 'use client' directive marks a module boundary — everything imported by that file is also bundled for the client. Extract interactive logic into a small, focused component and add 'use client' only there to keep the rest of the tree server-rendered.
Directly call async functions or await fetch() inside the component body — Server Components are async by default. There's no need for useEffect or SWR; the data is fetched at render time on the server and the result is streamed to the client.
A layout wraps multiple pages and persists across navigation, while a page is the unique UI for a route segment. Both default to Server Components; only add 'use client' if the layout or page itself requires interactivity, otherwise keep data fetching server-side for better performance.