Next.js 13+ answer
Next.js experimental app folder now covers this use case perfectly well.
Passing values from RSC to client components via React context
- Fetch your data in a React Server Component (RSC) layout
- In this layout, render a React context with this value
- Consume this context in your application as you wish
You can of course set this context in an RSC further down the tree, for example if you need it only on a certain page or component of a page.
In this real-life example, I fetch a survey definition in a page RSC and pass it down to client-code via context. My context provider exposes a typed reusable hook.
Sample code for the React Server Component (here a page):
// /app/referer/page.tsx (Server Component)
import { headers } from 'next/headers'
export default async function Page() {
const headersList = headers()
const referer = headersList.get('referer')
return (
// we set a CLIENT context,
// based on the SERVER context that we got via headers()
<RefererProvider value={referer}>
<InteractiveReferer />
</RefererProvider>
)
}
Sample code for the client context (don't forget the "use client"
directive):
// /components/InteractiveReferer.tsx (Client Component)
// use "client"
export const InteractiveReferer = () => {
// this context has been initialized by a parent RSC
// but we can access it as expected in a Client Component
const referer = useRefererContext()
const [clickedReferer, setClickedReferer] = useState(false)
return (
<button
onCLick={() => {
setClickedReferer(true)
}}
>
Referer: {referer}
</button>
)
}
Passing values from RSC to other RSCs via caching
Another novelty of Next.js app folder, is that now, server-side code is not limited to page-level getServerSideProps
anymore. Nested component can also be Server Components and trigger server calls. Therefore, you might sometimes not only want to setup a client-side context, but also a kind of server-side context, scoped to the current request.
I describe this use case extensively in this article. To sum it up, you can use React 18 cache
function (undocumented at the time of writing) to achieve this goal.
I've crafted an open source demonstration of this pattern, which can be summarized by this code sample:
import { cache } from "react";
export const getServerContext = cache(() => ({
// a dummy context for the demonstration
createdAt: Date.now().toString(),
calledByLayout: false,
calledByPage: false,
calledByNested: false
}))
You can mutate the returned value directly to store new information in this context.
A note on naming: I've be using the term "server context" to designate data stored in cache
that acts as a context. It's because it's the server-side equivalent of the "client" context.
However, "Request cache" is perhaps more suited, as "server context" may be used for an other purpose in future versions of Next.js and React (sharing data between RSC and client components).
Get/Set and layouts
For RSC, the "cache" function allows to implement "cached getters", you don't really pass the values around but instead you call the function to get the value. Thanks to caching, the value is actually retrieved only once, your database or API won't be overloaded with requests.
In rare situations, this is not possible to use this approach, namely if you want to derive a value from the page props (rather than fetching data from an API/database) and pass it to children without props drilling.
The server-only-context package implements an alterative "get/set" pattern for mimicking a context with RSC. You can set a value in the page, and access it in the component.
Be mindful that you can't guarantee that a layout is rendered before a page (because of client-side navigation), so you can't set a value from a layout and get it from a page. Hopefully, this pattern is never strictly needed.