For the complete documentation index, see llms.txt. Prefer markdown by appending .md to documentation URLs or sending Accept: text/markdown.

Secrets & environment

Keep secrets out of NEXT_PUBLIC_ variables and git. Use envin presets, .env.local for local secrets, and your host for production values.

Environment variables are the easiest place to accidentally publish a secret. TurboStarter validates config with envin and splits shared, app, and secret values so the secure default is clear.

For the full file layout, see Environment variables. This page focuses on the security rules.

Public vs private variables

Next.js only embeds variables prefixed with NEXT_PUBLIC_ into client bundles.

PrefixAvailable whereUse for
(none)Server onlyAPI keys, webhook secrets, database URLs, Better Auth secret
NEXT_PUBLIC_Server and browserProduct name, public URL, theme, feature flags that are safe to expose
Safe ✅
# Server-only - never prefix these
BETTER_AUTH_SECRET="..."
DATABASE_URL="postgresql://..."
STRIPE_SECRET_KEY="..."
STRIPE_WEBHOOK_SECRET="..."
LEMON_SQUEEZY_API_KEY="..."
LEMON_SQUEEZY_SIGNING_SECRET="..."

# Public - intentionally visible to the browser
NEXT_PUBLIC_PRODUCT_NAME="TurboStarter"
NEXT_PUBLIC_URL="https://app.example.com"
NEXT_PUBLIC_DEFAULT_LOCALE="en"
Unsafe - never do this ❌
NEXT_PUBLIC_STRIPE_SECRET_KEY="sk_live_..."
NEXT_PUBLIC_DATABASE_URL="postgresql://..."
NEXT_PUBLIC_BETTER_AUTH_SECRET="..."

Public means public

If a value has NEXT_PUBLIC_, assume every visitor can read it from the JS bundle. That includes “obscure” identifiers that still grant privileged access to a third-party API.

Secrets architecture

EnvironmentStore secrets inCommitted?
Local developmentRoot / app .env.localNo (gitignored)
CI / productionHost secrets (Vercel, Railway, GitHub Actions, …)No
Examples / docs.env.example with empty or dummy valuesYes

Never put real secrets in .env, .env.development, or .env.production if those files are committed. Use .env.example only as a template.

Auth secrets are declared as server fields in the auth env preset - they are not available to client code by design:

packages/auth/src/env.ts
export const preset = {
  id: "auth",
  server: {
    BETTER_AUTH_SECRET: z.string(),
    GOOGLE_CLIENT_ID: z.string().optional().default(""),
    GOOGLE_CLIENT_SECRET: z.string().optional().default(""),
    // ...
  },
} as const satisfies Preset;

Billing, storage, database, and API packages follow the same pattern: privileged keys live in package ./env presets and are imported only from server entry points.

Generate a strong auth secret

BETTER_AUTH_SECRET signs sessions and related tokens. Use a long random value in every environment:

openssl rand -base64 32

Rotate it carefully - changing the secret invalidates existing sessions.

Production checklist for env

  1. Set every required secret in the deployment platform (not in git)
  2. Confirm no NEXT_PUBLIC_ prefix on API keys, webhook secrets, or DB URLs
  3. Use separate secrets for test vs live billing providers
  4. Keep URL / NEXT_PUBLIC_URL aligned with the real production origin (auth callbacks and cookies depend on it)
  5. Remove seed defaults (SEED_EMAIL / SEED_PASSWORD) from production or change them immediately after first deploy

Environment variables

Shared vs app-specific files, local overrides, and NEXT_PUBLIC_ injection.

How is this guide?

Last updated on

On this page

Ship your startup everywhere. In minutes.Try TurboStarter