Environment variables

Complete reference for all environment variables

This boilerplate uses environment variables for configuration. All sensitive credentials and environment-specific settings should be stored in .env files.

Quick start

  1. Copy the example file: cp .env.example .env
  2. Configure required core variables (see below)
  3. Add optional integrations as needed
  4. Restart your dev server
Never commit .env files to version control. The .gitignore file already excludes them.

Variables reference

VariableRequiredScopeDescription
NUXT_PUBLIC_SITE_URLPublicYour site's public URL
NUXT_PUBLIC_SITE_NAMEPublicSite name (emails, meta tags)
NUXT_PUBLIC_SITE_DOMAINPublicDomain name (email sender)
NUXT_PUBLIC_BLOG_SOURCEPublicBlog source mode: md (Nuxt Content) or db (posts API/database)
DATABASE_URLServerPostgreSQL connection string
DATABASE_POOL_MAXServerpg.Pool max connections (default 10)
DATABASE_POOL_TIMEOUT_MSServerpg.Pool connection timeout in ms (default 20000)
RESEND_API_KEYServerResend API key for email delivery (OTPs log to console when unset)
BETTER_AUTH_SECRETServerSecret for signing auth tokens
BETTER_AUTH_URLServerBase URL for authentication
BETTER_AUTH_TRUSTED_ORIGINSServerExtra CORS / better-auth trusted origins (comma-separated)
BETTER_AUTH_TRUSTED_PROXIESServerIPs/CIDRs of proxies in front of the app (comma-separated) — see below
CONTACT_FORM_TO_EMAILSServerContact form recipient email(s) (comma-separated for multiple)
NUXT_PUBLIC_DISPLAY_CONTACT_EMAILPublicPublic email address displayed in the contact form
STRIPE_SECRET_KEYServerStripe secret key
NUXT_PUBLIC_STRIPE_PUBLISHABLE_KEYPublicStripe publishable key
NUXT_PUBLIC_STRIPE_LIVE_MODEPublicBuild-time flag: true selects live Stripe price IDs from payments.config.ts
STRIPE_WEBHOOK_SECRETServerStripe webhook signing secret
OPENAI_API_KEYServerOpenAI API key
ANTHROPIC_API_KEYServerAnthropic API key
GROK_API_KEYServerGrok / xAI API key
IPINFO_TOKENServerIPinfo geolocation token
NUXT_PUBLIC_UMAMI_IDPublicUmami website ID
NUXT_PUBLIC_UMAMI_HOSTPublicUmami host URL
NUXT_PUBLIC_VERCEL_ANALYTICSPublicEnable Vercel Analytics

Required variables

Site configuration

# Your site's public URL
NUXT_PUBLIC_SITE_URL="http://localhost:3000"

# Site name (used in emails, meta tags, etc.)
NUXT_PUBLIC_SITE_NAME="Your App Name"

# Domain name (used in email sender addresses)
NUXT_PUBLIC_SITE_DOMAIN="yourdomain.com"

Database

# PostgreSQL connection string
DATABASE_URL="postgresql://user:password@localhost:5432/database?sslmode=require"

# Optional pg.Pool overrides (configured in lib/prisma.ts — not URL query params)
# DATABASE_POOL_MAX=10
# DATABASE_POOL_TIMEOUT_MS=20000

Format: postgresql://[user]:[password]@[host]:[port]/[database]

Pool sizing is set on the pg.Pool in lib/prisma.ts (max / connectionTimeoutMillis), with optional DATABASE_POOL_MAX and DATABASE_POOL_TIMEOUT_MS overrides. Do not put Prisma-style connection_limit / pool_timeout on DATABASE_URL — this project uses the PrismaPg driver adapter, and pg ignores those keys.

Email (optional)

# Optional: Resend API key for sending emails
RESEND_API_KEY="re_..."
See the email integration guide for setup instructions. Without this key, local startup still works and OTPs are logged to the server console so passwordless login remains usable.

Authentication

# Better Auth secret key (generate with: openssl rand -base64 32)
BETTER_AUTH_SECRET="your-secret-key-here"

# Base URL for authentication
BETTER_AUTH_URL="http://localhost:3000"

# Optional: extra trusted origins for Capacitor / cross-origin clients
# Capacitor origins are always included; this adds your own (comma-separated)
BETTER_AUTH_TRUSTED_ORIGINS=

# Optional: IPs/CIDRs of proxies in front of the app (comma-separated)
BETTER_AUTH_TRUSTED_PROXIES=

Trusted proxies and rate limiting

better-auth buckets rate limits per client IP, which it reads from x-forwarded-for and then x-real-ip. It only trusts x-forwarded-for when the header contains a single address, unless you declare the proxies in front of the app:

# Cloudflare in front of your platform edge, for example
BETTER_AUTH_TRUSTED_PROXIES=173.245.48.0/20,103.21.244.0/22,10.0.0.0/8

Leave it empty when the app is not behind a proxy, or when your proxy replaces x-forwarded-for with a single address (nginx with proxy_set_header X-Real-IP is fine as-is). If the header carries multiple hops and no proxies are declared, the client IP cannot be resolved and every request falls into one shared rate-limit bucket — better-auth logs a warning once when this happens.

See the authentication guide for setup instructions.
Never commit your actual BETTER_AUTH_SECRET to version control. Keep it secure and regenerate it if exposed.

Optional variables

Contact form

# Email address(es) to receive contact form submissions (comma-separated for multiple)
CONTACT_FORM_TO_EMAILS="[email protected]"

# Public email address displayed in the contact form
NUXT_PUBLIC_DISPLAY_CONTACT_EMAIL="[email protected]"

Contact form submissions are handled by /api/contact-request/create, stored in the Inquiry table, and then forwarded to configured recipient emails.

Blog content source

# Optional: blog/changelog source mode (defaults to "md" when unset)
# md = Nuxt Content markdown files
# db = database-backed posts via /api/posts
NUXT_PUBLIC_BLOG_SOURCE="md"

This maps to config.public.blogSource in runtime config.

Stripe payments

# Stripe secret key (server-side)
STRIPE_SECRET_KEY="sk_test_..."

# Stripe publishable key (client-side)
NUXT_PUBLIC_STRIPE_PUBLISHABLE_KEY="pk_test_..."

# Use live Stripe price IDs from payments.config.ts (must be set at build time)
NUXT_PUBLIC_STRIPE_LIVE_MODE="false"

# Webhook signing secret from Stripe dashboard
STRIPE_WEBHOOK_SECRET="whsec_..."
Payment mode (auth or authless) is configured in shared/config/payments.config.ts, not in environment variables. See the payments integration guide for Stripe setup, including NUXT_PUBLIC_STRIPE_LIVE_MODE.

AI providers

# OpenAI API key for GPT models
OPENAI_API_KEY="sk-..."

# Anthropic API key for Claude models
ANTHROPIC_API_KEY="sk-ant-..."

# Grok / xAI API key
GROK_API_KEY="xai-..."
See the AI integration guide for API key setup.

Geolocation (IPinfo)

# IPinfo API token for IP geolocation
IPINFO_TOKEN="your_token"
See the geolocation guide for setup instructions.

Analytics

# Umami Analytics (both required to enable)
NUXT_PUBLIC_UMAMI_ID="your-website-id"
NUXT_PUBLIC_UMAMI_HOST="https://cloud.umami.is"

# Vercel Analytics (set to any truthy value to enable)
NUXT_PUBLIC_VERCEL_ANALYTICS="true"
See the analytics guide for setup instructions.

Variable naming conventions

Nuxt uses specific prefixes for environment variables:

NUXT_PUBLIC_*

Variables with this prefix are exposed to the client-side:

# ✅ Accessible in browser
NUXT_PUBLIC_SITE_URL="https://example.com"
NUXT_PUBLIC_STRIPE_PUBLISHABLE_KEY="pk_test_..."

Access in components:

<script setup>
const config = useRuntimeConfig()
console.log(config.public.siteUrl) // Works in browser
</script>
Never use NUXT_PUBLIC_ for secrets! These are exposed to the browser and visible in the source code.

Standard variables

Variables without a special prefix are server-side only:

# ✅ Only accessible on server
DATABASE_URL="postgresql://..."
STRIPE_SECRET_KEY="sk_test_..."
RESEND_API_KEY="re_..."

Access only in server code:

// ✅ Works in server/api routes
const config = useRuntimeConfig()
console.log(config.stripeSecretKey)

// ❌ Returns undefined in browser

Accessing runtime config

All environment variables are mapped to runtime config in nuxt.config.ts.

In server code:

server/api/example.ts
export default defineEventHandler(event => {
  const config = useRuntimeConfig()

  // Access server-only variables
  const dbUrl = config.databaseUrl
  const stripeKey = config.stripeSecretKey

  // Access public variables
  const siteUrl = config.public.siteUrl
})

In client code:

<script setup>
const config = useRuntimeConfig()

// ✅ Public variables work
console.log(config.public.siteUrl)
console.log(config.public.blogSource) // "md" or "db"

// ❌ Server variables are undefined
console.log(config.stripeSecretKey) // undefined
</script>

Environment-specific configuration

You can create different .env files for each environment:

  • .env - Local development
  • .env.staging - Staging environment
  • .env.production - Production environment
Use test/sandbox keys in development and staging, and live keys only in production.

Platform-specific configuration

Vercel

Add environment variables in the Vercel dashboard:

  1. Go to your project settings
  2. Navigate to "Environment Variables"
  3. Add each variable with appropriate scope (Production/Preview/Development)

Netlify

Add in Netlify dashboard or netlify.toml:

netlify.toml
[context.production.environment]
  NUXT_PUBLIC_SITE_URL = "https://yourdomain.com"

Docker

Use environment variables in docker-compose.yml:

docker-compose.yml
version: '3'
services:
  app:
    build: .
    environment:
      - DATABASE_URL=${DATABASE_URL}
      - NUXT_PUBLIC_SITE_URL=${NUXT_PUBLIC_SITE_URL}

Or use an .env file:

services:
  app:
    env_file:
      - .env.production

Security best practices

  • Never commit .env files - already excluded in .gitignore
  • Use test keys in development/staging, live keys only in production
  • Rotate keys after team changes or if exposed
  • Use restricted API keys with minimal required permissions (especially for Stripe and database)

Troubleshooting

Variables not loading

  1. Check file name: Must be exactly .env
  2. Restart dev server: Changes require restart
  3. Check syntax: No spaces around =
# ❌ Wrong
VARIABLE = "value"

# ✅ Correct
VARIABLE="value"

Public variables returning undefined

Add NUXT_PUBLIC_ prefix:

# ❌ Not accessible in browser
SITE_URL="https://example.com"

# ✅ Accessible in browser
NUXT_PUBLIC_SITE_URL="https://example.com"

Runtime config not updating

Clear Nuxt cache and restart:

rm -rf .nuxt
pnpm dev

Additional resources

For production, consider using a secrets management service like AWS Secrets Manager or HashiCorp Vault.