Composition Tips

A Next.js developer must decide on how to mix server components and client components in an application. This section provides a few guiding tips on this aspect.

Tip 1: To share data between components, use cached fetch().

Rather than relying on Redux / React Context (which isn't accessible on the server) or passing data down through props, you can leverage fetch() or React's cache() to retrieve data directly in the components that need it. React enhances fetch() by automatically caching requests, and when fetch isn't suitable, you can use cache() to achieve similar results.


Tip 2: To keep code out of client components, use the 'server-only' Node package.

Code that was intended to run exclusively on the server can sometimes unintentionally end up being executed on the client.

You can use the 'server-only' package to keep code on the server.
npm install server-only

import 'server-only'
 
export async function getData() {
  const res = await fetch('https://external-service.com/data', {
    headers: {
      authorization: process.env.API_KEY,
    },
  })
 
  return res.json()
}

Now, any Client Component that tries to import getData() will trigger a build-time error, indicating that this module is restricted to server-side use only.


Tip 3: To use third-party packages on client components, wrap the packages in client components.

Currently, many components from npm packages that rely on client-side features still do not have the 'use client' directive.

To resolve this, you can create your own Client Components to wrap third-party components that depend on client-side features.
carousel.tsx:
'use client'
 
import { Carousel } from 'acme-carousel'
 
export default Carousel

page.tsx:
import Carousel from './carousel'
 
export default function Page() {
  return (
    <div>
      <p>View pictures</p>
 
      {/*  Works, since Carousel is a Client Component */}
      <Carousel />
    </div>
  )
}
Since React context isn't supported in Server Components, attempting to create a context at the root of your application will result in an error. To resolve this issue, create your context and place its provider inside a Client Component. This way, your Server Component can render the provider, since it has been designated as a Client Component. You should render providers as deep as possible in the tree. This makes it easier for Next.js to optimize the static parts of your Server Components.
theme-provider.tsx:
'use client'
 
import { createContext } from 'react'
 
export const ThemeContext = createContext({})
 
export default function ThemeProvider({
  children,
}: {
  children: React.ReactNode
}) {
  return <ThemeContext.Provider value="dark">{children}</ThemeContext.Provider>
}

layout.tsx:
import ThemeProvider from './theme-provider'
 
export default function RootLayout({
  children,
}: {
  children: React.ReactNode
}) {
  return (
    <html>
      <body>
        <ThemeProvider>{children}</ThemeProvider>
      </body>
    </html>
  )
}

Tip 4: To reduce the size of the transmitted bundle, move client components down the component tree.

Instead of turning the entire layout into a Client Component, move the interactive functionality to a separate Client Component and keep the layout as a Server Component. This way, you avoid sending the full layout's JavaScript to the client, reducing the client-side load.


Tip 5: To pass props from server to client components, serialize prop data, fetch on client, or use a route handler.

When fetching data in a server component, you might need to pass that data as props to client components. Any props passed from server to client components must be serializable by React. If your client components rely on data that can't be serialized, you can either fetch the data on the client using a third-party library or on the server using a route Handler.


Tip 6: To use server components in client components, pass server components to client components as props.

Within client subtrees, you can still nest Server Components or invoke Server Actions, but there are a few key points to consider: During a request-response cycle, the execution moves from the server to the client. If you need server-side data or resources while on the client, this will result in a new request to the server—there’s no switching back and forth between client and server. When a new request is made to the server, all Server Components are rendered first, even those nested inside Client Components. The rendered output (RSC Payload) contains references to the Client Components' locations. React then uses this payload to merge Server and Client Components into a unified component tree on the client. Since Server Components render before Client Components, you can't import a Server Component directly into a Client Component module (as that would require another request to the server). Instead, you can pass a Server Component as a prop to a Client Component.

You can pass Server Components as props to Client Components. The pattern of "lifting content up" has been used to avoid re-rendering a nested child component when a parent component re-renders. You're not limited to the children prop. You can use any prop to pass JSX.
client-component.js:
'use client'
 
import { useState } from 'react'
 
export default function ClientComponent({
  children,
}: {
  children: React.ReactNode
}) {
  const [count, setCount] = useState(0)
 
  return (
    <>
      <button onClick={() => setCount(count + 1)}>{count}</button>
      {children}
    </>
  )
}

page.js:
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>
  )
}
You cannot import a Server Component into a Client Component.
'use client'
 
// You cannot import a Server Component into a Client Component.
import ServerComponent from './Server-Component'
 
export default function ClientComponent({
  children,
}: {
  children: React.ReactNode
}) {
  const [count, setCount] = useState(0)
 
  return (
    <>
      <button onClick={() => setCount(count + 1)}>{count}</button>
 
      <ServerComponent />
    </>
  )
}