Documentation

The Nuxt client in apps/client/ runs as a SPA (ssr: false). In production it is built to static assets that are embedded in the Rust binary and served same-origin — no proxy is involved.

In development (pnpm dev:client) the Nuxt dev server runs on http://localhost:3001 and proxies API + tile + admin requests to the Rust backend running on a separate port. The proxy table is dev-only.

Default ports

ConcernDefaultSource
Nuxt dev server:3001pnpm dev:client script
Public Rust backend:8080[server].port in config.toml
Admin Rust backend:8081[server].admin_bind in config.toml

The Nuxt dev proxy hardcodes :8080 and :8081 as its default targets.

Overriding the backend ports

If you run tileserver-rs on non-default ports — for example because 8080 is already in use, or because you set custom [server].port and [server].admin_bind values in your config.toml — export two env vars before starting the dev server:

TILESERVER_BACKEND_PORT=9000 \
TILESERVER_ADMIN_PORT=9001 \
  pnpm dev:client

The Vite dev proxy reads these once at config load and rewrites every proxied target accordingly.

Info

These env vars are deliberately not prefixed with NUXT_. The NUXT_* prefix is reserved by Nuxt for env vars that auto-map into runtimeConfig. The proxy block in nuxt.config.ts runs at config-load time (before any runtime exists), so it reads process.env directly and uses the unprefixed name.

Info

This only affects local development. Production builds embed the SPA inside the Rust binary, so the browser talks to whatever port the operator configures in config.toml without any proxy layer.

Concrete example

Run the backend on :9000 (public) and :9001 (admin):

# my-config.toml
[server]
host = "127.0.0.1"
port = 9000
admin_bind = "127.0.0.1:9001"
# Terminal 1 — backend
tileserver-rs --config my-config.toml

# Terminal 2 — frontend
TILESERVER_BACKEND_PORT=9000 \
TILESERVER_ADMIN_PORT=9001 \
  pnpm dev:client

The frontend at http://localhost:3001 will now proxy /data/*, /styles/*, /__admin/*, etc. to the right backend ports.

Why two ports?

The admin server runs on a separate bind so it can be firewalled to localhost (or a private subnet) while the public tile server stays internet-facing. The MCP admin UI (/admin/mcp/connected-apps, /admin/mcp/devices) talks to /__admin/oauth/* endpoints that only exist on the admin port. See the MCP guide for the security model.