At Hardfin, the Next.js framework provides a powerful way for us to build frontend applications. It comes loaded with features that we use on a daily basis — however one area that is limited in functionality is support for multiple build environments.
Backing up a bit, Next.js build environments are fundamentally powered by
environment variables, and these environment variables allow us to configure
different settings for our Next.js application without hardcoding them. This is
especially useful for configuring things like API endpoints based on the build
type — for example, you might have multiple APIs that serve development,
staging, and production data. Next.js supports environment variables by
relying on .env
files to differentiate development and production builds.
Only two environments!?
Next.js does in fact only support two different build types1 — development and production. This is fairly restrictive and there have been numerous issues opened and RFCs written to expand the built in environment-loading capabilities for Next.js. As of now though, projects that require operating across more than two environments need to get creative with their solutions.

Get creative
At Hardfin, we build our Next.js applications across several deployments such as development, staging, demo, and production. In the next section, we’ll walk through how we used a simple, custom script that allows us to define any number of different environments.
Next.js environment loading script
The first step to configuring environment variables is to understand how
environment variables can be loaded. It turns out that Next.js has a
built-in way to set environment variables at build time through
the next.config.js
file. Specifically, if a property called env
is exported,
all of the keys specified will be available in the build as environment
variables. This is similar (but not identical) to2 how environment variables
are loaded from a .env.development
or .env.production
file. For example, if
we wanted to configure an API_ENDPOINT
environment variable, it might look
something like the following:
// next.config.js
module.exports = {
env: {
API_ENDPOINT: 'http://localhost:1234'
}
};
// src/get-weather.ts
export function getWeather() {
return fetch(`${process.env.API_ENDPOINT}/weather`);
}
Now, if we wanted a different value for API_ENDPOINT
based on something like
the target environment, we could modify our next.config.js
script to include
this behavior:
// next.config.js
function getAPIEndpoint() {
if (process.env.TARGET_ENVIRONMENT === 'production') {
return 'http://api.example.com';
}
if (process.env.TARGET_ENVIRONMENT === 'staging') {
return 'http://api.staging.example.com';
}
return 'http://localhost:1234'
}
module.exports = {
env: {
API_ENDPOINT: getAPIEndpoint()
}
};
The above example is a good illustration of how we might set different environment variable values based off the environment, but the syntax is a bit verbose and cumbersome. We can improve on this by mimicking the existing environment variable patterns. We would do this by:
- Specifying an
env/.env.{environment}
file for each environment (env/.env.development
,env/.env.staging
, etc…)3 - Reading the appropriate file based off of the environment using the Node.js
fs
package. - Parsing the file using the
dotenv
package - Exporting the results through the
env
key innext.config.js
The result will look something like this:
.
├── env
│ ├── .env.demo
│ ├── .env.development
│ ├── .env.production
│ └── .env.staging
├── src
│ └── ...
│
├── next.config.js
└── package.json
// next.config.js
const dotenv = require('dotenv');
const fs = require('fs');
const path = require('path');
function loadTargetEnv(target) {
// Constructs a path such as env/.env.development
const envPath = path.join(__dirname, 'env', `.env.${target}`);
return dotenv.parse(fs.readFileSync(envPath));
}
module.exports = {
env: loadTargetEnv(process.env.TARGET_ENVIRONMENT)
};
Conclusion
While Next.js doesn’t provide default support for multiple environments, it’s possible to emulate the exact desired behavior by leveraging its configurable build process. Here, we’ve outlined a very basic script to load environment variables but this process can be as customizable as your application needs!
Next.js also supports a third build type specifically for automated test environments. See the documentation for
.env.test
. ↩︎See the documentation for the
env
property to see how it differs from loading variables via.env
files ↩︎The
.env
files are being placed in anenv/
directory so that the default Next.js behavior for.env
files is avoided. ↩︎