# Tailnet dev dashboard preview pattern

Use this when a greenfield fullstack dashboard needs a real link for the user before production deployment is ready.

## Goal

Expose a working frontend over the user's Tailnet while keeping the backend bound to localhost/internal networking.

## Preferred path: Tailscale Serve

If the agent has operator rights:

```bash
tailscale serve --bg <frontend-port>
tailscale serve status --json
```

Use this for a cleaner HTTPS Tailnet URL.

## If Serve requires operator/root rights

Do not stop at "serve denied". Use the direct Tailnet-IP fallback:

1. Keep the backend on localhost, e.g. `127.0.0.1:8010`.
2. Make the frontend proxy `/api` to the backend server-side, not via browser `localhost`.
3. Bind the frontend to all interfaces:

```bash
VITE_API_PROXY_TARGET=http://127.0.0.1:8010 npm run dev -- --host 0.0.0.0 --port 5174
```

4. Get the machine Tailnet IP:

```bash
tailscale status
```

5. Verify both frontend and proxied API through the Tailnet IP:

```bash
curl -fsS http://<tailnet-ip>:5174/dashboard >/dev/null
curl -fsS http://<tailnet-ip>:5174/api/health
```

6. Browser-QA the Tailnet link and check console errors.

## Vite config pattern

Use an environment-configurable proxy target so the same app works locally and over Tailnet:

```ts
const apiProxyTarget = process.env.VITE_API_PROXY_TARGET || 'http://localhost:8000'

export default defineConfig({
  server: {
    host: '127.0.0.1',
    port: 5173,
    proxy: { '/api': apiProxyTarget },
  },
})
```

For a Tailnet preview, override host and proxy target on the CLI. This prevents the user's browser from trying to call `127.0.0.1` on their own device.

## Report clearly

Tell the user whether the link is:

- Tailscale Serve HTTPS URL; or
- direct Tailnet IP + port fallback.

If Serve failed due operator permissions, give the fix:

```bash
sudo tailscale set --operator=$USER
```

Do not save this as "Tailscale Serve does not work"; it is an operator-permission setup issue with a known workaround.