0

When we try to use a ThemeProvider(as shown below) in NextJS, we need to declare this Provider as a client component. But in NextJS docs it is said that if we declare a component as a client component all other modules imported into it, including child components, are considered part of the client bundle - and will be rendered by React on the client.

Since our whole application component live inside the provider, does it mean all of the child component would behave as client comps. If that's the case, doesn't it completely eliminate SSR using NextJS? I'm curious about this interaction and how it aligns with the SSR and CSR principles that Next.js is built upon.

//layout.tsx
import { Provider } from './provider'
import '@/styles//globals.css'
import type { Metadata } from 'next'
import { Inter } from 'next/font/google'

const inter = Inter({ subsets: ['latin'] })

export const metadata: Metadata = {
  title: 'Create Next App',
  description: 'Generated by create next app',
}

export default function RootLayout({
  children,
}: {
  children: React.ReactNode
}) {
  return (
    <html lang="en">
      <body className={inter.className}>
        <Provider attribute="class" defaultTheme="dark">
          {children}
        </Provider>
      </body>
    </html>
  )
}

//provider.tsx
'use client'

import { ThemeProvider as NextThemesProvider } from "next-themes"
import { type ThemeProviderProps } from "next-themes/dist/types"
import { NextUIProvider } from "@nextui-org/react"
 
export function Provider({ children, ...props }: ThemeProviderProps) {
  return (
    <NextUIProvider>
      <NextThemesProvider {...props}>
        {children}
      </NextThemesProvider>
    </NextUIProvider>
  )
}

I am asking this question as a newbie in NextJS, so please excuse any technical inaccuracies or lack of experience in my query.

  • https://stackoverflow.com/questions/74992326/does-use-client-in-next-js-13-root-layout-make-whole-routes-client-component/75480826#75480826 – Yilmaz Aug 20 '23 at 14:34
  • @Yimaz It cleared lot of my doubt. But based on your answer in the above thread doesn't it make the highlighted statement from docs in my question wrong? Since it is specifically saying that even child components are considered as part of client bundle. Or does this child components has different connotation than the children prop? – DrusePstrago Aug 20 '23 at 15:31
  • And in your answer in the above thread it doesn't consider the case when the children is wrapped by provider that is client component. I hope you can update the answer if you have any idea on how is nextjs handling it internally – DrusePstrago Aug 20 '23 at 15:45
  • docs say child components. but we are passing server component as `children prop` – Yilmaz Aug 20 '23 at 15:47
  • Hi Yilmaz! I just found out that client comps can even accept server component as child component as well as children prop. I have attached the link to their updated doc in my answer where they have explained very clearly how both client and server comps work together. – DrusePstrago Aug 21 '23 at 12:40

2 Answers2

0

Your assumption that we need to use client component for creating a context is correct.

So to create a Provider, we need to make it a client component like:

"use client"

import React, {createContext} from "react";

const ThemeContext = createContext({})

export default function ThemeProvider({children}){
  return (
    <ThemeContext.Provider value="dark">
      {children}
    </ThemeContext.Provider>
  )
}

But you can import this client component inside a server component without any issue, like:

import ThemeProvider from './providers';

export default function RootLayout({
  children,
}: {
  children: React.ReactNode;
}) {
  return (
    <html>
      <body>
        <ThemeProvider>{children}</ThemeProvider>
      </body>
    </html>
  );
}

Without the use client, this component is treated as a server component.


On a side note, server components are new. So if you are trying to import a provider from a third party library, chances are, they are not using "use client" yet.

If that's the case, if you try to import that provider into your server component, that shall throw error: Error: "createContext" can't be used in Server Components.

To solve this, you need to wrap the third-party provider inside a client component, before using it in a server component.

"use client"

import React from "react";
import { ThemeProvider } from 'theme-package';
import { ThirdPartyProvider } from 'third-party-package';

export default function MyAppProvider({children}){
  return (
    <ThemeProvider>
      <ThirdPartyProvider>
        {children}
      </ThirdPartyProvider>
    </ThemeProvider>
  )
}

This MyAppProvider will be rendered only on the client (meaning, only in client bundle), even thought it has ThirdPartyProvider (which is a server component).

Once "use client" is defined in a file, all other modules imported into it, including child components, are considered part of the client bundle.

https://nextjs.org/docs/getting-started/react-essentials#the-use-client-directive

If you import a server component into a client component, that server component will be rendered as a client component.

Robin Thomas
  • 1,317
  • 8
  • 15
  • Thanks for the response Robin. I understand that by default the components are server rendered and we can use client comps inside of server comps. But issues is that highlighted statement in my question that is mentioned in the documentation where it mentions that "child components are considered part of client bundle". – DrusePstrago Aug 20 '23 at 14:42
  • @DrusePstrago If its fine, can you upvote my answer as well? Thanks :) – Robin Thomas Aug 26 '23 at 22:26
0

In the updated doc itself it has been clarified later on in the composition pattern

In summary:

If you need to nest Server Components within Client Components, you can pass Server Components as props to Client Components. A common pattern is to use the React children prop to create a "slot" in your Client Component.

When passing a child component or prop to the client component, the Client Component has no knowledge of what the child is. Its responsibility is solely to determine where the child component will be placed. As a result, the client component can effectively wrap both client & server component or accept them as a prop.

// This pattern works:
// You can pass a Server Component as a child or prop of a
// Client Component.
import ClientComponent from './client-component'
import ServerComponent from './server-component'

// Pages in Next.js are Server Components by default
export default function Page() {
  return (
    <ClientComponent>
      <ServerComponent />
    </ClientComponent>
  )
}

Only restriction while using both Client and Server component is that we cannot **import** the server component inside of client component, so this code will not work

'use client'

// You cannot import a Server Component into a Client Component.
import ServerComponent from './Server-Component'

export default function ClientComponent({ children }) {
  const [count, setCount] = useState(0)

  return (
    <>
      <button onClick={() => setCount(count + 1)}>{count}</button>

      <ServerComponent />
    </>
  )
}