Skip to content

Dashboard

Configure and serve the Pug dashboard against your self-hosted API.

The Pug dashboard, the React UI where you explore analytics and manage projects, is a separate repo from the backend: pug-sh/app (“Pug UI”). It’s a static single-page app (React + Vite, ConnectRPC, Tailwind) that talks to your pug server over Connect RPC. There’s no server runtime to operate: you build it to a folder of static files and serve them from any web server or CDN.

git clone https://github.com/pug-sh/app.git
cd app

1. Configure (build time)

The dashboard’s configuration is baked in at build time via VITE_* environment variables - they’re compiled into the JavaScript bundle, not read at runtime. Copy .env.example to .env.production (Vite loads it for production builds) and set the values, or export them in your CI before bun run build. Without a file, export every one you need: the defaults below are what the code falls back to, not what .env.example holds.

Variable Required? Default Description
VITE_API_BASE_URL Yes (none) Your pug server’s public URL. This is the one that makes it your instance - point it at the backend you deployed (e.g. https://api.example.com). Without it the app throws on load. .env.example has http://localhost:3000.
VITE_MAP_ASSETS_URL No the dashboard’s own origin Base URL for the dashboard’s map assets (basemap tiles, fonts, boundary data). .env.example points it at Pug’s public CDN, https://maps.pug.sh - see Map assets.
VITE_DEMO_ENABLED No (empty) Cosmetic: set to true to advertise an “Explore the live demo” link on the sign-in page. The real demo switch is the backend’s PUG_DEMO_ENABLED.
VITE_PUG_PROJECT_ID / VITE_PUG_PUBLIC_KEY No (empty) Leave empty. They report the dashboard’s own usage into a Pug project, and the events go to Pug Cloud (https://api.pugs.dev), not to VITE_API_BASE_URL.

Because these are compiled in, changing a value means rebuilding (or re-templating the built assets at deploy). If you serve the same build to multiple environments, build once per environment.

2. Build

bun install
bun run build

bun run build type-checks and produces a static bundle in dist/ - plain HTML, JS, and CSS with no server component. That folder is your entire deployable.

3. Serve

Serve the contents of dist/ from any static host - Cloudflare Pages, Netlify, S3 + CloudFront, nginx, Caddy. Two requirements:

  • SPA fallback (history mode). The dashboard uses client-side routing, so the host must serve index.html for any unmatched path (e.g. Cloudflare Pages does this automatically; for nginx use try_files $uri /index.html). Without it, deep links and refreshes 404.
  • HTTPS. The dashboard sends a JWT to the backend; serve it over TLS.

4. Wire it to the backend

The dashboard and backend are separate origins (e.g. https://app.example.com and https://api.example.com), so two backend settings must know about the dashboard - set these in the backend configuration:

Backend variable Set it to Why
PUG_CORS_ORIGINS your dashboard origin The browser blocks the dashboard’s API calls from an origin not on the list. The default, *, allows every website - set your dashboard’s origin instead.
PUG_DASHBOARD_BASE_URL your dashboard origin So magic-link and invite emails link back to your dashboard.

Google and other OIDC sign-in providers are configured only on the backend, in PUG_CONFIG_FILE - the dashboard fetches the list at runtime and renders a button per provider, so adding one needs no rebuild. Org SSO connections need no dashboard config either: admins add them in the dashboard once the backend has PUG_SSO_SECRET_KEY. See Authentication.

Dashboard -> APIVITE_API_BASE_URL points at your pug server
API -> Dashboard (CORS)PUG_CORS_ORIGINS includes your dashboard origin
API -> Dashboard (emails)PUG_DASHBOARD_BASE_URL is your dashboard origin
Sign-in providers (optional)PUG_CONFIG_FILE on the backend; nothing on the dashboard
Org SSO connections (optional)PUG_SSO_SECRET_KEY on the backend; nothing on the dashboard

Map assets

The dashboard renders maps from a set of static assets (a basemap tile archive, label fonts, and boundary data) rather than a third-party tile server. VITE_MAP_ASSETS_URL points at where those assets are hosted. .env.example sets it to Pug’s public CDN (https://maps.pug.sh), which works out of the box; keep that value for most self-hosts. Unset, the dashboard looks for the assets on its own origin, so a build from exported variables that leaves it out has no maps.

If you can’t reach that CDN (air-gapped, or by policy), mirror the assets to your own object store or CDN (it must allow CORS and honor HTTP Range requests) and point VITE_MAP_ASSETS_URL there. Either way, the dashboard works without reachable map assets: only the map visualizations are affected, everything else renders normally.

Local development

Run the dashboard against your local backend with Vite’s dev server:

cp .env.example .env
bun install
bun run dev        # http://localhost:5173

The .env.example value of VITE_API_BASE_URL (http://localhost:3000) already matches a local pug dev backend, so the two line up with no other config. Skip the copy and the app throws on load. Full local walkthrough: Development.

Further reading

Navigation

Type to search...

↑↓ navigate↵ selectEsc close