Env Diff

Compares two environments key by key and flags missing keys, localhost in production and reused secrets, masking secrets so the diff is safe to share.

StopsProd still pointing at localhost

Installation

pnpm dlx shadcn@latest add https://bollard-ui.vercel.app/r/env-diff.json

The files land in your project. They're yours to edit.

Usage

env-diff-usage.tsx
import { EnvDiff } from "@/components/ui/env-diff"

return <EnvDiff base={staging} target={production} baseName="staging" targetName="production" />

What it catches

  • Keys missing or empty in one environment
  • Production values that point at localhost, a test key or a staging host
  • Secrets reused across both environments
  • A copy of the missing keys, ready to fill

Props

EnvDiff props
PropTypeDefault
base* (required)

Reference environment: dotenv text or a key/value map.

string | Record<string, string>—
target* (required)

Environment being checked against the reference.

string | Record<string, string>—
baseName

Name of the reference environment. Also used to spot its values leaking into the target.

string"staging"
targetName

Name of the environment being checked.

string"production"
production

Treat the target as production: test keys and localhost values are flagged. Defaults to a name containing "prod" or "live".

boolean/\b(prod|production|prd|live)\b/i.test(targetName)
isSecret

Decides which keys are secret. Defaults to names containing SECRET, TOKEN, PASSWORD, KEY, DSN…

(key: string) => boolean—
showSame

Show unchanged keys too. They are hidden by default so the real differences stand out.

booleanfalse
statusLabels

Badge text per status. `{target}` is filled in.

Partial<Record<EnvDiffStatus, string>>—
hints

Explanation per status. `{base}`, `{target}` and `{reason}` are filled in.

Partial<Record<EnvDiffStatus, string>>—
cleanText

Summary when nothing needs attention.

string"{target} has everything {base} has."
problemsText

Summary when something does. `{count}` is filled in.

string"{count} to fix before deploying {target}"
copyMissingLabel

Label of the button that copies the missing keys as a KEY= template. `{count}` is filled in.

string"Copy {count} missing"
copiedLabel

Label shown after copying.

string"Copied"
showSameLabel

Label of the toggle for unchanged keys. `{count}` is filled in.

string"Show {count} unchanged"
hideSameLabel

Label of the same toggle while unchanged keys are shown.

string"Hide unchanged"
notSetText

Shown where a key isn't set.

string"not set"
emptyText

Shown where a value is an empty string.

string"empty"
maskedText

Read by screen readers for a masked value. `{fingerprint}` is filled in.

string"Masked value, fingerprint {fingerprint}. Matching finger…
secretText

Read by screen readers next to a secret key.

string"Secret"

* required. Also accepts the native props of <div> (className, aria-*, data-*…), passed through to it.

Accessibility

  • The comparison is a labelled list; masked dots are aria-hidden and secrets have a labelled icon
  • Show-same toggle uses aria-pressed
  • Copy feedback is aria-live="polite"

Only what the source does. Test with your own screen reader and content before shipping.

Limitations

This is a UX guard rail. Enforce on the server: Validate required config at deploy time on the server, and keep secrets in a secrets manager rather than in shared diffs.

  • Fingerprints are 4-hex-digit FNV hashes: fine for spotting reuse, but they can help confirm guesses of short, weak secrets, so don't share diffs of weak secrets
  • Which keys are secret is decided by isSecret or name patterns

What it stops

Prod still pointing at localhost

Also known as

.env · environment variables · config · staging · production · vercel env · secrets · deploy

Field type

Dependencies

Pairs well with