Making .env variables work client-side in Next.js app
#Introduction
So you're building your Next.js application, and you add some feature flag to your env. Things are fine, but then you try to access this variable on the client side, and you get either a reference error, hydration mismatch or maybe some other ungodly behavior. What happened? Well, the problem is - env variables are accessible through the process
object that exist by default only in the Node environment (aka not the browser). Next.js, as a full-stack framework, lets you access those variables on the server side, but if you want to use them also in the browser (have them in your client bundle), you need to explicitly expose those variables. How? That's what this post is about.
#Maybe you don't need to (or shouldn't) use this variable in the browser?
Before you start writing any code, let's ask yourself that question. Is it safe to ship this variable to the client, so that everybody can potentially see it? While it may be obvious to many people, it wouldn't hurt to remind ourselves what type of information is rather safe to expose and what is a security threat.
✅ feature flags ✅ base URLs for static assets ✅ localization settings ✅ public keys (still shouldn't land on the client side when not necessary) ✅ general UI configuration
❌ connection strings to other services (like a DB) ❌ private keys ❌ any other type of private access tokens ❌ general backend configuration
These are some general guidelines. Later in this post I'm gonna show you a specific example of how you can avoid exposing environmental values to the client, even if they're safe, when you don't necessarily need them for any interactions or side effects.
#TL;DR
Okay, if you're short on time and just look for the answer, here it is. You can either prefix the variable with NEXT_PUBLIC_
or expose it through a config file.
.env.local# Add a NEXT_PUBLIC_ prefix
NEXT_PUBLIC_YOUR_FLAG="true"
OR
next.config.js/** @type {import('next').NextConfig} */
module.exports = {
...
env: {
// explicitly add the flag to JS bundle
YOUR_FLAG: process.env.YOUR_FLAG,
},
};
Later in code you use it the same way as on the server.
"use client";
function YourComponent() {
return (
<>
<SomeComponent />
{process.env.YOUR_FLAG && (
<SomeFlaggedComponent />
)}
</>
);
}
Now it's time for some basic explanations. I will also provide links to Next.js docs, where you can read more about the matter.
#Solution 1: Add NEXT_PUBLIC
prefix
.env.local# Add a NEXT_PUBLIC_ prefix
NEXT_PUBLIC_YOUR_FLAG="true"
As the docs say: "In order to make the value of an environment variable accessible in the browser, Next.js can "inline" a value, at build time, into the JS bundle that is delivered to the client, replacing all references to process.env.[variable]
with a hard-coded value. To tell it to do this, you just have to prefix the variable with NEXT_PUBLIC_
.
This solution is quite simple, but requires the variable name change. Variable with NEXT_PUBLIC_
prefix will still be accessible on the server side, so there's no need to redeclare a variable, but it takes more space to write it. Yeah, first world problem.
Also, note that this works only for build-time env variables, as you wouldn't be able to change the value of this variable post build - it will be inlined as "true", "false" or whatever you make it, and it will stay this way in JS bundle.
#Solution 2: nextConfig.env
If you're like me, and you don't like the idea of prefixing a variable name each time you want to use it, this second solution will probably suit you more.
next.config.js/** @type {import('next').NextConfig} */
module.exports = {
...
env: {
// explicitly add the flag to JS bundle
YOUR_FLAG: process.env.YOUR_FLAG,
},
};
It's dead simple, you add the flag to nextConfig.env
object, where key is the name of the flag and value is taken from process.env, and webpack will inline every client-side process.env.YOUR_FLAG
with whatever value it will be set to, during process start. Works kinda the same way as the previous solution, but without the need to set a prefix.
It also fits the scenario where you want some env value to be accessible on every environment (dev, prod, test, etc.) and you don't want to declare it every time in the .env
file for each environment.