Do Client Components in Next.js Really Run Only on the Client?
After you complete this article, you will have a solid understanding of:
- Where client components are actually rendered.
- Why client components aren't just rendered on the client, and what the benefits are.
- What hydration is, what a hydration error is, and how to fix one.
There are two types of components in Next.js
- Server Components
- Client Components
Server components are relatively easy to understand (okay, maybe not). They are rendered only on the server and sent to the client as a static HTML file.
But what happens with client components? Do they only render on the client? Why do we even need client components? How are they useful?
I know you have many questions in your mind, but let's take it step by step.
Have You Ever Encountered a Hydration Error?
If you're lucky and have never seen one, let's take a look at what a hydration error looks like. This error will actually help you understand some concepts even more clearly.
Try running this code in your editor:
"use client";
const MyComponent = () => {
const currentTime = new Date().getTime();
return <div>{currentTime}</div>;
};
And, you will see;
"Server-rendered HTML didn't match the client."
Wait, what? Why was the HTML rendered on the server even though it was a client component? Isn't it supposed to be rendered on the client? How is this different from server components if everything is rendered on the server?
Let's start by understanding what hydration actually is.
What is Hydration?
Hydration → The process of taking pre-rendered HTML (usually generated on the server) and enhancing it with client-side JavaScript to enable interactivity.
So, hydration gives our HTML a superpower. Thanks to the hydration process, we're not just reading a plain HTML document coming from the server, but we're also able to interact with it.
When we create a client component in Next.js, first, a pre-rendered HTML is created for that component on the server side—but nothing else.
No JavaScript code, no React useState
calls, no useEffect
calls. It's just the HTML code.
After we receive that HTML on the client side, we are ready to start the hydration process. At this point, we get our useState
calls, useEffect
, or any other interactivity used in our component.
Okay. But can we render everything on the client? I don't want to get involved with hydration or any other stuff!
Of course, we can. But there are downsides.
Benefits of Server-Side Rendering Before Hydration
1.Faster Initial Load
If all Client Components ran only on the client, you would see a blank screen while waiting for JavaScript to load and execute.
2.Better SEO
A fully client-rendered page can cause SEO issues, as search engines may struggle to index content properly.
3.Network Optimization
Server-rendered HTML has a smaller bundle size and better caching opportunities at the CDN level.
Now we know why rendering the HTML part on the server and sending the JavaScript code afterward (a.k.a hydration) is a better approach in most cases.
Common Causes of Hydration Errors
"use client";
const MyComponent = () => {
return (
<p>
<div></div>
</p>
);
};
In a case like this, the browser might try to fix the incorrectly placed elements. If that happens, the final HTML won't match React's Virtual DOM, leading to a hydration error.
Another common cause is trying to access the window
object inside a client component. Since window
doesn't exist on the server, it causes hydration issues.
"use client";
const MyComponent = () => {
// This will cause a hydration error
const screenWidth = window.innerWidth;
return <div>Your screen width is: {screenWidth}px</div>;
};
In this example, trying to access window.innerWidth
during server rendering will fail because the window
object doesn't exist on the server. This causes the component to render differently on the server versus the client.
"use client";
const MyComponent = () => {
// This will cause a hydration error
const currentTime = new Date().getTime();
return <div>{currentTime}</div>;
};
The issue here is that new Date().getTime()
will return different values on the server and client, causing a mismatch between server-rendered HTML and client-side rendering.
"use client";
const MyComponent = () => {
// This will cause a hydration error
const randomValue = Math.random();
return <div>Random value: {randomValue}</div>;
};
Using Math.random()
generates different values on the server and client, leading to a hydration error since the server-rendered content doesn't match what's generated on the client.
"use client";
const MyComponent = () => {
// This will cause a hydration error
useEffect(() => {
// This runs after initial render
document.getElementById("my-element").innerHTML = "Changed content";
}, []);
return <div id="my-element">Initial content</div>;
};
Direct DOM manipulation inside a useEffect
can cause hydration errors if it runs too early and changes the DOM before React has finished hydrating the component.
Opting Out of Server-Side Rendering
What if we want to render a component only on the client? Is there a way to do that?
There is always a way for everything!
import dynamic from "next/dynamic";
const MyComponent = dynamic(() => import("../components/MyComponent"), {
ssr: false,
});
export default function Page() {
return <MyComponent />;
}
This way, MyComponent
will only render on the client, and Next.js won't attempt to pre-render it on the server.
Now you fully understand how Client Components work in Next.js!
Was this blog helpful for you? If so,