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.
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.
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:
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.
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.
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.