Cheat SheetsFull-Stack IntegrationEnvironment & Config

Environment & Config — Cheat Sheet

Full-Stack Integration · 2 topics. Download the PDF or the Instagram carousel and share it.

Cheat Sheet · AiCanCode.org
Environment & Config
Full-Stack Integration2 topicsQuick revision reference
1

Configuration Across Two Deployables

Two apps, two sets of variables, and one hard rule: anything the browser can read is public forever, because it is compiled into the bundle.

  • A NEXT_PUBLIC_ or VITE_ variable is inlined at build time and published in every shipped bundle
  • Server-only modules should import "server-only" so a client import fails the build instead of leaking a secret
  • Validate and coerce the whole environment at startup so a missing value crashes on boot, not on the first request
  • Commit .env.example as the onboarding document and keep real values per environment in Vercel and Fly
  • A leaked secret must be rotated, not scrubbed — and credentials in a query string end up in access logs
The prefix is a publication decision
# Frontend (.env.local) — NEXT_PUBLIC_ is inlined into the bundle
NEXT_PUBLIC_API_URL=https://api.aicancode.org
NEXT_PUBLIC_RAZORPAY_KEY_ID=rzp_live_xxx      # publishable by design
NEXT_PUBLIC_POSTHOG_KEY=phc_xxx

# Backend (Fly secrets) — never leaves the server
DATABASE_URL=postgres://...
JWT_SECRET=...
RAZORPAY_KEY_SECRET=...
OPENAI_API_KEY=...

// The rule: NEXT_PUBLIC_ (or VITE_) is a declaration that this value
// is public. It is substituted at BUILD time and lives in the shipped
// JavaScript forever — including in every previously deployed bundle.

// So a secret placed there is not "leaked if someone looks", it is
// published. Rotate it; you cannot unpublish it.

// A server-only variable is simply unprefixed, and reading it from a
// client component returns undefined by design:
process.env.JWT_SECRET        // undefined in the browser

// The dangerous middle: Next.js server components and route handlers
// CAN read secrets — and importing such a module into a client
// component is how a secret ends up in the bundle. Mark them:
import 'server-only'          // build fails if a client imports it
2

Running Both Locally

Two processes, a database and a proxy decision. The goal is that a new developer is productive in one command, and that local behaviour predicts production.

  • Run infrastructure in Docker and the apps natively for fast reloads and a usable debugger
  • Choose proxy or direct to match production — a mismatch hides cookie and CORS failures until deploy
  • Cookies on localhost are same-site even across ports, which is why cookie auth can pass locally and fail in production
  • Use a seed script rather than production data, and keep migrations single-headed and in version control
  • Case-sensitive filesystems, serverless writes and real latency are the differences that produce production-only bugs
Infra in Docker, apps native, one command
# Terminal 1 — API
cd tool-hub-api && uvicorn app.main:app --reload --port 8000

# Terminal 2 — web
cd ToolHub && npm run dev            # :3000

# One command instead, with concurrently or a Makefile
"dev": "concurrently -n api,web -c blue,green \
        \"cd ../api && uvicorn app.main:app --reload\" \"next dev\""

# docker compose, when the stack has more moving parts
services:
  db:    { image: postgres:16, ports: ["5432:5432"],
           environment: { POSTGRES_PASSWORD: dev } }
  redis: { image: redis:7, ports: ["6379:6379"] }
  api:   { build: ./api, ports: ["8000:8000"], depends_on: [db, redis],
           volumes: ["./api:/app"] }        # bind mount for hot reload

# Run infrastructure in Docker and the apps natively: you get fast
# reloads and a real debugger, without installing Postgres by hand.

# The target is a README with three lines:
#   cp .env.example .env.local
#   docker compose up -d
#   npm run dev
Learn this free with Aria, your AI tutor → AiCanCode.org/learn/full-stack