Environment Variables

This page documents every environment variable used by each package in the monorepo, explains how Next.js loads .env files, and lists which variables are security-sensitive.


How Next.js loads environment variables

Next.js loads .env files in the following order (later files override earlier ones):

  1. .env — base defaults, can be committed
  2. .env.local — local overrides, never committed (gitignored)
  3. .env.development / .env.production — per-environment defaults
  4. .env.development.local / .env.production.local — per-environment local overrides

[!NOTE] Variables are resolved at server startup (development) or build time (production). Changing a .env file requires restarting the dev server or rebuilding.

NEXT_PUBLIC_ prefix

Any variable prefixed with NEXT_PUBLIC_ is inlined into the JavaScript bundle at build time and exposed to the browser. Never put secrets in NEXT_PUBLIC_ variables.

NODE_ENV behavior

NODE_ENVWhen active
developmentnext dev
productionnext build + next start
testtest runners (vitest, jest)

The frontend envConfig.ts conditionally sets the production API URL based on NODE_ENV:

// Pseudocode — actual implementation in frontend-app/app/config/envConfig.ts
const apiBase =
  process.env.NODE_ENV === "production"
    ? "https://api.tolosaerronka.es/api"
    : process.env.NEXT_PUBLIC_API_BASE_URL;

This means you do not need NEXT_PUBLIC_API_BASE_URL in a production build unless you want to override the default domain.


backend-js variables

Source of truth: backend-js/.env.example
Config loader: src/app/config/envConfig.ts

Place your values in backend-js/.env.local for local development.

Database

VariableRequiredExampleDescription
MYSQL_HOSTlocalhostHostname or IP of the MySQL server
MYSQL_USERpakagMySQL user with access to the database
MYSQL_PASSWORDyourpasswordPassword for MYSQL_USER — treat as secret
MYSQL_DATABASEerronkaDatabase name (the schema creates it as erronka)
MYSQL_PORToptional3306MySQL port; defaults to 3306 if unset

[!WARNING] The four MySQL variables can be undefined at runtime if .env.local is missing or misspelled. The MySQL pool will accept undefined values without throwing at startup, but every query will fail. Always verify the pool connects on first request.

JWT

VariableRequiredExampleDescription
JWT_SECRET✅ (in prod)a-very-long-random-string-hereSecret used to sign and verify access tokens
JWT_ACCESS_EXPIRES_INoptional15mAccess token TTL (jsonwebtoken format)
JWT_REFRESH_EXPIRES_DAYSoptional7Refresh token TTL in days

[!WARNING] JWT_SECRET ships with a hardcoded fallback value in src/app/config/envConfig.ts. This fallback is visible in the public repository. Any attacker can use it to forge valid JWT tokens. Always override this variable in production.

Email

VariableRequiredExampleDescription
RESEND_API_KEY✅ (for emails)re_xxxxxxxxxxxxxxxxxxxxAPI key for Resend. Required for all email flows: account activation, password reset, package status notifications

Google Maps

VariableRequiredExampleDescription
GOOGLE_DIRECTIONS_API_KEY✅ (for routes)AIzaSy_xxxxGoogle Directions + Geocoding API key. Required for POST /api/routes/create and POST /api/packages/create

[!NOTE] GOOGLE_DIRECTIONS_API_KEY uses a TypeScript non-null assertion (!) in envConfig.ts, which suppresses compile-time errors but allows undefined to slip through at runtime. The key must be present or route creation and geocoding will throw uncaught errors.

Application URLs

VariableRequiredExampleDescription
TRACKING_BASE_URL✅ (for tracking emails)http://localhost:3000/trackingBase URL prepended to the tracking token in tracking notification emails
RESET_BASE_URL✅ (for auth emails)http://localhost:3000/reset-passwordBase URL prepended to the reset/activation token in auth emails

Tokens and users

VariableRequiredExampleDescription
TRACKING_EXPIRES_DAYSoptional15Number of days before a tracking token expires. Default: 15
DEFAULT_USER_PASSWORD✅ (in prod)ChangeMe123!Temporary password assigned to newly created users until they activate their account. Override in production

[!WARNING] DEFAULT_USER_PASSWORD also has a hardcoded fallback. Every user created while this fallback is active can be accessed with the known default password until they change it.


frontend-app variables

Config loader: frontend-app/app/config/envConfig.ts
Committed defaults: frontend-app/.env

VariableRequiredExampleDescription
NEXT_PUBLIC_API_BASE_URL✅ in devhttp://localhost:3001/apiBackend API base URL. In NODE_ENV=production this is overridden by the hardcoded production domain. Override here to change the production API endpoint
NEXT_PUBLIC_HERE_API_KEYe7HQklfajNcKR8ke8SLwwHZiGzll84RtOU6hYnXS9esHERE Maps API key used for map rendering in the frontend
NEXT_PUBLIC_DOCS_BASE_URLoptionalhttp://localhost:3002/URL to the documentation site; used in navigation links

[!NOTE] All three frontend variables are prefixed with NEXT_PUBLIC_ and therefore embedded in the client bundle. They are visible to anyone who inspects the page source. This is expected for URL configuration; it is only a concern if you accidentally add a secret key here.


docs variables

The docs package (@repo/docs) does not define its own .env variables. It runs a standard Next.js + Nextra build with no runtime secrets. The predev and prebuild scripts regenerate the search index (scripts/generate-search-index.mjs) automatically — no extra configuration is needed.


Security rules

What MUST NOT go in .env or version control

  • MYSQL_PASSWORD
  • JWT_SECRET
  • DEFAULT_USER_PASSWORD
  • RESEND_API_KEY
  • GOOGLE_DIRECTIONS_API_KEY
  • Any SSL certificates or private keys

What .env.local is for

.env.local is gitignored and is the right place for:

  • Local database credentials
  • Local copies of secret API keys for development

What .env (committed) is for

Only non-secret defaults that are safe to share publicly:

  • MYSQL_HOST=localhost (acceptable — obvious for local dev)
  • NEXT_PUBLIC_* variables (by design, these are public)
  • JWT_ACCESS_EXPIRES_IN=15m (safe configuration)

Never put secrets in NEXT_PUBLIC_*

Variables starting with NEXT_PUBLIC_ are inlined into the JavaScript bundle and sent to every browser. Even if the variable is not rendered visibly, it can be extracted by anyone with DevTools.


Quick reference — all variables

PackageVariableSecret?Required?
backend-jsMYSQL_HOSTno
backend-jsMYSQL_USERno
backend-jsMYSQL_PASSWORDyes
backend-jsMYSQL_DATABASEno
backend-jsMYSQL_PORTnooptional
backend-jsJWT_SECRETyes
backend-jsJWT_ACCESS_EXPIRES_INnooptional
backend-jsJWT_REFRESH_EXPIRES_DAYSnooptional
backend-jsRESEND_API_KEYyes
backend-jsGOOGLE_DIRECTIONS_API_KEYyes
backend-jsTRACKING_BASE_URLno
backend-jsRESET_BASE_URLno
backend-jsTRACKING_EXPIRES_DAYSnooptional
backend-jsDEFAULT_USER_PASSWORDyes
frontend-appNEXT_PUBLIC_API_BASE_URLnodev only
frontend-appNEXT_PUBLIC_HERE_API_KEYno*
frontend-appNEXT_PUBLIC_DOCS_BASE_URLnooptional

* HERE Maps keys are client-side by design and can be domain-restricted in the HERE developer console. They are not secret in the traditional sense, but should be restricted to your domain.