Development workflow

One origin

Astro renders the pages, Nitro serves /api, and both come off one origin — no CORS, no API base URL to configure, one thing to deploy.

npm run dev      # Astro on :4321, Nitro on :3000, /api proxied
npm run build    # astro build, then vite build
npm run preview  # the real thing: one server, both halves
development production
Pages astro dev on :4321 static files in dist/, served by Nitro
API vite dev (Nitro) on :3000 Nitro server in .output/
One origin via Astro’s dev proxy (vite.server.proxy in astro.config.mjs) Nitro’s publicAssets, which mounts dist/ at /

Build order matters

npm run build runs Astro first, then Nitro — that order matters, because the Nitro build folds Astro’s finished dist/ into .output/public/.

The dev script

scripts/dev.mjs runs both dev servers. It is dependency-free and worth reading: astro dev stays in the foreground on a TTY but detaches into a background daemon when its output is piped, and the script reports that rather than assuming it crashed.

Type checking

Two tsconfigs, because the halves target different runtimes: tsconfig.json (Astro, DOM) and tsconfig.server.json (Nitro, Node). npm run typecheck runs both.

Changing the API port

ZN_API_PORT (default 3000) sets the Nitro port and the proxy target together — astro.config.mjs, vite.config.ts and scripts/dev.mjs all read it from .env, so change it in one place. A value already in the environment wins over the file, so ZN_API_PORT=3005 npm run dev is still a one-off.