Next.js Discord

Discord Forum

Hydration of Server vs Client Components

Answered
NicraM posted this in #help-forum
Open in Discord
Avatar
Hi, I'm trying to understand the difference in rendering and hydration times between server components and components marked with "use client".
I'm using Next.js 15 with the app directory.

Let’s assume I have a page that renders 50,000 elements:
export default function Home() {
  const list = Array.from({ length: 50000 }, (_, index) => index + 1);
  return (
    <div className="App">
      {list.map((x) => (
        <div className="element" key={x}>
          {x}
        </div>
      ))}
    </div>
  );
}

When analyzing the performance of this application using Lighthouse, I consistently get similar results:

FCP: 0.6 [s]
LCP: 0.7 [s]
TBT: ~750 [ms]
When I simply add "use client" to the component, the performance metrics improve significantly:

FCP: 0.4 [s]
LCP: 0.5 [s]
TBT: ~500 [ms]
No matter how many times I repeat the tests, the client component ("use client") consistently loads faster.
It’s worth noting that, regardless of "use client", the page is statically pre-rendered with the full list of 50,000 elements.

Where could this difference come from?
Does hydration differ between server and client components?
Do you have any ideas about what could affect this result?
Answered by NicraM
For posterity, in case anyone finds this interesting:

It seems I’ve identified the cause of such a result.
Namely, the difference lies in the size of the prerendered HTML file, which affects the loading and parsing time of that HTML.
In the case of the client-side component, the HTML file contains the entire list of 50,000 elements and weighs 136 kB (1.6 MB after decompression).
Additionally, a JS file associated with this page is sent along with the HTML to hydrate the client-side component.
This file, for such a simple component, weighs 931 bytes (584 bytes after decompression).

In the case of the server-side component, the HTML file contains not only the list of elements but also information about "what was rendered on the server so that React doesn’t have to hydrate those elements."
Thanks to this, we no longer need to fetch the JS file that was necessary for the client-side component.
But as a result, the HTML file weighs 399 kB and 5.2 MB after decompression.
View full answer

3 Replies

Avatar
@NicraM curious about this.. so can you rerun your tests but this time in build? npm run build and the npm run start


Ping me when you get the results
Avatar
So, after running the project at build time, FCP and LCP remain unchanged. However, TBT is on average 200 [ms] lower in both cases, still leaving a significant difference. Now we have ~300 [ms] for the client component and ~500 [ms] for the server component.
No matter how many times I perform the analysis, the results remain consistent.

@Arinji
Avatar
For posterity, in case anyone finds this interesting:

It seems I’ve identified the cause of such a result.
Namely, the difference lies in the size of the prerendered HTML file, which affects the loading and parsing time of that HTML.
In the case of the client-side component, the HTML file contains the entire list of 50,000 elements and weighs 136 kB (1.6 MB after decompression).
Additionally, a JS file associated with this page is sent along with the HTML to hydrate the client-side component.
This file, for such a simple component, weighs 931 bytes (584 bytes after decompression).

In the case of the server-side component, the HTML file contains not only the list of elements but also information about "what was rendered on the server so that React doesn’t have to hydrate those elements."
Thanks to this, we no longer need to fetch the JS file that was necessary for the client-side component.
But as a result, the HTML file weighs 399 kB and 5.2 MB after decompression.
Answer