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