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
process.env
Don’t use Again, they are evaluated and inlined into the bundle at build time.
serverRuntimeConfig
or publicRuntimeConfig
Don’t use These features are now considered “legacy” and do not work with Output File Tracing1, Automatic Static Optimization, or React Server Components.
global.process.env
API routes: Use 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.
global.process.env
in getServerSideProps
Server-side-rendered pages: Use 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
During server-side rendering of the page, inject the public runtime environment variables into the global scope or the DOM.
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.
- When called on the server-side, the function can access the environment variable from
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:
- Use
dynamic()
withssr: false
to force the component that needs the environment variables to be loaded and rendered on the client-side. - Turn the page into a server-side-rendered page by using
getServerSideProps
.
Further reading
Relevant discussions on the Next.js GitHub repo:
- Not possible to use different configurations in staging + production??
- docker image with
NEXT_PUBLIC_
env variables - Allow dynamic
serverRuntimeConfig
andpublicRuntimeConfig
values fromprocess.env.*
withoutputStandalone: true
Footnotes
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. ↩