apocalypse

dev

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.

Read more in docs

#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.

Related tags:

env
variable
variables
next
nextjs
client
side
browser
server
reference
error
hydration
mismatch
feature
flag