/layout.js @ 1688734135484 [Home]

Client Component Details

Loading Slow Client Component...

Above is a variation of our Client Component with:

These are significant changes and allow us to see more details about how Client Components really work. Let's dig in...

Here are the relevant lines from the React Virtual DOM:

(Because this is all dynamic, I cannot reference exact line labels or numbers, since they may change depending on the run-time environment and other factors. I will reference things generally.)

The meaning of these 4 lines is:

Client Component

Let's look at the client component source:

'use client'
function pause(t) {
  return new Promise(r=>setTimeout(r,t))
export const ClientComponent =  async() => {
  await pause(3000);
  return <p className={"client-component"}>
    This content is generated client-side. Datestamp: {Date.now()}

Simple enough. It should be obvious what the intent is.

NoSSR Wrapper

In order to use this Client Component and prevent it from running on the server, we wrap it in a wrapper RSC Component:

import dynamic from 'next/dynamic'
const ClientComponent = dynamic(() => import('./ClientComponent').then(m=>({"default":m.ClientComponent})), {
  ssr: false,
  loading: () => <div className={"client-component"}>Loading Slow Client Component...</div>
export const ClientComponentNoSSR = ()=><ClientComponent/>

Here you see the "fallback" defined as part of the call to next/dynamic. And this component simply returns the rendered results of the async Client Component.

(Note: The .then() and "default" syntax are hacks needed for next/dynamic to import a named export rather than the default, which React.lazy() expects)

Let's Build A Generic NoSSR Wrapper Component!


You might be tempted to write a reusable RSC function that does the next/dynamic and ssr:false magic for you in a reusable way, so you can hide it away. Like:

const ClientComponent = NoSSRImport('@/components/ClientComponent');

That could internally use next/dynamic and pass the string argument into import(), right?

Unfortunately, no. import() is magically converted to other syntax at build time, and requires a static string argument. It cannot be dynamic, so you can't pass it a variable. This is just the black box magic that happens in compilers/bundlers that is invisible to you and causes confusing error messages.

Client Component FAQs

Can I import() an RSC into a Client Component?
No. If a Client Component imports another Component, it is treated as a Client Component. This is because the Client Component runs in the browser. It can't call Server Component code on the fly while it's trying to render itself in the browser! Once a 'use client' directive is found in a Component, every one of its imported Components and all their imported Components will all be treated as Client Components. This is necessary because they rely on each other to render at run-time in the browser. I hope that makes sense. This is important. More later.
Can I import() a Client Component into an RSC?
Yes. As previously covered, that just tells the bundler to bundle the Client Component source separately and send it to the browser for execution.
Can an Server Component be a child of a Client Component?
Yes. We will explore this next. The reason is because the RSC's output can be treated as static input to the {children} prop of the Client Component. It doesn't know or care whether its children content was static, rendered on the server, or rendered on the client.
Can a Server Component's output be passed as a prop to a Client Component?
Yes, for the same reason.
Can I pass a Server-Side Function as a prop to a Client Component?
No. Anything passed as props to a Client Component must be serializable, so it can be sent as static content. Functions cannot be serialized. But there's a catch: Server Functions. This is alpha pre-release functionality and will be covered later.
Can a Client Component be a child of a Server Component?
Yes! We have already demonstrated it on this page!

Server Components as Children of Client Components