Ice hockey stick and puck on a frozen lake. Photo by Beppe Karlsson

How to access environment variables in Remix run on Cloudflare Pages

January 20, 2022

Update: When I wrote this post, there was no out-of-the-box integration with environment variables for Remix on Cloudflare Pages. The situation has changed since then. I've updated the solution below but also kept the old solution for posterity in case someone is still using the old setup.

Updated solution

The most recent starter of Remix on Cloudflare Pages has seen some significant improvements. The Cloudflare team have been involved in setting up the excellent Cloudflare Pages starter with Remix. You can find their walk through here.

The new starter takes care of the old problem out of the box. However, if you're coming from Node.js land, there are still some gotchas.

No .env file

Cloudflare handles environment variables differently. You're probably used to throwing your environment variables in a .env file and using dotenv to access the variables on process.env. That doesn't work with Cloudflare. Instead, you need to create a .dev.vars file. The syntax in the file is the same as for .env.

Types

If you're using TypeScript the starter also comes with a very neat little script to generate the types for your environment variables, added to the scripts field of package.json. If you run npm run build-cf-types or wrangler types in the terminal, it will generate all the types for your environment variables, with full intellisense support.

Accessing the variables

How you access the environment variables differs with Cloudflare Pages as there's no global process.env object. The variables are instead injected in the context object in all your Remix loaders and actions, i.e. context.cloudflare.env.

By default, the variables are only accessible server-side. If you need client-side variables, pass them from a loader. If you need the variable in multiple routes you could add them to the root loader.

root.tsx
export async function loader({context}: LoaderFunctionArgs) {
  return json({
    env: {
      MY_PUBLIC_ENV_VAR: context.cloudflare.env.MY_PUBLIC_ENV_VAR,
    }
  })
}

Keep in mind that any variables passed this way are publicly accessible. Don't pass secret environment variables from the loaders.

To read the variables on the frontend, you can grab them using the useMatches hook from Remix. The hook returns an array and the first element of the array is always the root loader.

some-route.tsx
export function MyComponent() {
  const {env} = useMatches()[0];
  // do something with your env variables
}

Closing thoughts

Initially, I didn't like not being able to use process.env to access the environment variables everywhere. But this way, you have to be more deliberate in handling your environment variables. It changes a few patterns, though, like initiating third-party libs. The best solution is to initiate it in the load-context.ts file and hang it on the context object. It encourages a cleaner client-server pattern powered by Remix's Forms, actions and loaders.

Below is the original post as written when the article was initially published in late January 2022.

Original post

After a long break from coding to spend time with my daughter and move houses (I would not recommend moving with a baby 😅), I'm finally getting back to it. A lot happened on the World Wide Web while I was out, and perhaps the one that made the most noise was the open-sourcing of Remix. So I thought I'd give it a shot, and I'm glad I did.

Bootstrapping a Remix project with create-remix@latest lists Cloudflare Pages as a possible deployment target.

Great! I thought and immediately selected that option, watching the disk spin out the files while I listened to some retro synthwave.

The problem

I'll fast forward past the next bit because this isn't a review, but I got to a stage where I needed to use an environment variable. An API key that I didn't want to commit to source code.

But how do you add an environment variable in Remix on Cloudflare Pages? Not even Google seemed to know. I found some vague information about environment variables in Cloudflare Pages, and it was easy enough to add them to the settings interface. But how could I access those? And what about local development?

The Remix docs linked to the Cloudflare Documentation for environment variables, but that didn't help. Then the Remix docs went on to talk about dotenv. Ok, I know dotenv. Let's bring that in for local development, and I'll worry about deployment later. Cloudflare says no!

Cloudflare Pages run all the Remix backend functions in Cloudflare Workers, and guess what Cloudflare Workers isn't: a Node.js environment. So dotenv's internal imports of Node modules crashed the build.

There has to be a way, I thought. Then I changed the music to death metal and banged my head against the desk for an hour or so.

All the documentation I read for Cloudflare Pages and Workers told me I should be able to access environment variables as a param on the destructured argument that comes into the worker functions. But when I tried that in Remix, I got a big fat nothing. Or rather, undefined. So I started to dig into the source code for the Remix Cloudflare Pages integration. At this point, my brain was mush, so I can't tell you exactly how I figured it out, but eventually, I did, and there it was.

The solution

There's a folder called functions in the scaffolded project. That folder contains a single catch-all route (I can't remember if that's what Remix calls them) called [[path]].js. That file contains the following lines:

[[path]].js
const handleRequest = createPagesFunctionHandler({
  build,
});

The createPagesFunctionHandler accepts another property on that destructured argument: getLoadContext. That's how you pass the context from Cloudflare Workers to Remix functions.

javascript
const handleRequest = createPagesFunctionHandler({
  build,
  getLoadContext: (context) => context,
});

That's it, as simple as that. One missing property on an object and no documentation (that I found over several hours of googling) to tell you how to add it or that it was needed wasted half a day for me. I hope this post will help you avoid going down the same path. Instead, keep those hours. Do something more useful with them. Maybe solve the next problem and write it up so that you've already written an article when I get to it.

Maybe you could solve how to add environment variables locally with Cloudflare Pages because I wasn't able to get that working other than by passing the variables in as arguments to wrangler when I run the script.

bash
wrangler pages dev ./public --watch ./build --binding ENV_VAR=\"my-environment-variable-value\"

I'd love to hear about it if you found this helpful or fixed the local environment variable problem. I'm @BeppeKarlsson on Twitter. Drop me a line.