Skip to content

Deployment ​

The build is a folder of static files. What you need beyond that is a rewrite rule and four environment variables.

Build ​

bash
npm run build

It type-checks and bundles in parallel and writes dist/. Every JavaScript and CSS file is also written as .gz and .br, so a host can serve the compressed copy with no work at request time. There are no source maps, by design.

Check the real build before you ship it:

bash
npm run preview

To see where the weight is:

bash
ANALYZE=true npm run build

That adds dist/stats.html, a treemap of every chunk with its gzip and brotli size.

Environment variables ​

Four matter in a deployed build. Set them in your host's environment UI, not in a committed file.

VariableRead byEffect
VITE_API_BASE_URLThe axios instanceThe base every request is built on. Defaults to /api.
VITE_USE_MOCK_AUTHThe auth mock and every demo mockOnly the exact string true turns them on. Leave it unset in production unless you are deploying a public demo.
VITE_DEMO_MODEThe sign-in pagePre-fills the demo credentials and does nothing else. Demo deploys only.
VITE_REALTIME_URLuseRealtimeNotificationsA WebSocket or SSE endpoint. Unset means no realtime channel; the notification center still works locally.

Two more exist and are development-only. VITE_API_TARGET is the dev proxy's destination in vite.config.ts, and VITE_PRODUCT_REAL_API=true detaches the products module's demo data so that one module talks to a real server while auth stays mocked. Neither does anything in a build.

The zip's .env.development sets VITE_USE_MOCK_AUTH and VITE_DEMO_MODE for npm run dev only. A build does not read that file, so a production build starts with the demo backend off. A public demo sets both variables in the host's environment UI.

Every variable exposed to the browser must start with VITE_. Anything without that prefix stays out of the bundle, which is exactly where a database URL or an API secret belongs.

WARNING

VITE_USE_MOCK_AUTH=true makes the app accept any sign-in and answer every request from browser memory. It must never be on in front of real users.

Any static host ​

The build is static files, and a single-page app needs one rule: serve index.html for any path that is not a file. Without it a refresh on /invoices/42 returns a 404 from the host before the router ever sees it.

Vercel ​

vercel.json ships with the one rewrite that does it:

json
{
  "$schema": "https://openapi.vercel.sh/vercel.json",
  "rewrites": [
    { "source": "/((?!.*\.).*)", "destination": "/index.html" }
  ]
}

Point the project at the repository, leave the framework preset on Vite, and set your environment variables in the dashboard.

Docker ​

The Dockerfile builds in Node and serves from nginx:

bash
docker build -t smart-admin .
docker run --rm -p 8080:80 --name smart-admin smart-admin

The app is then on http://localhost:8080. The image carries a health check that pings / every thirty seconds.

nginx.conf is copied in as the only server config. It already handles:

  • the SPA fallback, so every unknown path resolves to index.html;
  • pre-compressed assets through gzip_static, with on-the-fly gzip as the fallback;
  • hashed assets cached for a year (Cache-Control: max-age=31536000), and index.html sent with Cache-Control: no-cache, so a new deploy is reachable on the next navigation;
  • security headers on every response.
HeaderValue
X-Frame-OptionsSAMEORIGIN
X-Content-Type-Optionsnosniff
Referrer-Policystrict-origin-when-cross-origin
Permissions-Policycamera=(), microphone=(), geolocation=(), browsing-topics=()
Strict-Transport-Securitymax-age=31536000; includeSubDomains
Content-Security-PolicyThe line below.
nginx
add_header Content-Security-Policy "default-src 'self'; script-src 'self'; style-src 'self' 'unsafe-inline' https://fonts.googleapis.com; img-src 'self' data: blob: https:; font-src 'self' https://fonts.gstatic.com data:; connect-src 'self' https://api.yourdomain.com; frame-ancestors 'self'; base-uri 'self'; form-action 'self'" always;

Edit connect-src before production. https://api.yourdomain.com is a placeholder. Replace it with your real API origin, or the browser blocks every request to it. If you turn on live notifications, add their wss:// origin to the same list.

The rest of the policy is there for what the app does:

  • X-Frame-Options: SAMEORIGIN and frame-ancestors 'self' let the app frame itself, which the responsive preview needs, and stop every other site from framing it.
  • style-src allows inline styles because Reka UI writes positioning styles for popovers and dialogs at runtime, and https://fonts.googleapis.com for the stylesheets of the fonts picked in the settings.
  • img-src allows blob: for the file-upload previews.

HSTS starts at one year, which belongs only on a domain you serve over HTTPS and intend to keep serving over HTTPS.

The two caching location blocks set caching with expires and nothing else. nginx drops every server-level add_header in a location that has an add_header of its own, so adding one there would strip the security headers from those responses.

A checklist before you ship ​

  • VITE_USE_MOCK_AUTH is not true.
  • VITE_API_BASE_URL points at the real API, or the host proxies /api.
  • The CSP connect-src names your API origin.
  • Unknown paths fall back to index.html.
  • npm run build is green, and npm run preview behaves like the dev server.

Where to go next ​