How toUse `process.env` client-side with Remix

The process.env thing is a Node.js-only feature that many front-end developers adopted as something to use in their client-side code.

Usually backed by a build tool like webpack inlining the env variables at build time, they're not actual env variables but more like build variables.

In Remix, they decided only to inline the value of process.env.NODE_ENV because it's a commonly used variable in many libraries, including React, to optimize the code for production. The only way to access any other env variables is server-side, which means in your loaders and actions.

But we need to use one of these variables in a function used both client-side and server-side, for example, in the UI.

First, we need to return the variable from a loader.

export async function loader() {
  return json({ variable: process.env.VARIABLE });
}

Then we could access it with useLoaderData and pass it to our function.

export default function View() {
  let { variable } = useLoaderData<typeof loader>();
  doSomething(variable);
  // ...
}

Doing it this way is ideal because our code doesn't depend on process.env to be a thing, which for example, in Cloudflare works doesn't exist, and the only way to access env variables is from the loader/action context object.

export async function loader({ context }: LoaderArgs) {
  return json({ variable: context.VARIABLE });
}

But let's decide we're using Node, and we still want to access variables directly as process.env.VARIABLE in our code.

Remix docs suggest returning the env variables from the root route loader and using an inline script to make them globally available client-side.

//example copied from Remix docs
export async function loader() {
  return json({ ENV: { VARIABLE: process.env.VARIABLE } });
}

export function Root() {
  const data = useLoaderData();
  return (
    <html lang="en">
      <head>
        <Meta />
        <Links />
      </head>
      <body>
        <Outlet />
        {/* This is the inline script tag */}
        <script
          dangerouslySetInnerHTML={{
            __html: `window.ENV = ${JSON.stringify(data.ENV)}`,
          }}
        />
        <Scripts />
      </body>
    </html>
  );
}

Doing this, we can do ENV.VARIABLE client-side, but not server-side, so our function would have to detect if it's running on the server using process.env.VARIABLE and otherwise use ENV.VARIABLE, but here's the trick, we can set process into the global scope too so that we can use process.env.VARIABLE client-side too.

<script
  dangerouslySetInnerHTML={{
    __html: `window.process = ${JSON.stringify({
      env: data.ENV,
    })}`,
  }}
/>

Now, our code can use window.process.env.VARIABLE or just process.env.VARIABLE, as in our server-side code.