Skip to main content

NestledJS App Previews

Step-by-step setup for previewing NestJS + React apps with a live API and frontend per branch.

Text Guide

NestledJS App Previews

NestledJS projects run two processes in the preview container — a NestJS API and a React Router (Vite) frontend — each on their own subdomain. This guide walks through the exact configuration, in the order you should do it.

Prerequisites

  • Preview environments enabled for your project (see Preview Environments)
  • A DATABASE_URL the preview can reach (a dev/staging database — never production)
  • Your project uses pnpm and Nx

Step 1: Make the Vite Dev Server Reachable (one-time code change)

This is the step that's easy to miss — and without it the web preview will fail. By default the Nx/Vite dev server binds to localhost and rejects unknown hostnames, so the preview's reverse proxy can't reach it (you get a 502) and Vite blocks the request.

In apps/web/vite.config.ts, update the server block:

server: {
  port: 4200,
  host: process.env.VITE_HOST || 'localhost',
  allowedHosts: ['.preview.flightdesk.dev'],
  // ...keep any existing fs / proxy settings
},
  • host: process.env.VITE_HOST || 'localhost' lets the preview bind to 0.0.0.0 (via the VITE_HOST secret below) while staying localhost for local dev.
  • allowedHosts: ['.preview.flightdesk.dev'] lets Vite accept the preview subdomain. The leading dot matches all subdomains.

Commit this change on the branch you want to preview — the preview builds from the branch, so the fix has to be present there.

Step 2: Set Your Environment Variables

In Project Settings → Secrets, add the variables below. FlightDesk only auto-injects NODE_ENV, NODE_OPTIONS, and NX_DAEMON into the container — everything else your app needs, including HOST, must be set here as a secret.

| Key | Value | Why | |---|---|---| | DATABASE_URL | Connection string for a dev/staging DB | App can't boot without it | | JWT_SECRET | Any random string | Auth signing | | API_COOKIE_SECRET | Any random string | Cookie signing | | HOST | 0.0.0.0 | NestJS must bind all interfaces or Caddy 502s | | VITE_HOST | 0.0.0.0 | Vite dev server must bind all interfaces (pairs with Step 1) | | API_URL | {{PREVIEW_URL:api}} | API's own public URL | | SITE_URL | {{PREVIEW_URL}} | Frontend's public URL | | VITE_API_URL | {{PREVIEW_URL:api}} | Where the browser calls the API | | ALLOWED_ORIGINS | {{PREVIEW_URL}} | CORS allow-list for the frontend | | VITE_COOKIE_NAME | __session_{{BRANCH_SLUG}} | Unique per-branch session cookie name | | API_COOKIE_DOMAIN | .preview.flightdesk.dev | Shares the cookie across the web + api subdomains | | EMAIL_PROVIDER | mock | Don't send real emails from previews |

The HOST=0.0.0.0 / VITE_HOST=0.0.0.0 pair is the #1 cause of "the preview goes Ready but every request 502s." Inside the container, anything bound to localhost is unreachable from Caddy across the Docker bridge.

About the cookie name. In NestledJS, both the web app and the API read VITE_COOKIE_NAME (the API only falls back to API_COOKIE_NAME), so this one secret covers both processes — you don't set it twice. Make it unique per branch with {{BRANCH_SLUG}}: previews live under *.preview.flightdesk.dev, a subdomain of flightdesk.dev, so a generic __session could otherwise collide with the FlightDesk app's own cookie or with other branches' previews. Pair it with API_COOKIE_DOMAIN=.preview.flightdesk.dev so the cookie is shared between the web and api subdomains but never leaks up to flightdesk.dev.

The {{...}} placeholders are replaced at spin-up with the real per-branch URLs. See Preview Environment Variables for the full list.

Step 3: Configure Setup Commands

In Project Settings → Preview → Setup Commands, enter these (one per line):

pnpm install
pnpm prisma generate

These run once before your processes start. prisma generate creates the Prisma client from your schema. (Nx builds shared libraries on demand when you run nx serve, so a separate build:libs step usually isn't needed — add one only if your project requires it.)

Do not run migrations here. Preview containers connect to a shared database — running prisma migrate deploy in a preview would alter it. Only run prisma generate in setup commands.

Step 4: Configure Processes

In Project Settings → Preview → Processes, paste this JSON:

[
  { "name": "api", "command": "pnpm nx serve api", "port": 3000 },
  { "name": "web", "command": "pnpm nx serve web", "port": 4200, "primary": true }
]

Use the ports your apps actually listen on. The NestledJS API reads PORT (default 3000); the Vite web dev server uses 4200. Mark the web process primary so it gets the clean subdomain.

What this gives you for a branch named feature-payments:

  • Web (primary): https://feature-payments.preview.flightdesk.dev
  • API: https://api-feature-payments.preview.flightdesk.dev

Step 5: Health Check Path (optional)

Leave this blank to start — FlightDesk then marks the preview ready as soon as the primary process stops returning 502/503 on its root URL, which is the right behavior for most apps.

If you do set a Health Check Path, note that it is appended to the primary process's URL (the web app here), and the preview is only marked ready on a 200. So /api/health would be checked against the web subdomain, not the API. Only set a path your primary process serves.

How It Works

The preview container:

  1. Clones your branch
  2. Runs pnpm install then pnpm prisma generate
  3. Starts the API and the web app via PM2, binding to 0.0.0.0 on their ports
  4. Caddy terminates TLS and proxies each subdomain to the right port

Both processes restart automatically if they crash. Logs for each are available separately on the task page.

Troubleshooting

Preview is Ready but every request returns 502

A process is bound to localhost instead of 0.0.0.0. Confirm HOST=0.0.0.0 and VITE_HOST=0.0.0.0 are set in secrets, and that Step 1's vite.config.ts change is committed on the branch. Restart the preview after fixing.

Vite shows "Blocked request. This host is not allowed."

allowedHosts: ['.preview.flightdesk.dev'] is missing from vite.config.ts on the branch (Step 1).

API crashes on startup

Usually a missing env var — most often DATABASE_URL. Check the API process logs on the task page, fix the secret, and hit Restart.

Web app can't reach the API

Make sure VITE_API_URL is {{PREVIEW_URL:api}} and ALLOWED_ORIGINS includes {{PREVIEW_URL}}. The frontend bakes VITE_API_URL at dev-server start, so a change requires a Restart.