Build Guide / 08
Build & deployment
The build produces two separate artifacts — a static frontend bundle and a bundled server — because the two run in different places in production.
The build command
npm run build
Which runs, in order:
vite build
esbuild server.ts --bundle --platform=node --format=cjs --packages=external --outfile=dist/server.cjs
| Step | Produces | Runs where |
|---|---|---|
vite build | dist/ — the compiled SPA (HTML/JS/CSS) | Served as static files |
esbuild ... server.ts | dist/server.cjs — a single CommonJS bundle of the Express app | Node process / serverless function |
Running the production build locally
npm start
Which is just node dist/server.cjs — at this point
server.ts switches out of Vite-middleware mode and instead
serves the static dist/ folder directly, with a catch-all route
back to index.html for client-side routing.
Deploying to Netlify
netlify.toml defines the whole pipeline:
[build]
command = "npm run build"
publish = "dist"
[functions]
directory = "netlify/functions"
node_bundler = "esbuild"
[[redirects]]
from = "/api/*"
to = "/.netlify/functions/api"
status = 200
[[redirects]]
from = "/*"
to = "/index.html"
status = 200
In this model, netlify/functions/api.ts wraps the same Express
app from server-app.ts (via serverless-http) so API
routes work as a Netlify Function instead of a long-running Node process
— the Express code itself doesn't need to know which environment it's
in. The two redirect rules do the rest: /api/* goes to the
function, everything else falls through to the SPA shell so deep links and
refreshes resolve correctly.
Security headers
netlify.toml also sets baseline headers on every response:
X-Frame-Options: SAMEORIGIN, X-Content-Type-Options:
nosniff, X-XSS-Protection, and a
Referrer-Policy of strict-origin-when-cross-origin.
Scheduled jobs in production
The IPD auto bed-charge cron is scheduled inside server.ts using
node-cron, which only runs while a long-lived Node process is
alive. On a serverless target it needs a scheduled-function trigger instead
(e.g. Netlify Scheduled Functions or an external cron hitting an endpoint)
— this is worth confirming explicitly for whichever environment is
live, since the code currently assumes a persistent process.