javascriptreactjsnext.jsserver-side-renderingserver-side

How to fetch data for Client Components on server-side


When I say Client Components -> I mean files that uses use client at the top of it, and Server Components — files that are using use server accordingly

Nobody says, how to fetch data on the server side and pass it directly to the Client Components (or I somehow didn't find that) Even more, — async/await functions and components are forbidden in Client Components, and if you will try to fetch server data to prerender client components with it you will get an error:

Error: async/await is not yet supported in Client Components, only Server Components. This error is often caused by accidentally adding `'use client'` to a module that was originally written for the server.

The NextJS documentation says that client-side fetch data should be handled in this way:

useEffect(() => {
    fetch('/api/profile-data')
      .then((res) => res.json())
      .then((data) => {
        setData(data)
        setLoading(false)
      })
  }, [])

But this doesn't satisfy me at all, I want to get my data and fill the client components with it on the server side getStaticProps/getServerSideProps are also not available anymore, so...

So, the solution that I got App directory:

/app
  page.tsx
  clientPage.tsx
  layout.tsx

page.tsx

// That's used by default, but anyway
"use server"

import React from 'react'
import { ClientPage } from './clientPage'

const getServerDataForClient = async () => {
  console.log('That was executed on the server side')

  const serverData = await new Promise(resolve => {
    setTimeout(() => resolve({ someData: 'secret' }), 3000)
  })

  return serverData
}

const Page = async () => {
  console.log('That also was executed on the server side')
  const serverData = await getServerDataForClient()

  return <ClientPage serverData={serverData} />
}

export default Page

clientPage.tsx

    'use client'

import React, { FC, useEffect, useState } from 'react'

type TClientPageProps = {
  serverData: {
    someData: string
  }
}

// This function will be executed firstly on the server side
// It will ignore all dynamic logic that works in the browser (like useEffect, useLayoutEffect and etc.)
// And just will return the html with rendered data (including initial state of `useState`, and `useRef`, if we would use that)
export const ClientPage: FC<TClientPageProps> = ({ serverData }) => {
  // first we will get `server state` in the server and browser and as an initial state
  const [state, setState] = useState('server state')
  
  console.log("This log will be shown in the server logs on component's first mount and browsers on all mounts")

  useEffect(() => {
    // that won't be called on the server side
    // it will be changed only when the component will be mounted in the browser
    setState('browser state')
  }, [])

  return (
    <main>
      <div>State is: {state}</div>
      <div>Server data is: {serverData.someData}</div>
    </main>
  )
}

So, NextJS provided to us a better way of handling server-side functions (I really like that we get rid of getServerSideProps/getStaticProps), but now — I have to have two files: page.tsx for a server logic and clientPage.tsx to provide all that logic

Am I using that right? Or I am missing something? Other question in my mind is that if I have nested components in the code then i have to pass data through prop drilling I am no able to solve this confusion.

P.S. If you will try to join this two files page.tsx and clientPage.tsx into one — you will get an error ("useState" is not allowed in Server Components.) Also if you will try to make something like this and use Server Actions in Client Components to fetch data on the server side to pre-render it:

const getServerDataForClient = async () => {
  'use server'

  console.log('That was executed on the server side')

  const serverData = await new Promise(resolve => {
    setTimeout(() => resolve({ someData: 'secret' }), 3000)
  })

  return serverData
}

You will also get an error, because Client Components can't use async/await to await the server action

Or, the other option that I thought of — maybe add use client to all the page-components I mean,

/src/components/Partners
"use client"

export const Partners: FC = () =>
  // some code here

But then the App Router become less comfortable, because I will need to create a one more joining compoennt, if I will need a few of them on the same page


Solution

  • Generally, it is preferable to have the server be the one fetching the data rather than the client almost always. There's cases where you might want client-side fetching like with infinite scrolling.

    Your approach of having a client component with UI logic and a server component that fetches the data and renders the client component is correct and most of the time that's the way you'll want to develop Next.js apps. And there's nothing wrong with having this distinction, as a matter of fact, it is good because it enforces the Single-responsibility Principle, as in, your components should do only one thing and one thing well.


    In this case, your server component will be the one to fetch the data, which will then delegate to the rest of your UI components to render that data. If you build your components to be as modular as possible, "prop drilling" doesn't become much of an issue because you'd be passing only the data necessary to those components that would work anywhere in the app.

    Take the following example:

    interface Props {
      user: User
    }
    
    const AvatarImage: React.FC<Props> = ({ user }) => {
      return (
        <img src={user.image} />
      );
    });
    

    And,

    interface Props {
      image: string
    }
    
    const AvatarImage: React.FC<Props> = ({ image }) => {
      return (
        <img src={image} />
      );
    });
    

    While the example is too contrived and simplistic, which one seems better? The first option would only work for users, while the second one works for anything. If this component were to use many more properties of the User interface, you'd be tempted to instead get the data through a context to avoid prop drilling from the top like:

    interface Props {
      
    }
    
    const AvatarImage: React.FC<Props> = () => {
      const { image } = useUserContext();
    
      return (
        <img src={image} />
      );
    });
    

    But this forces AvatarImage to really work only for users. While you solved the "prop drilling" issue, you created a new one by increasing the coupling between AvatarImage and User and reducing the component's flexibility.


    On another note, making your page components be client components will not allow you to use exports like generateMetadata() which need to be run inside the server.


    TL;DR: Yes, having multiple components that do a single thing is appropriate. A server component that fetches data and delegates the UI rendering to a client component is definitely SOLID design.