Server and Client Component Patterns in Next.js

Just a guy who loves to write code and watch anime.
Search for a command to run...

Just a guy who loves to write code and watch anime.
No comments yet. Be the first to comment.
Qualities that make outstanding builders.

Bipedal vs quadrupedal: how many legs This is about the number of legs the creature walks on. Bipedal. Two legs. Humans, ostriches, T-rex, kangaroos, most fantasy humanoids. Quadrupedal. Four legs. Do

Intro You hear "lerp" and "smoothstep" everywhere in game dev. They sound like math jargon. They're not. Both are small tools that do the same job: smoothly move from one value to another. The problem

What is Kinematics Kinematics is the math field. It's the study of how things move without worrying about forces (which would be dynamics). FK and IK are the two branches: forward kinematics and inver

Intro Textures are usually the biggest cost in a 3D scene. Memory, bandwidth, and load time all get eaten by them. Resizing them is the obvious lever. There's more. This post is about the less obvious

Fetch data directly
Access backend resources (databases, internal APIs)
Keep sensitive information secure (tokens, API keys)
Hold large dependencies that don't need to be sent to the client
Add interactivity with event listeners (onClick, onChange)
Use React state and lifecycle effects (useState, useEffect)
Access browser-only APIs (window, localStorage)
Use custom hooks that depend on state or browser APIs
Use React class components
Client Components need the "use client" directive at the top of their file to tell Next.js they should run on the client.
When multiple server components need the same data, you don't need to pass props down or use context. Instead, each component can fetch the data it needs directly.
Next.js automatically memoizes fetch calls, so if multiple components request the same data, the actual fetch happens only once. For non-fetch functions, use React's cache function for the same effect.
Sometimes server-specific code might accidentally be included in client bundles, like code accessing private environment variables. To prevent this, use the server-only package:
// In your server-only module
import "server-only";
export async function getData() {
// Code that should only run on the server
const res = await fetch("https://api.example.com", {
headers: { authorization: process.env.API_KEY },
});
return res.json();
}
If any client component tries to import this module, you'll get a build-time error. For client-only code, use the client-only package.
Many third-party components haven't yet added the "use client" directive despite using client-only features. These will work in Client Components but fail in Server Components.
The solution is to wrap these third-party components in your own Client Component:
// carousel.tsx
"use client";
import { Carousel } from "acme-carousel";
export default Carousel;
Now you can use your wrapped component in Server Components.
React context doesn't work in Server Components. To use context, create your provider in a Client Component:
// theme-provider.tsx
"use client";
import { createContext } from "react";
export const ThemeContext = createContext({});
export default function ThemeProvider({ children }) {
return <ThemeContext.Provider value="dark">{children}</ThemeContext.Provider>;
}
Then use it in your Server Component layout:
// layout.tsx
import ThemeProvider from "./theme-provider";
export default function Layout({ children }) {
return (
<html>
<body>
<ThemeProvider>{children}</ThemeProvider>
</body>
</html>
);
}
Place providers as deep in the tree as possible to help Next.js optimize static parts.
To reduce JavaScript sent to the browser, move interactive parts into separate Client Components rather than making entire layouts or pages client components.
Example: Keep a layout as a Server Component, but make just the interactive search bar a Client Component.
You can pass data from Server to Client Components, but the props must be serializable (can be converted to JSON). Complex objects like functions, dates, or classes won't work.
If you need non-serializable data, either fetch it directly in the Client Component or use a Route Handler.
You can think of your UI as a tree starting with the root layout (a Server Component). Some subtrees can be rendered on the client by adding "use client".
Within client subtrees, you can still include Server Components, but there are important rules to follow.
This doesn't work:
"use client";
// ❌ Error: You cannot import a Server Component into a Client Component
import ServerComponent from "./server-component";
export default function ClientComponent() {
const [count, setCount] = useState(0);
return (
<>
<button onClick={() => setCount(count + 1)}>{count}</button>
<ServerComponent />
</>
);
}
This works well:
// In client-component.tsx
'use client'
export default function ClientComponent({ children }) {
const [count, setCount] = useState(0)
return (
<>
<button onClick={() => setCount(count + 1)}>{count}</button>
{children}
</>
)
}
// In page.tsx (a Server Component)
import ClientComponent from './client-component'
import ServerComponent from './server-component'
export default function Page() {
return (
<ClientComponent>
<ServerComponent />
</ClientComponent>
)
}
This approach allows both components to render independently → the Server Component renders on the server before the Client Component renders on the client.
You're not limited to just the children prop. You can use any prop to pass Server Component content to a Client Component.