Environment variables and secrets
submitComment from part 15 logs a comment to the console — a real implementation saves it to a database, which means a connection string that has no business being hardcoded in lib/posts.ts or committed to git. This part covers where that actually goes.
.env.local
# .env.local
DATABASE_URL="postgres://user:password@localhost:5432/devnotes"# .gitignore — already includes this by default in create-next-app
.env*.localNext.js loads .env.local automatically — no package to install, no manual dotenv import. create-next-app already gitignores it by default specifically so a real secret never gets committed by accident.
Reading it — server-side only, by default
// lib/db.ts
const connectionString = process.env.DATABASE_URL;process.env.DATABASE_URL here works only in code that runs on the server — inside a Server Component, a Server Action, or a Route Handler, matching exactly the server-only code covered in part 3. This is the safe default: nothing about it is ever sent to the browser.
The NEXT_PUBLIC_ prefix: a real boundary, not a formality
# .env.local
NEXT_PUBLIC_SITE_URL="https://devnotes.example.com"
DATABASE_URL="postgres://user:password@localhost:5432/devnotes"// a Client Component
"use client";
export function ShareButton() {
const url = process.env.NEXT_PUBLIC_SITE_URL;
// ...
}Only variables prefixed exactly NEXT_PUBLIC_ get inlined into the client-side JavaScript bundle at build time — DATABASE_URL above, with no prefix, is undefined in any Client Component, by design. This isn't a convention to remember to follow; it's how Next.js's build process actually works, and it's the reason a real secret should never be prefixed NEXT_PUBLIC_ even temporarily while testing something — once it's built with that prefix, it's in the shipped JavaScript, readable by anyone who opens devtools.
Different values per environment
.env.local — local dev only, gitignored, real secrets
.env.development — committed, shared dev defaults
.env.production — committed, shared production defaults.env.local always wins over the more general .env.development/.env.production files when present — the standard pattern is committing non-sensitive shared defaults in the latter, while every real secret lives only in .env.local locally and in the hosting platform's own environment variable settings in production (part 22 covers exactly that).
Prefixing a variable NEXT_PUBLIC_ "just to make sure it's available everywhere" without checking whether it's actually meant to be public. NEXT_PUBLIC_SITE_URL above is genuinely fine to expose — it's already visible in every page's URL bar. A database connection string, an API key with write access, a JWT signing secret: never this prefix, regardless of where in the code it's needed.
Next: rendering strategies — this series has used static generation without naming it as such; time to cover SSG, SSR, and ISR as the deliberate choices they actually are.