Stop trusting your .env file

A personal take on how I stopped shipping broken Vite builds by validating environment variables before the build even starts. No JSON schema, no magic — just JavaScript and clear rules. A blunt tool for a stupidly common problem After building more React apps with Vite than I care to admit, I noticed a recurring theme: most of the time, what broke wasn't the code — it was the environment. A missing VITE_ variable. A malformed URL. A boolean that was actually a string. A Docker ARG that never made it to the final build. And of course, the worst kind: it didn't crash the build... it crashed the user experience. I got tired of catching these bugs in staging (or worse, production), so I built a tool that would blow up my build early — on purpose. It's called envguardr, and it's dead simple. The problem with Vite and environment variables Vite helpfully injects any variable prefixed with VITE_ into import.meta.env, but: It doesn't check if the variable is actually defined. It doesn't validate types. It assumes everything is a string, and that's your problem now. You can write if (VITE_ENABLE_FEATURE) and end up with true, "true", "banana", or undefined — and Vite will happily compile it. What I use now (and why it finally works) My stack: React + Vite frontend, Nginx for static serving, Docker multistage for builds. Nothing exotic. What changed was the discipline — and the tooling. Here’s how I validate my environment before anything gets built: // env.schema.js export default { VITE_API_URL: { type: 'url', required: true }, VITE_APP_VERSION: { type: 'string', required: true }, VITE_APP_ENV: { type: { enum: ['staging', 'production'] }, default: 'production' }, } That’s it. No YAML, no JSON schema. Just JavaScript and some rules that make sense. The Dockerfile FROM node:18 AS builder ARG VITE_API_URL ARG VITE_APP_VERSION ARG VITE_APP_ENV ENV VITE_API_URL=$VITE_API_URL ENV VITE_APP_VERSION=$VITE_APP_VERSION ENV VITE_APP_ENV=$VITE_APP_ENV WORKDIR /app COPY . . RUN npm ci RUN npx envguardr validate ./env.schema.js RUN npm run build FROM nginx:alpine COPY --from=builder /app/dist /usr/share/nginx/html Validation happens before the build. If anything’s missing or wrong, it fails fast — as it should. What changed Since I added envguardr: I haven’t shipped a broken build. I don’t debug missing env vars in production anymore. My CI logs finally tell me what’s wrong instead of silently succeeding. No magic. Just boundaries. For anyone who's ever dealt with process.env in Docker This isn't another generic linter. It's a purpose-built CLI for one thing: making sure your environment is what your app actually needs. You write a schema. You run envguardr. It validates your env. If something's wrong, it kills the build. That's the whole point. Try it npm install --save-dev envguardr npx envguardr validate ./env.schema.js I built envguardr to protect myself from careless builds. It just happens to work for other people too.

May 10, 2025 - 18:57
 0
Stop trusting your .env file

A personal take on how I stopped shipping broken Vite builds by validating environment variables before the build even starts. No JSON schema, no magic — just JavaScript and clear rules.

A blunt tool for a stupidly common problem

After building more React apps with Vite than I care to admit, I noticed a recurring theme: most of the time, what broke wasn't the code — it was the environment.

A missing VITE_ variable. A malformed URL. A boolean that was actually a string. A Docker ARG that never made it to the final build. And of course, the worst kind: it didn't crash the build... it crashed the user experience.

I got tired of catching these bugs in staging (or worse, production), so I built a tool that would blow up my build early — on purpose.

It's called envguardr, and it's dead simple.

The problem with Vite and environment variables

Vite helpfully injects any variable prefixed with VITE_ into import.meta.env, but:

  1. It doesn't check if the variable is actually defined.
  2. It doesn't validate types.
  3. It assumes everything is a string, and that's your problem now.

You can write if (VITE_ENABLE_FEATURE) and end up with true, "true", "banana", or undefined — and Vite will happily compile it.

What I use now (and why it finally works)

My stack: React + Vite frontend, Nginx for static serving, Docker multistage for builds. Nothing exotic. What changed was the discipline — and the tooling.

Here’s how I validate my environment before anything gets built:

// env.schema.js
export default {
  VITE_API_URL: { type: 'url', required: true },
  VITE_APP_VERSION: { type: 'string', required: true },
  VITE_APP_ENV: { 
    type: { enum: ['staging', 'production'] },
    default: 'production' 
  },
}

That’s it. No YAML, no JSON schema. Just JavaScript and some rules that make sense.

The Dockerfile

FROM node:18 AS builder

ARG VITE_API_URL
ARG VITE_APP_VERSION
ARG VITE_APP_ENV

ENV VITE_API_URL=$VITE_API_URL
ENV VITE_APP_VERSION=$VITE_APP_VERSION
ENV VITE_APP_ENV=$VITE_APP_ENV

WORKDIR /app
COPY . .

RUN npm ci
RUN npx envguardr validate ./env.schema.js
RUN npm run build

FROM nginx:alpine
COPY --from=builder /app/dist /usr/share/nginx/html

Validation happens before the build. If anything’s missing or wrong, it fails fast — as it should.

What changed

Since I added envguardr:

  • I haven’t shipped a broken build.
  • I don’t debug missing env vars in production anymore.
  • My CI logs finally tell me what’s wrong instead of silently succeeding.

No magic. Just boundaries.

For anyone who's ever dealt with process.env in Docker

This isn't another generic linter. It's a purpose-built CLI for one thing: making sure your environment is what your app actually needs.

You write a schema. You run envguardr. It validates your env. If something's wrong, it kills the build.

That's the whole point.

Try it

npm install --save-dev envguardr
npx envguardr validate ./env.schema.js

I built envguardr to protect myself from careless builds. It just happens to work for other people too.