reactjsnext.jsoptimizationserver-side-renderingclient-side-rendering

Is it unnecessary to render static content in a server component and then pass it to a client component via a prop in NextJS


I am just wondering if the first of the two examples is redundant since according to the NextJS docs,

Client Components allow you to write interactive UI that is prerendered on the server and can use client JavaScript to run in the browser.

I am not 100% sure if this implies that NextJS will look through your client components and selectively render the static content server side or not. Sorry if this is a silly question, thank you for your help!

Example 1: Possibly Redundant

// ServerComponent.jsx
export default function ServerComponent() {
    return (
        <ClientComponent
            RenderedOnServer={<p>Rendered on the Server 100%</p>}
        />
    );
}

// ClientComponent.jsx
"use client";

import { useState } from "react";

export default function ClientComponent({ RenderedOnServer }) {
    const [counter, setCounter] = useState(0);
    return (
        <div>
            {RenderedOnServer}
            <p>Count: {counter}</p>
            <button onClick={() => setCounter(counter + 1)}>Click Me</button>
        </div>
    );
}

Example 2: Possibly Less Performant (Less Rendering Occurring Server Side)

// ClientComponent.jsx
"use client";

import { useState } from "react";

export default function ClientComponent({ RenderedOnServer }) {
    const [counter, setCounter] = useState(0);
    return (
        <div>
            <p>
                Maybe rendered on the Server, not 100% if it has the same
                performance
            </p>
            <p>Count: {counter}</p>
            <button onClick={() => setCounter(counter + 1)}>Click Me</button>
        </div>
    );
}

Wasn't able to find too much information on this online, just hoping to clarify my misconceptions with NextJS rendering patterns.


Solution

  • I think there are several misconceptions:

    The 'use client' directive

    This directive won't prevent a component to be rendered on server side by Next.js. It just prevents a component to be a rendered as React Server Component (RSC).

    Don't confuse Next.js "server side rendering" with RSCs. Yes, RSCs will be rendered on server side, but so are all other components. The biggest difference is that RSCs won't be rendered again on the client, and won't need to be hydrated either (see below). And RSCs can be async, which allows some straight data fetching on the server side etc.

    RSCs are incompatible with much stuff, most prominently with hooks or JavaScript functions in the HTML (e.g. event handlers). A RSC with a hook or some JavaScript logic will lead to an error. This is where use client comes into play, telling Next.js: "Well, starting from here, this is not a RSC anymore. Don't render it as one, so these errors won't occur."

    Again, all other components will be rendered on the server side first as well - just not as RSCs!

    Initial rendering on client side

    After a component was rendered on server side and passed to the client, it won't be rerendered there initially. Instead, it will be taken as it is and get hydrated with interaction logic/JavaScript. So for a moment you'll see buttons but clicking them won't have any effect (b/c not event handler was attached to them yet). Normally, the hydration should be fast enough you won't notice. Then, hooks (resp. their callbacks) might be invoked on the client side, triggering a rerendering. But until this is eventually done, the user got an initial component to look at.

    This non-RSC server side rendering of Next.js is an attempt to carry over the advantages of RSCs to other components. And yes, there are possible performance gains from it, as described in the link.

    Given, hook callbacks won't be invoked and some stuff might be missing after the server side rendering of a non-RSC component, but it's better than nothing!

    Enforcing some logic to be only run at the client side

    As just explained, this is not achieved by 'use client'. Instead, what you do is adding some logic as

    export const useClientSide = () => {
      const [onClientSide, setOnClientSide] = useState(false);
      useEffect(() => setOnClientSide(true), []); // won't be invoked on server side, so it will be always false there
      return onClientSide;
    };
    ...
    const MyComponent: React.FC = () => {
      const onClientSide = useClientSide();
      if (!onClientSide) return null; // will return here on server side and even initally on client side. But soon after, the useEffect callback in useClientSide will be invoked, setting onClientSide true
      ... // client side logic to be run on first client side rerendering
    }
    

    Static content

    Server side-rendered content isn't required to be static. Not even RSCs are required to be static. When building with Next.js, it might attempt to build pages statically (those which don't ever change, e.g. by default the 404 page) and supports static assets. Of your components, none will be rendered statically - they'll vary by each request made.

    Regarding the initial question

    Server side rendering might come with redundance in the meaning of: the component might be rendered more often. But this doesn't mean a worse performance (quite the opposite) and comes with other advantages - which aren't all restricted to RSCs, but mostly apply to non-RSC server side rendering as well.