JCC Express

Deployment

This guide covers shipping a JCC Express MVC application to production: environment, builds, database, process management, Inertia SSR (when you use React/Vue with server-side rendering), and queues. Hosting details (Docker, PaaS, VPS) vary by provider; the steps below are what almost every deploy shares.


What you need on the server

  • Bun (recommended runtime, same as local development).
  • Node.js (optional but common) if you use npm for installs or run node for SSR scripts.
  • A database reachable from the app (MySQL, PostgreSQL, SQLite for small setups, etc.).
  • A reverse proxy (nginx, Caddy, Traefik, or your cloud load balancer) terminating HTTPS and forwarding to the app’s PORT.

Production environment (.env)

Never commit real .env files. On the server, create .env from .env.example and set at least:

  • APP_ENV=production
  • APP_DEBUG=false
  • APP_URL=https://your-domain.example (full URL with scheme; used for links, cookies, OAuth redirects)
  • PORT — Often set by the platform; otherwise choose a non-privileged port the proxy targets.
  • Database — DB_* (or Mongo / Sequelize keys as your app uses).
  • JWT_SECRET — Strong, unique value; run bun artisanNode key:generate on the server if you are not copying a known secret from a vault.
  • Mail, OAuth, Redis, queue — Fill only what you use; see .env.example.

Restart the app process after any .env change.


Build steps

Order matters for typical Inertia + Vite apps.

1. Frontend assets (Inertia / Vite)

Produces public/build (and SSR bundles when configured):

Bash

(Typical vite-build script: vite build && vite build --ssr — check package.json in your app.)

2. TypeScript compile (optional but common)

Emits JavaScript under ./build per tsconfig.json:

Bash

Some teams skip tsc in production and run the HTTP entry directly; choose one approach and stay consistent.

3. Framework build command

bun artisanNode build copies public, filtered resources, and SSR bootstrap bits into build/ alongside your compiled output—use it when your deployment layout expects those files under build/. It does not replace tsc or vite-build.


Database

Run migrations on the server (or in CI before traffic):

Bash

Avoid migrate:fresh on production unless you intend to wipe data. Seed only when appropriate:

Bash

Inertia SSR in production (React / Vue)

Skip this section if ssr is off in app/Http/kernel.ts.

When Inertia is used with React or Vue and SSR is enabled (for example inertia({ rootView: "welcome", ssr: true })), the HTML for the first load is rendered by a separate SSR process. That process must be running and listening before you start the main HTTP server: the Inertia middleware calls it during requests, and starting the app first can cause failed or empty SSR until the SSR service comes up.

Complete Build steps and Database above, then use this start order:

  1. bun artisanNode inertia:start-ssr — start first and keep it running.
  2. Main HTTP app — start only after SSR is up. On apps scaffolded with jcc-express-starter, package.json includes a start script for production; use bun run start or npm run start (not dev, which uses watch). If you have no start script, run your entry directly (for example bun server.ts or the compiled build/server.js).

Manual example (two terminals or a script):

Bash

PM2 — register SSR before the app so it starts first (or use pm2 start order / ecosystem file with inertia-ssr listed before jcc-app):

Bash

On restarts, bring inertia-ssr up before jcc-app (or rely on PM2’s start order in an ecosystem config).

Optional: ssr:server script

Some setups use the npm script that runs the compiled SSR entry (for example node bootstrap/ssr/ssr.mjs):

Bash

If your stack expects this instead of or in addition to inertia:start-ssr, apply the same rule: start that SSR process before the main server. See docs/docs-for-dev.md for your version.

Ports and configuration

The SSR server listens on a port your middleware expects. Keep that port open between the SSR process and the main app (localhost, Docker network, or firewall). See the main developer guide for env vars and URLs.


Run the HTTP application

If you use Inertia SSR: start bun artisanNode inertia:start-ssr (or your documented SSR command) first, as in the previous section—then start the HTTP server below.

If you do not use SSR: you only need the main server.

Scaffolded apps (jcc-express-starter) — package.json defines a start script for production (no file watcher). From the project root:

Bash

Other / custom setups — run whatever your start script maps to, or invoke the entry yourself:

Bash

Compiled deploy (after tsc):

Bash

Do not use dev (or any --watch script) in production. Put each long-running process under systemd, PM2, Docker, or your platform’s supervisor so it restarts on failure.

Behind a reverse proxy, configure trusted proxies / X-Forwarded-* if your framework or Express app expects correct client IPs and https URLs (see docs/docs-for-dev.md for your version).


Queues and scheduled work

If you use database or Redis queues, run workers in production:

Bash

Use a process manager so workers stay up, for example:

Bash

Schedule bun artisanNode schedule:run from cron or a scheduler if your app uses the task scheduler.


Checklist before going live

  • APP_ENV=production, APP_DEBUG=false, correct APP_URL (HTTPS).
  • Secrets (JWT_SECRET, session, mail, OAuth) are production values, not placeholders.
  • vite-build (and build / tsc if you rely on emitted JS) completed successfully.
  • bun artisanNode migrate applied on the target database.
  • If using Inertia SSR: bun artisanNode inertia:start-ssr (or your documented SSR script) is running before the main app (bun run start / npm run start on scaffolded projects, or your equivalent).
  • TLS certificates and proxy timeouts are configured.
  • Backups and log aggregation are in place for your org.