Remix does not do anything directly with environment variables, 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".
Here are some example environment variables you might find in the wild:
DATABASE_URL
: The URL for a Postgres DatabaseSTRIPE_PRIVATE_KEY
: The key a checkout workflow will use on the serverSTRIPE_PUBLIC_KEY
: The key a checkout workflow will use on the browserIf you're 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 enviornment variable to change the behavior of your app without rebuilding or even redeploying.
Environment variables on your server will be handled by your host, for example:
If your host doesn't have any conventions for environment variables during development, we recommend using dotenv.
If you're using the Remix App Server, you can do this very quickly:
npm add dotenv
touch .env
Edit your .env
file.
SOME_SECRET=super-secret
Then updated your package.json dev script to this:
{
"dev": "node -r dotenv/config node_modules/.bin/remix dev",
"start": "remix-serve build"
}
Now you can access those values in your loaders/actions:
export function loader() {
console.log(process.env.SOME_SECRET);
}
Note that dotenv is only for development, you should not use it in production, so don't do that with your start script, only dev. You'll need to follow your host's guides on adding secrets to your production server.
.env
file to git, the point is that it contains secrets!
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:
Instead we recommmend 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 clientside environment variables in the loaders.
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.
export function loader() {
return {
ENV: {
STRIPE_PUBLIC_KEY: process.env.STRIPE_PUBLIC_KEY,
FAUNA_DB_URL: process.env.FAUNA_DB_URL
}
};
}
export function Root() {
return (
<html lang="en">
<head>
<Meta />
<Links />
</head>
<body>
<Outlet />
<Scripts />
</body>
</html>
);
}
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/>
export function loader() {
return {
ENV: {
STRIPE_PUBLIC_KEY: process.env.STRIPE_PUBLIC_KEY
}
};
}
export function Root() {
let data = useLoaderData();
return (
<html lang="en">
<head>
<Meta />
<Links />
</head>
<body>
<Outlet />
<script
dangerouslySetInnerHTML={{
__html: `window.ENV = ${JSON.stringify(
data.ENV
)}`
}}
/>
<Scripts />
</body>
</html>
);
}
Access the values
import { loadStripe } from "@stripe/stripe-js";
export async function redirectToStripeCheckout(
sessionId
) {
let stripe = await loadStripe(window.ENV.stripe);
return stripe.redirectToCheckout({ sessionId });
}