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>
);
}
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>
);
}
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>
)
}
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>
);
}
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>
);
}
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>
)
}
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>
);
}
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 (18)
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
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.
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.
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.
It's a valid tip, but it doesn't allow using the context, but rather importing the context provider into a server component.
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?
Yeah
That's correct
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..yes
ChildA
in your example does benefit. this is because ifChildA
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 thechildren
. this means that it is not being imported inchildA
, 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-...
It seems like the context is reset every time it navigates to another route. Am I missing something?
./app/layout.tsx
...
return (
<html lang={params.lang}>
<body className={roboto.variable}>
<SessionContextWrapper>{children}</SessionContextWrapper>
</body>
</html>
);
is there a way to do async operations inside Provider wich is client component , here is what i want to achieve
Please don't hesitate to leave a comment if you find this post useful or have a suggesstion.
is this will make all the components client side rendered
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
I have context that I want to pass its values throughout my Next js app.
I have the context in the layout.tsx file. Within the layout.tsx file, I also have my Navbar, Header and Footer components. Now the issue is, the context values can be used in the aforementioned components, but not in the children component. In development everything works fine but in production (trying to host it on Vercel), I get issues that the context values are not accessible. It doesn't even work with ClerkProvider as well so obviously the problem is not from my context provider. Please help me fix it.
"If these providers include the "use client" directive, they can be rendered directly inside of your Server Components."
I think the word "rendered" should be "pre-rendered" in this sentence.
A client component cannot literally be rendered inside a server component.