DEV Community

Browny
Browny

Posted on • Updated on

Using React Context in NextJS Server Components

Most React applications rely on context to share data between components, either directly via createContext, or indirectly via provider components from third-party libraries.
In NextJs, context is fully supported within client components but not server components. This is because, server components are not interactive or have no React state, and context is basically for rendering interactive components after some React state change.

So how can we wrap our server components in a React Context?

The latest version of NextJS replaces the pages folder with the new app directory which by default is the directory for server components.

You can check the migration guide see how you can replace your existing pages directory with the new app directory.
You can also adopt both methods to suit your needs but would recommend you read and understand the migration guide.

Using custom context provider

The following code show how you can use React context in NextJS client component.

/components/sidebar.tsx

'use client';

import { createContext, useContext, useState } from 'react';

const SidebarContext = createContext();

export function Sidebar() {
  const [isOpen, setIsOpen] = useState();

  return (
    <SidebarContext.Provider value={{ isOpen }}>
      <SidebarNav />
    </SidebarContext.Provider>
  );
}

function SidebarNav() {
  let { isOpen } = useContext(SidebarContext);

  return (
    <div>
      <p>Home</p>

      {isOpen && <Subnav />}
    </div>
  );
}
Enter fullscreen mode Exit fullscreen mode

However, context providers are typically rendered near the root of an application to share global states, like the current theme. Since context is not supported in Server Components, trying to create a context at the root of your application will cause an error:

/app/layout.tsx

import { createContext } from 'react';

//  createContext is not supported in Server Components
export const ThemeContext = createContext({});

export default function RootLayout({ children }) {
  return (
    <html>
      <body>
        <ThemeContext.Provider value="dark">
          {children}
        </ThemeContext.Provider>
      </body>
    </html>
  );
}

Enter fullscreen mode Exit fullscreen mode

So how can we have our context provider in the server component?
We can do that by creating our context in a client component first.
Create a /providers/index.tsx file and add your context code into it. Mark the component as client component with the use client directive.

/providers/index.tsx

"use client"

import React, {createContext} from "react";

const ThemeContext = createContext({})

export default function ThemeProvider({children}){
return (
<ThemeContext.Provider value="dark">
 {children}
</ThemeContext.Provider>
)
}

Enter fullscreen mode Exit fullscreen mode

Now we can use our provider in the server component and it will render the provider directly since it has been marked as a client component. With that done, all other client components in our will be able to consume our theme context.

import ThemeProvider from './providers';

export default function RootLayout({
  children,
}: {
  children: React.ReactNode;
}) {
  return (
    <html>
      <body>
        <ThemeProvider>{children}</ThemeProvider>
      </body>
    </html>
  );
}

Enter fullscreen mode Exit fullscreen mode

Using Third-Party Context Providers

Third-party packages often include Providers that need to be rendered near the root of your application. However, since Server Components are so new, many third-party providers won't have added the "use client" directive yet.
If these providers include the "use client" directive, they can be rendered directly inside of your Server Components.
If you try to render a third-party provider that doesn't have "use client", it will cause an error:

import { ThemeProvider } from 'theme-package';

export default function RootLayout({ children }) {
  return (
    <html>
      <body>
        {/*  Error: `createContext` can't be used in Server Components */}
        <ThemeProvider>{children}</ThemeProvider>
      </body>
    </html>
  );
}
Enter fullscreen mode Exit fullscreen mode

To solve this, we need to wrap the third-party provider in our own component and mark it as client with the use client directive just as our custom context.
Edit your custom context as below.

/providers/index.tsx

"use client"

import React from "react";
import { ThemeProvider } from 'theme-package';
import { AuthProvider } from 'auth-package';

export default function MyAppProvider({children}){
return (
<ThemeProvider>
 <AuthProvider>
  {children}
 </AuthProvider>
</ThemeProvider>
)
}

Enter fullscreen mode Exit fullscreen mode

Now we can render the MyAppProvider directly in our root layout.

import { MyAppProvider } from './providers';

export default function RootLayout({ children }) {
  return (
    <html>
      <body>
        <MyAppProvider>{children}</MyAppProvider>
      </body>
    </html>
  );
}
Enter fullscreen mode Exit fullscreen mode

Your app is now set to consume your custom providers as well as third-party providers and with the provider rendered at the root, all the hooks and components from these libraries will work as expected.

Important

You should render providers as deep as possible in the components tree. This makes it easier for Next.js to optimize the static parts of your Server Components.

Conclusion

React Context Provide developers a way to share data across our app. Client components in React by default supports context.
We can use this feature in server components only by marking our provider as client.
React context should be rendered as deep as possible in the components tree.

Top comments (14)

Collapse
 
jonathanludena_91 profile image
Jonathan Ludeña

I have a Provider to which an environment variable (api key) is inserted. Do you think it could work to protect this API key from being rendered on the client side? I am using React's GoogleRecaptcha v3 provider to wrap a form

Collapse
 
codingbrowny profile image
Browny

Protecting sensitive information like an API key in a React application is crucial to prevent it from being exposed on the client side. However, when using a React Provider to manage environment variables, you should be aware that anything you include in your client-side JavaScript code, including environment variables, can potentially be accessed by users with the right knowledge and tools. Therefore, it's essential to use additional security measures to protect sensitive data.

Collapse
 
jonathanludena_91 profile image
Jonathan Ludeña

Could you recommend some way in which you have been able to protect this information on the client side. I've been thinking about using secrets, but they can be used from the server side, and react's Recaptcha version 3 provider inserts it from a provider component. If you can help me, I would really appreciate it.

Thread Thread
 
codingbrowny profile image
Browny

It is important to know that context providers primarily work on the client side within the react application. They allow you to control which data is shared or passed down to components. You define the methods you want to expose via the context provider and only components explicitly accessing the context will receive the data. While context providers do not inherently exposes data to the client, you should still be cautios about what data you put into context and who have access to it.

Here are some the practices I would reccomend.

Server-Side Rendering (SSR):
Utilize server-side rendering to render the initial HTML page on the server and inject the API key on the server side. This way, the API key won't be visible in the client-side JavaScript. You can use libraries like Next.js for SSR in React applications.

Environment Variables:
Store sensitive data like API keys as environment variables on the server-side.
NextJs has built-in support for enviroment variables or you can leverage on the env support from your deployment provider.
These environment variables should be kept confidential and not exposed in your client-side code. You can access these server-side environment variables when making API calls on the server.

Proxy Server:
Implement a server-side proxy that handles API requests to the third-party service. Your React application can make requests to your proxy server, which will then use the API key to communicate with the external service. This way, the API key is never exposed on the client side.

Limited Scope API Key:
If possible, obtain or generate an API key that has limited permissions or access rights. This way, even if it were exposed, it would have minimal impact. For example, if your API key is only used for read operations, restrict it to read-only access.

Encryption:
If you must include the API key in your client-side code, consider encrypting it and decrypting it on the server when needed. This adds an additional layer of security.

Protect API Key Storage:
Make sure that your API key is not stored in a way that can be easily accessed or retrieved from your client-side code. Avoid storing it in local storage, session storage, or cookies, as these can be accessed by client-side scripts.

Use Google reCAPTCHA Securely:
When using Google reCAPTCHA, follow Google's guidelines and best practices for handling API keys and client-side integration. Google provides guidance on how to keep your API key secure when using their services.

I hope this helps.

Collapse
 
scottmackenzzz profile image
Scott Mackenzie

Super helpful. Thanks for clarifying. I guess the default/initial output of any provider matters since it will be the first thing seen before the client takes over?

Collapse
 
codingbrowny profile image
Browny

Yeah
That's correct

Collapse
 
gildembergleite profile image
Gildemberg Leite

It's a valid tip, but it doesn't allow using the context, but rather importing the context provider into a server component.

Collapse
 
sney profile image
Snehil

If the parent of a server component is a provider, which is a client component; would it benefit from being a server component?
e.g.
in Parent > ChildA > ChildB('use client'), parent and childA can benefit from server components.
by doing the above, Parent > ContextProvider('use client') > ChildA > ChildB('use client'), does ChildA benefit from being a server component as its wrapped in a client component..

Collapse
 
mistersingh179 profile image
Mister Singh

yes ChildA in your example does benefit. this is because if ChildA is a server component, it is still being built on the server and gets all benefits of sever side rendering like fetch memoization, data cache, & full route cache.
with that said it is import to know how ChildA was put in the parent context provider client component. it needs to go in as part of the children. this means that it is not being imported in childA, but rather being passed in from above as a prop. this is what allows it to be server side rendered.
there is so important that they even named this pattern. It is called the interleaving pattern.
in general you can benefit by reading this part of the documentation
nextjs.org/docs/app/building-your-...

Collapse
 
bornbrainy profile image
Ebenezer Amaraiwu

is there a way to do async operations inside Provider wich is client component , here is what i want to achieve

'use client'

import authicatedUser
import { createContext, useContext } from 'react'

interface User {
  id: string
  fullName: string
  username: string
  role: string
}

const UserContext = createContext<User | undefined>(undefined)

export const useUserContext = () => {
  const user = useContext(UserContext)

  if (user === undefined) {
    throw new Error('useUserContext must be used within a UserProvider')
  }
}

export default async function UserProvider({
  children,
}: {
  children: React.ReactNode
})
const { user } = await authenticatedUser()
{
  return <UserContext.Provider value={user}>{children}</UserContext.Provider>
}

Enter fullscreen mode Exit fullscreen mode
Collapse
 
salehweb profile image
salih hassan • Edited

is this will make all the components client side rendered

Collapse
 
mistersingh179 profile image
Mister Singh

no. this does not make all children components automatically client side.
children components will be server side by default and client side if they are so marked.
this happens because the context provider wrapping component did not import the children component, there are in fact just passed in as props and all the provider component is doing is deciding where to place them so they render there.
hope that helps

Collapse
 
prchaljan profile image
Jan Prchal

By using a context provider at the root of your app, could there be a decreased rendering speed at the initial load? Assuming that all the server-side components will be shown only after the state provider had done its thing in the browser? Could there be a speed benefit by using something like zustand where you don't need to use a provider? Thanks.

Collapse
 
codingbrowny profile image
Browny

Please don't hesitate to leave a comment if you find this post useful or have a suggesstion.