notes.dt.in.th

Runtime Environment Variables in Next.js

In Next.js, environment variables are evaluated and inlined at build time. This means that if you want to change the value of an environment variable, you will need to rebuild the app.

When building a production-grade Next.js app, this way of environment variable handling is not ideal for Docker-based deployments for the following reasons:

  • You will need to build separate Docker images for each environment (e.g. dev, staging, prod).
  • You will need to rebuild the Docker image every time you want to change an environment variable.
  • Your secrets would end up in the Docker image.

More ideally, we want to create a single Docker image and reuse them across multiple environments. This requires our app to be able to access environment variables that are injected at run-time. This note describes how to do that in a Next.js app.

How to access environment variables at runtime

Don’t use process.env

Again, they are evaluated and inlined into the bundle at build time.

Don’t use serverRuntimeConfig or publicRuntimeConfig

API routes: Use global.process.env

Unlike process.env, references to global.process.env are not inlined into the bundle at build time, so you can use them in your API routes instead.

Server-side-rendered pages: Use global.process.env in getServerSideProps

getServerSideProps is evaluated on the server-side, so you can use global.process.env there too. You can then pass the environment variables to the client-side code via props.

Client-side-rendered components: Fetch the data you need from an API

You can create an API route that returns the data you need. Then in the client-side code, you can request the data from that API route with client-side fetching. Libraries like React Query, SWR, or @trpc/next can help you with accomplishing this.

If you don’t like the idea of making an extra request to the server, there’s an advanced solution below (warning: advanced).

(Advanced) Isomorphic components that are not statically-prerendered: Inject the public environment variables into the global scope or the DOM so that they can also be accessed by client-side code

  1. During server-side rendering of the page, inject the public runtime environment variables into the global scope or the DOM.

  2. Create an isomorphic function that can access the public environment variable.

    • When called on the server-side, the function can access the environment variable from global.process.env. To ensure consistent behavior between the server and the client, it should prevent access to non-public environment variables.
    • When called on the client-side, the function can access the environment variable from wherever you injected it in step 1.
  3. Use the isomorphic function you created in step 2 in your components.

Statically-prerendered pages:

Unfortunately, there is no way to access runtime environment variables in static pages (or pages that gets optimized with Automatic Static Optimization). This is because static pages are pre-rendered at build time.

You can either:

Further reading

Relevant discussions on the Next.js GitHub repo:

Footnotes

  1. Using Output File Tracing is pretty important for keeping images small, because otherwise your Docker image would be pretty large — 60 MB for TypeScript and another 60 MB for swc binaries.