For the complete documentation index, see llms.txt. Prefer markdown by appending.mdto documentation URLs or sendingAccept: 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.
| Prefix | Available where | Use for |
|---|---|---|
| (none) | Server only | API keys, webhook secrets, database URLs, Better Auth secret |
NEXT_PUBLIC_ | Server and browser | Product name, public URL, theme, feature flags that are safe to expose |
# 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"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
| Environment | Store secrets in | Committed? |
|---|---|---|
| Local development | Root / app .env.local | No (gitignored) |
| CI / production | Host secrets (Vercel, Railway, GitHub Actions, …) | No |
| Examples / docs | .env.example with empty or dummy values | Yes |
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:
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 32Rotate it carefully - changing the secret invalidates existing sessions.
Production checklist for env
- Set every required secret in the deployment platform (not in git)
- Confirm no
NEXT_PUBLIC_prefix on API keys, webhook secrets, or DB URLs - Use separate secrets for test vs live billing providers
- Keep
URL/NEXT_PUBLIC_URLaligned with the real production origin (auth callbacks and cookies depend on it) - 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