Environment Variables

Remix does not do anything directly with environment variables (except during local development), but there are some patterns we find useful that we’ll share in this guide.

Environment Variables are values that live on the server that your application can use. You may be familiar with the ubiquitous NODE_ENV. Your deployment server probably automatically sets that to “production”.

Running remix build compiles using the value of process.env.NODE_ENV if it corresponds with a valid mode: “production”, “development” or “test”. If the value of process.env.NODE_ENV is invalid, “production” is used as a default.

Here are some example environment variables you might find in the wild:

  • DATABASE_URL: The URL for a Postgres Database
  • STRIPE_PRIVATE_KEY: The key a checkout workflow will use on the server
  • STRIPE_PUBLIC_KEY: The key a checkout workflow will use on the browser

If your experience with web development is primarily with the JS frameworks in the last few years, you might think of these as something for your build to use. While they can be useful for bundling code, traditionally those are “build arguments” not environment variables. Environment variables are most useful at runtime on the server. For example, you can change an environment variable to change the behavior of your app without rebuilding or even redeploying.

Server Environment Variables

Local Development

If you’re using the remix dev server to run your project locally, it has built-in support for dotenv.

First, create an .env file in the root of your project:

  1. touch .env

Do not commit your .env file to git, the point is that it contains secrets!

Edit your .env file.

  1. SOME_SECRET=super-secret

Then, when running remix dev you will be able to access those values in your loaders/actions:

  1. export async function loader() {
  2. console.log(process.env.SOME_SECRET);
  3. }

If you’re using the @remix-run/cloudflare-pages adapter, env variables work a little differently. Since Cloudflare Pages are powered by Functions, you’ll need to define your local environment variables in the .dev.vars file. It has the same syntax as .env example file mentioned above.

Then, you can pass those through via getLoadContext in your server file:

  1. export const onRequest = createPagesFunctionHandler({
  2. build,
  3. getLoadContext(context) {
  4. // Hand-off Cloudflare ENV vars to the Remix `context` object
  5. return { env: context.env };
  6. },
  7. mode: process.env.NODE_ENV,
  8. });

And they’ll be available via Remix’s context in your loader/action functions:

  1. export const loader = async ({ context }: LoaderArgs) => {
  2. console.log(context.env.SOME_SECRET);
  3. };

Note that .env files are only for development. You should not use them in production, so Remix doesn’t load them when running remix serve. You’ll need to follow your host’s guides on adding secrets to your production server, via the links below.

Production

Environment variables when deployed to production will be handled by your host, for example:

Browser Environment Variables

Some folks ask if Remix can let them put environment variables into browser bundles. It’s a common strategy in build-heavy frameworks. However, this approach is a problem for a few reasons:

  1. It’s not really an environment variable. You have to know which server you’re deploying to at build time.
  2. You can’t change the values without a rebuild and redeploy.
  3. It’s easy to accidentally leak secrets into publicly accessible files!

Instead we recommend keeping all of your environment variables on the server (all the server secrets as well as the stuff your JavaScript in the browser needs) and exposing them to your browser code through window.ENV. Since you always have a server, you don’t need this information in your bundle, your server can provide the client-side environment variables in the loaders.

  1. Return ENV for the client from the root loader - Inside your loader you can access your server’s environment variables. Loaders only run on the server and are never bundled into your client-side JavaScript.

    1. export async function loader() {
    2. return json({
    3. ENV: {
    4. STRIPE_PUBLIC_KEY: process.env.STRIPE_PUBLIC_KEY,
    5. FAUNA_DB_URL: process.env.FAUNA_DB_URL,
    6. },
    7. });
    8. }
    9. export function Root() {
    10. return (
    11. <html lang="en">
    12. <head>
    13. <Meta />
    14. <Links />
    15. </head>
    16. <body>
    17. <Outlet />
    18. <Scripts />
    19. </body>
    20. </html>
    21. );
    22. }
  2. Put ENV on window - This is how we hand off the values from the server to the client. Make sure to put this before <Scripts/>

    1. export async function loader() {
    2. return json({
    3. ENV: {
    4. STRIPE_PUBLIC_KEY: process.env.STRIPE_PUBLIC_KEY,
    5. },
    6. });
    7. }
    8. export function Root() {
    9. const data = useLoaderData<typeof loader>();
    10. return (
    11. <html lang="en">
    12. <head>
    13. <Meta />
    14. <Links />
    15. </head>
    16. <body>
    17. <Outlet />
    18. <script
    19. dangerouslySetInnerHTML={{
    20. __html: `window.ENV = ${JSON.stringify(
    21. data.ENV
    22. )}`,
    23. }}
    24. />
    25. <Scripts />
    26. </body>
    27. </html>
    28. );
    29. }
  3. Access the values

    1. import { loadStripe } from "@stripe/stripe-js";
    2. export async function redirectToStripeCheckout(
    3. sessionId
    4. ) {
    5. const stripe = await loadStripe(
    6. window.ENV.STRIPE_PUBLIC_KEY
    7. );
    8. return stripe.redirectToCheckout({ sessionId });
    9. }