Question

Retrieve data server side and save in context with Next.js

I would like to be able to retrieve data from an API, server-side, and load it into React context, in order to make it available to any component in my app. I have tried various things, but nothing seems to allow me to do exactly what I want. Some things I've tried include:

getServerSideProps - This allows me to retrieve the data, server-side, but only exists for Page components, so if I want this available on every page, and I don't know which page my user will land on, I'll need to add logic for this to every single page.

getInitialProps in _app.js - I can add this to the _app.js component, which will run server-side, and can make it available to all components via a context provider, but the problem is that it runs on every single page, even when navigating client-side. I would like to be able to call the API once and only once, and this doesn't seem to allow that.

getInitialProps in _document.js - I can add this to the _document.js component, which only runs on the server, which seems to address the problem of it being called for every page, but I cannot figure out how to store it in React context from there. In fact, I can't seem to figure out how to access this data anywhere. It looks like getInitialProps in _document.js is called after getInitialProps in _app.js, so I'm not sure if I can use the value I generate from getInitialProps in _document.js when I am in _app.js.

There are a number of ways I can make this work if I call the API on the client, but that won't work for my use case as it will cause a flash of content when the client updates with the data from the API.

Has anyone come up with a way of addressing this use case?

 46  22611  46
1 Jan 1970

Solution

 46

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

  1. Fetch your data in a React Server Component (RSC) layout
  2. In this layout, render a React context with this value
  3. 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.

2023-02-22

Solution

 17

In Next.js, there's no native function to a) retrieve data from an API, b) do it on the server, c) make it available on every page, and d) only query the API on the first page the user visits.

As you've found out, getInitialProps and getServerSideProps will run every time you visit that page.

However, we can get this to work.

If you need the data before the initial load

  1. Use getInitialProps in _app.js to retrieve data from the API
  2. Load the data into React context inside the _app.js file so it persists between pages
  3. When the browser gets the data, create a cookie.
  4. On a subsequent page load, in getInitialProps, check if there's a cookie. If so, don't retrieve the data.

There's a fairly popular library called nookies to help with cookies in a Next.js project.

If you can load the page then fetch the data

There is a performance cost to using getInitialProps in _app.js: you'll never be able to create a fully static page. That's because getInitialProps will have to run on every single page load.

If you can fetch the data after page load, add an API route. Then, in the context provider, use useEffect to fetch the data.

2021-02-20