Skip to main content
The dashboard deploys as static files. vite build produces dist/ — HTML, JS, CSS — there is no Node server in production. What varies is who serves those files.
VITE_-prefixed values are compiled into the client bundle — never put credentials in them. The dashboard authenticates admins interactively (JWT in memory + an httpOnly refresh cookie); no API keys are involved.

Single node — the default

The Spree server serves the dashboard itself at /dashboard: one container, one origin, zero CORS or cookie configuration. The official Spree Docker image ships with the stock dashboard baked in — deploy the image anywhere Docker runs (Render, Railway, AWS, Azure, your own host) and https://your-api-host/dashboard just works. Under the hood, the spree_dashboard gem (in the Gemfile of the starter and the official image) serves the directory in Spree::Dashboard.dist_path — or the SPREE_DASHBOARD_DIST_PATH env var — with SPA semantics: every dashboard route falls back to index.html, hashed assets get immutable caching. The bundle is built with a relative API base (VITE_SPREE_API_URL unset) and base: /dashboard/, so the same image works on every host and environment with no rebuild. API-only deployments simply omit the gem.

Custom dashboard, single node

The official image contains the stock dashboard. Once you’ve customized yours (your own pages/plugins in apps/dashboard/), bake your build into your own image:
The command detects apps/dashboard/, selects the Dockerfile’s custom dashboard stage (--build-arg DASHBOARD_SOURCE=custom), and passes your app as a named build context — the Node toolchain lives only in that throw-away stage, and the final image is Rails plus your built dist/ served at /dashboard. Without the CLI, the equivalent is:
Inside the stage the build runs with VITE_BASE_PATH=/dashboard/ (asset URLs resolve under the mount) and VITE_SPREE_API_URL unset (API calls stay origin-relative). Projects without apps/dashboard/ build exactly what plain docker build backend/ produces — and if your backend/Dockerfile predates dashboard support, the command warns instead of silently shipping the stock dashboard over your customized one.

Static host / CDN — the alternative

For edge-served assets or an independent dashboard release cadence, deploy dist/ to a static host on its own domain:
This is the cross-origin topology, with three requirements:
  1. HTTPS on both sides. The refresh cookie is issued with SameSite=None; Secure in production, and browsers drop Secure cookies over HTTP. There is no insecure-production mode.
  2. Allowed Origins. Add the dashboard’s URL (e.g. https://dashboard.mystore.com) to Settings → Allowed Origins in the Spree admin — the API rejects cross-origin requests until then.
  3. SPA fallback. Every path must rewrite to index.html so the router owns the URL (/* → /index.html on your host: _redirects on Netlify, vercel.json rewrites, try_files $uri /index.html; on nginx).
A middle ground also works: serve dist/ and proxy /api/* + /rails/* to Rails from one domain behind your own nginx/Caddy/ingress — same-origin simplicity without baking the bundle into the image.

Render

If your project deploys to Render via the Blueprint that create-spree-app places at the project root, spree add dashboard wires the single-node topology automatically: it extends the backend service’s buildCommand to also build apps/dashboard (Render’s Ruby runtime includes Node, and the full repo is available during the build) and adds one env var so the server serves the result:
That’s the whole setup — one service, one origin, your customized dashboard (it builds from your apps/dashboard, not the stock bundle) at https://your-service.onrender.com/dashboard. No VITE_SPREE_API_URL, no Allowed Origins entry, no extra service. Redeploys rebuild on every push. If you prefer the dashboard on Render’s CDN instead, add a static-site service by hand using the cross-origin recipe above.

Any other static host

The same three ingredients, by hand: build with VITE_SPREE_API_URL set, upload dist/, configure the /* → /index.html rewrite. Then add the origin to Allowed Origins.

Checklist (cross-origin only)

Single-node deployments skip all of this — one origin needs none of it.
  • VITE_SPREE_API_URL points at the production API (baked at build time — one build per environment)
  • HTTPS on the dashboard and the API
  • Dashboard origin added to Settings → Allowed Origins
  • All paths rewrite to index.html
  • No secrets in any VITE_ variable