Remix Fundamentals

Environment Variables Solution

Kent C. Dodds

Kent C. Dodds

Professional Trainer
Remix Fundamentals

Check out a free preview of the full Remix Fundamentals course

The "Environment Variables Solution" Lesson is part of the full, Remix Fundamentals course featured in this preview video. Here's what you'd learn in this lesson:

Kent walks through the solution to the environment variables exercise.


Transcript from the "Environment Variables Solution" Lesson

>> We're going to talk about environment variables, again, it's really important I must emphasize to you that you only do this for environment variables you're okay ending up on the client. You do not want to do this for all environment variables or whatever cuz that could be really bad.

So let's first talk about the problem, what are we trying to accomplish here? So I'm going to come into, yeah, let's go into admin, index, that'll work. And we'll say process env ADMIN_EMAIL. Did I spell that right, EMAIL? There we go, right? So that's what I, my objective is admin email should go right here.

And again, I've got this environment variable configured in here. Remix automatically reads that .env file, in production you should have these set as environment variables in whatever hosting provider you're using. However, every one of them does it differently, you have to look up how they do it. But during development, the .env file works great.

And so I just want to print out what is the admin email? So if I come over here, go to admin, the root route, I'm gonna get unexpected error process is not defined. What garbage is this? And what's really annoying is, if I look at the server rendered stuff, admin email is right there.

So it actually works. If I turn off JavaScript, it works. I dehanced my project. Remember that? Yeah, so the problem is that the browser does not have process. It has a processing instruction apparently. [LAUGH] But it doesn't have process so that is this is not going to work not in your React components.

Now it would work in a loader again like that works just fine and so what you could do is you could say export to a function that's a loader and return a JSON response with adminEmail. And we'll bring in JSON from remix node. Cool, and then we can say, useLoaderData Admin email.

And there we go. Now it works out an email. That's great. However, I don't wanna do that. You didn't even wanna wait for me to type all that. I could tell. You're like man I'm going to type the whole thing. Yes, I did. And it was not cool.

I don't want to do that. And the reason I don't want to do that is because, well, here it's bringing back. This will never change through the entire lifetime of while this application is running until the next deploy or until I change the environment variable. That value won't change.

So why do I care to send that to the client like this? I could just have that, or why do I have it as part of this loader? If I anywhere else that I want to do this I'd have to have another loader for that, it'd be really annoying I don't want to do that.

Because this doesn't change during the lifetime of the users experience on the app, I think that it's it would actually be better to instead just have this be global. You typically don't do global stuff in web apps it's not the best to just say let's global everything because if those things can change now you have to start talking about subscriptions and render and use mutable data source.

So yeah, we don't wanna do that typically, but for this, I think it totally makes sense. And I have a convention that I really like for this. And that convention involves a global env variable. So, again, because this runs both on the client and on the server, I need to have a way to define a global env for both the client and the server.

So, let's do the server first because this is gonna blow up on the server side as well. If we look at our console output, we're gonna get yeah, just a ton of nonsense reference error env is not to find, so it's let's deal with the server side first.

For the server, you have this entry.server file. That is the first part of your code that's going to run or get imported when your server starts up, okay? Now again, I probably should mention that we're using the remix server module which has an express server underneath the hood that is serving all these pages.

You can actually integrate remix within another express server or within a fastly or faster phi I always forget or a COA or like whatever other node framework that you want to. And they're all going to be a little bit different. But this entry server typically is going to be the first bit of your code that's imported.

And so here I can say global.env equals admin, email process env admin email, okay? And so now my server renderer is going to work and actually you see a flash, do you see that? It flashes for a second before hydration kicks in because now the client is busted.

But that server renderer totally works because we now have that defined. Where is that? There it is. Great, so now on the client side, we have well, you might think, well, it'll just be the entry.client. Well, yes, this is the first bit of your code that actually executes on the client.

However, this only executes on the client. So you can't say window.env equals anything with process env because again wait a second, okay? [LAUGH] Process is not defined. I'm not sure why that what it's okay. Anybody, do you know what it is?
>> Servers working.
>> The server works, but the reason that it's not blowing up hydration is because this line blows up before we can hydrate.

[LAUGH] So it's actually not hydrating. So that's fun. The whole app actually still works even though we're not hydrating. That's hilarious. That's fun. Okay, so yes, this is not going to work if you wanna have JavaScript to work. So what are we gonna do? Well, we actually are going to use a loader for this.

We're gonna go into our root. And we'll have an ENV as prose or admin email process.env.ADMIN_EMAIL. And now I can get my data from use loader data. And I'm going to add a script with dangerously set inner HTML. I'm going to generate JavaScript, window.env equals JSON stringify data.env.

There we go. You got a format code like a Neanderthal. Okay, now we have a global env in the client. And we have a global env on the server type scripts and I'm happy but we can fix that later. Ta-da, admin email and no full page refreshes anymore, hooray!

Okay, so quick review for the server side, we can go in, in our entry server say global.ENV. And so now when this code runs on the server, that server renders just fine because it's on global. That's how you do it in node. And then on the client, we're going into our app right here.

And we're going to use our loader to get it from the server into the client. Interestingly, you could actually say, global.env right here, and that would also work. Here you go because it's global, cool. All right, great, so let's make TypeScript not super angry about this. I'm going to make a file called env.server.Ts and this is going to have a function called get env and that's going to return my env and we're going to export that function so nope.

Thanks though admin email. There we go. And so now we can come in here. We'll say getEnv and an entry server getEnv. Okay, pleasure skill is still not happy about this. So the next thing we're going to do, I actually I think I just gave this to you, because it's kind of TypeScript stuff.

And this is not a TypeScript workshop. So we'll come over here to declare global ENV. So what how do we get the type of the ENV? We're going to say type ENV equals type of getEnv and we want the return type of that. So return type, nice, handy generic to get the type of the env.

So now, our env has the admin email. This is great because in our files where we want to start referencing this we can say env.get some autocomplete. So that's fancy. But that could be a string or undefined. In most cases, you're not going to function properly if your environment variables are not defined.

So the last thing we're going to do is say invariant on admin email. And Admin email is not set. So now our env will require the admin email. And if for some reason that's not defined, then you'll get an error on startup. And so that your server won't startup, won't deploy, and your old app will still work, hopefully.

The existing deploy should still work, hopefully, and everything should be golden. And now you have a way where you can anywhere in the app reference your global environment variables because you set it to global here, and you set it to global there. Again, please don't put like a ton of nonsense on this object.

It really should just be the stuff you want in the client, and just the stuff that doesn't change during the lifetime of the app should just be environment variables, okay? Great, that was fun. Any questions or anything before we move on?
>> Is this how you'd recommend approaching this in production?

Or is it more of a demo and there's a package plug in that handles the ENV passing like this for you?
>> Yeah, good question. So I think at the heart of the question is, hmm, that seems like a lot of work. [LAUGH] I'd rather not have to do that myself at least I'm not trying to downplay or anything.

I don't think that I would install a package for this if it existed just because I don't think it's that much work myself. And I'd prefer to have the code in front of me so I can modify it as needed without having to learn some package. This is absolutely how I do it in production.

I didn't mention it before, but my app is 27,000 lines of code and when we're talking remix, that's actually pretty significant. Because Remix is like, a lot smaller than what I used to write. So and it works great for me. So I don't think that I'm unaware of a package that does this but really in essence you literally just have one file that holds a function for getting the environment variables you want shared across.

Do you go to your entry server to set this and then you go to your route to set this width and then there's a little bit of work in the loader I suppose. But other than that that's kind of it so I'm not sure I would care to do that any differently.

Learn Straight from the Experts Who Shape the Modern Web

  • In-depth Courses
  • Industry Leading Experts
  • Learning Paths
  • Live Interactive Workshops
Get Unlimited Access Now