Environment and config
Skill param087/saas-starter-skills/skills/environment-and-config
Production-grade full-stack SaaS skills for AI coding agents — Next.js, Postgres/Drizzle, Auth, Stripe & Vercel patterns for Codex, Claude Code, Cursor & OpenCode.
npx -y skills add param087/saas-starter-skills --skill environment-and-configAssembled from the repository path, not quoted from the project. Check it against their README if it does not work.
One thing to look at
- 1 stars1 stars. Stars are a popularity signal and not a quality one, but at this level it is likely that nobody has read this closely except its author, and you would be relying on your own review.
What its author says it does
Copied from the file, not written here
Use when wiring up env vars, secrets, or runtime config — validate them with zod at boot, split server-only from public (NEXT_PUBLIC_) values, and fail fast with a clear error instead of a cryptic undefined at runtime.
SKILL.md
2.8 KB, as published. Nobody here has run it
Environment & Config
Overview
Untyped process.env is a runtime landmine: a missing STRIPE_SECRET_KEY shows up as undefined three layers deep at 2am. Validate the whole environment once at boot with zod, expose a typed env object, and make a misconfiguration a startup crash with an actionable message.
When to use
- Adding any new secret or config value.
- A bug traces back to an undefined/empty env var.
- You need to keep server secrets out of the client bundle.
The pattern
// src/env.ts
import { z } from "zod";
const server = z.object({
DATABASE_URL: z.string().url(),
AUTH_SECRET: z.string().min(32),
STRIPE_SECRET_KEY: z.string().startsWith("sk_"),
STRIPE_WEBHOOK_SECRET: z.string().startsWith("whsec_"),
RESEND_API_KEY: z.string().startsWith("re_"),
NODE_ENV: z.enum(["development", "test", "production"]).default("development"),
});
const client = z.object({
NEXT_PUBLIC_APP_URL: z.string().url(),
});
const parsed = server.merge(client).safeParse(process.env);
if (!parsed.success) {
console.error("❌ Invalid environment:", parsed.error.flatten().fieldErrors);
throw new Error("Invalid environment variables");
}
export const env = parsed.data;
Import env everywhere instead of process.env. TypeScript now knows every key exists and its type.
Server vs client
- Only
NEXT_PUBLIC_*vars reach the browser. Never prefix a secret withNEXT_PUBLIC_. - Keep a
clientschema separate so it's obvious which values are public. - For strict isolation, put
import "server-only"in aenv.server.tsand only merge public keys into a client-safe export.
Operational hygiene
- Commit a
.env.examplewith every key (empty or dummy values) — it's the contract for new devs and CI. - Add
.env*.localto.gitignore; never commit real secrets. - In CI/prod, set vars in the platform (Vercel/Doppler/1Password), not in files.
Pitfalls
- Reading
process.envdirectly — bypasses validation and types; you lose the safety net. NEXT_PUBLIC_on a secret — it gets inlined into client JS and shipped to every visitor.- Validating lazily per-request — validate once at module load so boot fails loudly.
- No
.env.example— onboarding becomes guesswork and CI breaks mysteriously. - Trusting defaults in prod — require critical vars; only default the harmless ones.
Hand-off
A typed env that the whole app trusts. database-schema, authentication, payments-stripe, and transactional-email all read their secrets from here.