Ben
Ben

Reputation: 21615

How does the "use client" directive work in Next.js 13

I'm trying to wrap my head around server side rendering with Next.js 13.4 and the new app/ directory. To my understanding, every component is a server component (i.e. rendered server-side) by default.

However, we can use the 'use client' directive to forcibly turn a component into a client component.

Below, I set up a simple "Hello World" component, first as a server component and then as a client component. In each case, I compare the page source code.

src/app/page.js (server component)

export default function Home() {
  return (
    <main>
      <h1>Hello World</h1>
    </main>
  )
}

Chrome > view page source

<!DOCTYPE html>
<html lang="en">

<head>
  <meta charSet="utf-8" />
  <title>Create Next App</title>
  <meta name="description" content="Generated by create next app" />
  <meta name="viewport" content="width=device-width, initial-scale=1" />
  <script src="/_next/static/chunks/polyfills.js" noModule=""></script>
</head>

<body>
  <main>
    <h1>Hello World</h1>
  </main>
  <script src="/_next/static/chunks/webpack.js" async=""></script>
  <script src="/_next/static/chunks/main-app.js" async=""></script>
  <script>(self.__next_f = self.__next_f || []).push([0])</script>
  <script>self.__next_f.push([1, "0:\"$L1\"\n"])</script>
  <script>self.__next_f.push([1, "2:I{\"id\":\"(app-client)/./node_modules/next/dist/client/components/app-router.js\",\"chunks\":[\"webpack:static/chunks/webpack.js\"],\"name\":\"\",\"async\":false}\n4:I{\"id\":\"(app-client)/./node_modules/next/dist/client/components/error-boundary.js\",\"chunks\":[\"webpack:static/chunks/webpack.js\"],\"name\":\"\",\"async\":false}\n6:I{\"id\":\"(app-client)/./node_modules/next/dist/client/components/layout-router.js\",\"chunks\":[\"app-client-internals:static/chunks/app-client-internals.js\"],\"name\":\"\",\"async\":false}\n7:I{\"id\":\"(app-client)/"])</script>
  <script>self.__next_f.push([1, "./node_modules/next/dist/client/components/render-from-template-context.js\",\"chunks\":[\"app-client-internals:static/chunks/app-client-internals.js\"],\"name\":\"\",\"async\":false}\n"])</script>
  <script>self.__next_f.push([1, "1:[[],[\"$\",\"$L2\",null,{\"assetPrefix\":\"\",\"initialCanonicalUrl\":\"/\",\"initialTree\":[\"\",{\"children\":[\"__PAGE__\",{}]},\"$undefined\",\"$undefined\",true],\"initialHead\":[\"$L3\",null],\"globalErrorComponent\":\"$4\",\"notFound\":[\"$\",\"html\",null,{\"lang\":\"en\",\"children\":[\"$\",\"body\",null,{\"children\":[\"$L5\",\"$undefined\",[[\"$\",\"title\",null,{\"children\":\"404: This page could not be found.\"}],[\"$\",\"div\",null,{\"style\":{\"fontFamily\":\"system-ui,\\\"Segoe UI\\\",Roboto,Helvetica,Arial,sans-serif,\\\"Apple Color Emoji\\\",\\\"Segoe UI Emoji\\\"\",\"height\":\"100vh\",\"textAlign\":\"center\",\"display\":\"flex\",\"flexDirection\":\"column\",\"alignItems\":\"center\",\"justifyContent\":\"center\"},\"children\":[\"$\",\"div\",null,{\"children\":[[\"$\",\"style\",null,{\"dangerouslySetInnerHTML\":{\"__html\":\"body{color:#000;background:#fff;margin:0}.next-error-h1{border-right:1px solid rgba(0,0,0,.3)}@media (prefers-color-scheme:dark){body{color:#fff;background:#000}.next-error-h1{border-right:1px solid rgba(255,255,255,.3)}}\"}}],[\"$\",\"h1\",null,{\"className\":\"next-error-h1\",\"style\":{\"display\":\"inline-block\",\"margin\":\"0 20px 0 0\",\"padding\":\"0 23px 0 0\",\"fontSize\":24,\"fontWeight\":500,\"verticalAlign\":\"top\",\"lineHeight\":\"49px\"},\"children\":\"404\"}],[\"$\",\"div\",null,{\"style\":{\"display\":\"inline-block\"},\"children\":[\"$\",\"h2\",null,{\"style\":{\"fontSize\":14,\"fontWeight\":400,\"lineHeight\":\"49px\",\"margin\":0},\"children\":\"This page could not be found.\"}]}]]}]}]]]}]}],\"asNotFound\":false,\"children\":[[\"$\",\"html\",null,{\"lang\":\"en\",\"children\":[\"$\",\"body\",null,{\"children\":[\"$\",\"$L6\",null,{\"parallelRouterKey\":\"children\",\"segmentPath\":[\"children\"],\"error\":\"$undefined\",\"errorStyles\":\"$undefined\",\"loading\":\"$undefined\",\"loadingStyles\":\"$undefined\",\"hasLoading\":false,\"template\":[\"$\",\"$L7\",null,{}],\"templateStyles\":\"$undefined\",\"notFound\":\"$undefined\",\"notFoundStyles\":\"$undefined\",\"childProp\":{\"current\":[[\"$\",\"main\",null,{\"children\":[\"$\",\"h1\",null,{\"children\":\"Hello World\"}]}],null],\"segment\":\"__PAGE__\"},\"styles\":[]}]}]}],null]}]]\n"])</script>
  <script>self.__next_f.push([1, "5:[[[\"$\",\"meta\",null,{\"charSet\":\"utf-8\"}],null,null,null,null,null,null,null,null,null,null,[\"$\",\"meta\",null,{\"name\":\"viewport\",\"content\":\"width=device-width, initial-scale=1\"}],null,null,null,null,null,null,null,null,null,null,[]],[null,null,null,null],null,null,[null,null,null,null,null],null,null,null,null,null]\n3:[[[\"$\",\"meta\",null,{\"charSet\":\"utf-8\"}],[\"$\",\"title\",null,{\"children\":\"Create Next App\"}],[\"$\",\"meta\",null,{\"name\":\"description\",\"content\":\"Generated by create next app\"}],null,null,null,null,n"])</script>
  <script>self.__next_f.push([1, "ull,null,null,null,[\"$\",\"meta\",null,{\"name\":\"viewport\",\"content\":\"width=device-width, initial-scale=1\"}],null,null,null,null,null,null,null,null,null,null,[]],[null,null,null,null],null,null,[null,null,null,null,null],null,null,null,null,null]\n"])</script>
</body>

</html>

src/app/page.js (client component)

'use client';

export default function Home() {
  return (
    <main>
      <h1>Hello World</h1>
    </main>
  )
}

Chrome > view page source

<!DOCTYPE html>
<html lang="en">

<head>
  <meta charSet="utf-8" />
  <title>Create Next App</title>
  <meta name="description" content="Generated by create next app" />
  <meta name="viewport" content="width=device-width, initial-scale=1" />
  <script src="/_next/static/chunks/polyfills.js" noModule=""></script>
</head>

<body>
  <main>
    <h1>Hello World</h1>
  </main>
  <script src="/_next/static/chunks/webpack.js" async=""></script>
  <script src="/_next/static/chunks/main-app.js" async=""></script>
  <script>(self.__next_f = self.__next_f || []).push([0])</script>
  <script>self.__next_f.push([1, "0:\"$L1\"\n"])</script>
  <script>self.__next_f.push([1, "2:I{\"id\":\"(app-client)/./node_modules/next/dist/client/components/app-router.js\",\"chunks\":[\"webpack:static/chunks/webpack.js\"],\"name\":\"\",\"async\":false}\n4:I{\"id\":\"(app-client)/./node_modules/next/dist/client/components/error-boundary.js\",\"chunks\":[\"webpack:static/chunks/webpack.js\"],\"name\":\"\",\"async\":false}\n6:I{\"id\":\"(app-client)/./node_modules/next/dist/client/components/layout-router.js\",\"chunks\":[\"app-client-internals:static/chunks/app-client-internals.js\"],\"name\":\"\",\"async\":false}\n7:I{\"id\":\"(app-client)/"])</script>
  <script>self.__next_f.push([1, "./node_modules/next/dist/client/components/render-from-template-context.js\",\"chunks\":[\"app-client-internals:static/chunks/app-client-internals.js\"],\"name\":\"\",\"async\":false}\n8:I{\"id\":\"(app-client)/./src/app/page.js\",\"chunks\":[\"app/page:static/chunks/app/page.js\"],\"name\":\"\",\"async\":false}\n"])</script>
  <script>self.__next_f.push([1, "1:[[],[\"$\",\"$L2\",null,{\"assetPrefix\":\"\",\"initialCanonicalUrl\":\"/\",\"initialTree\":[\"\",{\"children\":[\"__PAGE__\",{}]},\"$undefined\",\"$undefined\",true],\"initialHead\":[\"$L3\",null],\"globalErrorComponent\":\"$4\",\"notFound\":[\"$\",\"html\",null,{\"lang\":\"en\",\"children\":[\"$\",\"body\",null,{\"children\":[\"$L5\",\"$undefined\",[[\"$\",\"title\",null,{\"children\":\"404: This page could not be found.\"}],[\"$\",\"div\",null,{\"style\":{\"fontFamily\":\"system-ui,\\\"Segoe UI\\\",Roboto,Helvetica,Arial,sans-serif,\\\"Apple Color Emoji\\\",\\\"Segoe UI Emoji\\\"\",\"height\":\"100vh\",\"textAlign\":\"center\",\"display\":\"flex\",\"flexDirection\":\"column\",\"alignItems\":\"center\",\"justifyContent\":\"center\"},\"children\":[\"$\",\"div\",null,{\"children\":[[\"$\",\"style\",null,{\"dangerouslySetInnerHTML\":{\"__html\":\"body{color:#000;background:#fff;margin:0}.next-error-h1{border-right:1px solid rgba(0,0,0,.3)}@media (prefers-color-scheme:dark){body{color:#fff;background:#000}.next-error-h1{border-right:1px solid rgba(255,255,255,.3)}}\"}}],[\"$\",\"h1\",null,{\"className\":\"next-error-h1\",\"style\":{\"display\":\"inline-block\",\"margin\":\"0 20px 0 0\",\"padding\":\"0 23px 0 0\",\"fontSize\":24,\"fontWeight\":500,\"verticalAlign\":\"top\",\"lineHeight\":\"49px\"},\"children\":\"404\"}],[\"$\",\"div\",null,{\"style\":{\"display\":\"inline-block\"},\"children\":[\"$\",\"h2\",null,{\"style\":{\"fontSize\":14,\"fontWeight\":400,\"lineHeight\":\"49px\",\"margin\":0},\"children\":\"This page could not be found.\"}]}]]}]}]]]}]}],\"asNotFound\":false,\"children\":[[\"$\",\"html\",null,{\"lang\":\"en\",\"children\":[\"$\",\"body\",null,{\"children\":[\"$\",\"$L6\",null,{\"parallelRouterKey\":\"children\",\"segmentPath\":[\"children\"],\"error\":\"$undefined\",\"errorStyles\":\"$undefined\",\"loading\":\"$undefined\",\"loadingStyles\":\"$undefined\",\"hasLoading\":false,\"template\":[\"$\",\"$L7\",null,{}],\"templateStyles\":\"$undefined\",\"notFound\":\"$undefined\",\"notFoundStyles\":\"$undefined\",\"childProp\":{\"current\":[[\"$\",\"$L8\",null,{\"params\":{},\"searchParams\":{}}],null],\"segment\":\"__PAGE__\"},\"styles\":[]}]}]}],null]}]]\n"])</script>
  <script>self.__next_f.push([1, "5:[[[\"$\",\"meta\",null,{\"charSet\":\"utf-8\"}],null,null,null,null,null,null,null,null,null,null,[\"$\",\"meta\",null,{\"name\":\"viewport\",\"content\":\"width=device-width, initial-scale=1\"}],null,null,null,null,null,null,null,null,null,null,[]],[null,null,null,null],null,null,[null,null,null,null,null],null,null,null,null,null]\n3:[[[\"$\",\"meta\",null,{\"charSet\":\"utf-8\"}],[\"$\",\"title\",null,{\"children\":\"Create Next App\"}],[\"$\",\"meta\",null,{\"name\":\"description\",\"content\":\"Generated by create next app\"}],null,null,null,null,n"])</script>
  <script>self.__next_f.push([1, "ull,null,null,null,[\"$\",\"meta\",null,{\"name\":\"viewport\",\"content\":\"width=device-width, initial-scale=1\"}],null,null,null,null,null,null,null,null,null,null,[]],[null,null,null,null],null,null,[null,null,null,null,null],null,null,null,null,null]\n"])</script>
</body>

</html>

I'm confused because it appears as though the client component has its HTML pre-rendered, but "Hello World" clearly exists in the source code. I expected to see something akin to React's root DOM node - basically an empty div waiting on some HTML to be injected. What am I missing here?

Upvotes: 5

Views: 12343

Answers (3)

fp_Dev
fp_Dev

Reputation: 11

From here

Server and Client Components are rendered differently during Static Rendering:

  • Client Components have their HTML and JSON prerendered and cached on the server. The cached result is then sent to the client for hydration.
  • Server Components are rendered on the server by React, and their payload is used to generate HTML. The same rendered payload is also used to hydrate the components on the client, resulting in no JavaScript needed on the client.

Now, with Server and Client Components, React can render on the client and the server meaning you can choose the rendering environment at the component level.

By default, the app router uses Server Components, allowing you to easily render components on the server and reducing the amount of JavaScript sent to the client.

Upvotes: 1

Karson Jo
Karson Jo

Reputation: 1702

When client/server components are rendered

Client components

Client components allow the use of browser APIs and are mainly used to handle user interactions. However, NextJs has server-side rendering (SSR).

Therefore:

  1. pre-rendered on server when first loaded
  2. re-rendered in the browser during user interaction

Server components

Server components are used to handle logic that does not require client involvement (data fetching, non-interactive UI, etc.). Since there is no client-side involvement, there is no need to send these JavaScript packages to the client.

⚠️But be careful: if you import a server component directly in a client component, it will become a client component as well.

But anyway, for normal use of server components:

  1. should only be rendered server-side

pre-rendering behavior

So in NextJs both client and server components are pre-rendered on the server, which improves SEO and is one of the original purposes of NextJs.

If you want to see "blank" in the source code, you need to use the "client-only" component:

  • Use useState to return empty for the first render, and useEffect to change the state to trigger a rerender at the right time.
  • Or use 'next/dynamic'.

But of course you don't want to do that in most cases. ;-)

Upvotes: 0

Yilmaz
Yilmaz

Reputation: 49182

From here

Client Components

Client Components enable you to add client-side interactivity to your application. In Next.js, they are pre-rendered on the server and hydrated on the client. You can think of Client Components as how components in the Pages Router have always worked.

Upvotes: 0

Related Questions