Skip to content
← All docs

Troubleshooting

Common issues running or deploying the marketing website, and how to fix them.

The API won't start

"Invalid environment configuration" on boot - a required env value is missing or malformed. The error lists the offending fields. Check website/backend/.env against configuration.md. Common causes: a malformed DATABASE_URL, or a non-numeric PORT / JWT_TTL_HOURS.

Port already in use - another process holds port 4000. Stop it or set a different PORT.

/api/health/db returns { "ok": false }

The API is up but cannot reach PostgreSQL.

  • Confirm PostgreSQL is running and reachable at the host/port in DATABASE_URL.
  • Confirm the role/password and database name exist (see installation.md).
  • For managed Postgres, you likely need TLS - append ?sslmode=require (or ?sslmode=no-verify for self-signed certs) to DATABASE_URL.

Migrations

Tables are missing - run npm run migrate in website/backend. Pending migrations also run automatically on startup, so check the boot logs for "Applied migration" / "No pending migrations".

A migration failed - it runs inside a transaction and rolls back on error, so a failure leaves the schema unchanged and is not recorded in schema_migrations. Fix the cause (often connectivity or privileges) and run again.

Forms fail in the browser

CORS error in the console - the API rejects the frontend's origin. Add the frontend origin to CORS_ORIGINS (comma-separated) and restart the API. For production this must be your exact https://leadfella.com origin.

"Network error - please check your connection" - the frontend can't reach the API. Confirm the API is running and that NEXT_PUBLIC_API_URL matches its URL. Remember NEXT_PUBLIC_* is baked in at build time - rebuild after changing it.

HTTP 429 "Too many requests" - the submit rate limit (5/minute per IP) fired. This is expected protection; wait a minute, or raise RATE_LIMIT_*/the submit limiter for load testing.

A submission "succeeds" but nothing is stored - the hidden honeypot field was filled, so the request was treated as a bot and silently accepted. Real form submissions leave the honeypot empty.

Admin panel

Login fails with valid-looking credentials - verify ADMIN_EMAIL matches what you typed and that either ADMIN_PASSWORD_HASH or ADMIN_PASSWORD is set. With neither set, login is disabled by design (the API logs an error). Regenerate a hash with npm run seed:admin -- "<password>".

Signed out unexpectedly / repeated logins - the admin token expired (JWT_TTL_HOURS, default 12h) or JWT_SECRET changed (rotating it invalidates all sessions). Sign in again.

Can't reach /admin in production - it is intentionally noindex but still served; confirm the frontend deployment includes it and the API origin is in CORS_ORIGINS.

Build issues

Frontend npm run build type errors - run npm run typecheck for the full list. The build fails on type or lint errors by design.

Frontend calls the wrong API in production - NEXT_PUBLIC_API_URL was unset or wrong at build time. Set it and rebuild.

Maintenance scripts

A lead script hits the wrong database - the scripts read DATABASE_URL; if unset they fall back to the dev default (.../leadfella_site). Export the real DATABASE_URL before running, e.g. list-leads, export-leads, delete-lead.

Where do exports go? - export-leads writes to website/backend/exports/ (gitignored, since CSVs may contain PII) unless you pass --out or --stdout.

Still stuck? Check the backend logs (pino, console + structured) and the browser console/network tab, then see configuration.md.