Deployment
Agent-native apps use Nitro under the hood, which means you can deploy to any platform with zero config changes — just set a preset.
Workspace Deploy: One Origin, Many Apps
If your project is a workspace, you can ship every app in it to a single origin with one command:
agent-native deploy
# https://your-agents.com/mail/* → apps/mail
# https://your-agents.com/calendar/* → apps/calendar
# https://your-agents.com/forms/* → apps/forms
Each app is built with APP_BASE_PATH=/<name>, packaged into dist/<name>/, and fronted by a generated dispatcher worker at dist/_worker.js. A _routes.json manifest tells Cloudflare Pages which paths are dynamic.
Same-origin deploy gives you two big wins for free:
- Shared login session — log into any app, every app is logged in.
- Zero-config cross-app A2A — tagging
@calendarfrom mail is a same-origin fetch; no CORS, no JWT signing between siblings.
Publish the output with:
wrangler pages deploy dist
Only need to deploy to Cloudflare Pages? That's the out-of-the-box target. Other targets stay per-app (see below) — or file an issue if you want another unified preset.
Per-app independent deploy is still supported — just cd apps/<name> && agent-native build like a standalone scaffold.
How It Works
When you run agent-native build, Nitro builds both the client SPA and the server API into .output/:
.output/
public/ # Built SPA (static assets)
server/
index.mjs # Server entry point
chunks/ # Server code chunks
The output is self-contained — copy .output/ to any environment and run it.
Setting the Preset
By default, Nitro builds for Node.js. To target a different platform, set the preset in your vite.config.ts:
import { defineConfig } from "@agent-native/core/vite";
export default defineConfig({
nitro: {
preset: "vercel",
},
});
Or use the NITRO_PRESET environment variable at build time:
NITRO_PRESET=netlify agent-native build
Node.js (Default)
The default preset. Build and run:
agent-native build
node .output/server/index.mjs
Set PORT to configure the listen port (default: 3000).
Docker
FROM node:20-slim AS build
WORKDIR /app
COPY package.json pnpm-lock.yaml ./
RUN corepack enable && pnpm install --frozen-lockfile
COPY . .
RUN pnpm build
FROM node:20-slim
WORKDIR /app
COPY --from=build /app/.output .output
COPY --from=build /app/data data
ENV PORT=3000
EXPOSE 3000
CMD ["node", ".output/server/index.mjs"]
Vercel
// vite.config.ts
export default defineConfig({
nitro: { preset: "vercel" },
});
Deploy via the Vercel CLI or git push:
vercel deploy
Netlify
The Nitro netlify preset works well and, in practice, has given us much faster cold starts than Cloudflare Pages (~200ms TTFB vs ~9s) for templates that talk to external Postgres (Neon). Either set the preset in vite.config.ts:
// vite.config.ts
export default defineConfig({
nitro: { preset: "netlify" },
});
…or set NITRO_PRESET=netlify at build time from netlify.toml (recommended for monorepo templates — keeps the preset scoped to the deploy rather than hard-coded in the template's Vite config).
Monorepo template pattern
For a workspace template at templates/<name>/, commit a netlify.toml at the template's root. All paths are repo-root-relative (Netlify's base should stay empty — the whole repo is the build context so pnpm workspace resolution works):
# templates/<name>/netlify.toml
[build]
command = "pnpm install && NITRO_PRESET=netlify pnpm --filter <name> build"
publish = "templates/<name>/dist"
functions = "templates/<name>/.netlify/functions-internal"
[build.environment]
NITRO_PRESET = "netlify"
Notes:
.netlify/functions-internalis where Nitro 3 writes its server functions — Netlify picks them up from there.- When you create a new Netlify site via the dashboard, its monorepo auto-detect sometimes picks the wrong template (e.g. selecting
templates/calendarwhen you wantedtemplates/<name>). Manually clear the base directory and letnetlify.tomldrive the build. - Do not put
netlify.tomlat the repo root — each template manages its own.
Always build on Netlify CI
Do not run netlify deploy --prod from your Mac. The framework's Nitro build (createDanglingOptionalDepStubs() in packages/core/src/deploy/build.ts) stubs platform-specific optional native deps that aren't installed on the current OS. On macOS, that stubs out the Linux libsql binaries; on Netlify's Linux runtime the server function then crashes with:
Cannot find module '@libsql/linux-x64-gnu'
Letting Netlify CI run the build fixes this — on Linux, pnpm installs the real binary and the stub creator skips it. Push to the branch connected to the Netlify site and let it build.
Env vars
From the template directory, link the site and import .env:
cd templates/<name>
netlify link --name <netlify-site-name>
netlify env:import .env
Then set deployment-only vars that aren't in .env:
netlify env:set BETTER_AUTH_URL https://<your-domain>
netlify env:set BETTER_AUTH_SECRET "$(openssl rand -hex 32)"
netlify env:set NITRO_PRESET netlify
BETTER_AUTH_URL must match the public URL the app is served on (custom domain or <site>.netlify.app). BETTER_AUTH_SECRET should be a fresh 32-byte hex — do not reuse the dev secret.
Cloudflare Pages
// vite.config.ts
export default defineConfig({
nitro: { preset: "cloudflare_pages" },
});
External Postgres latency
Cloudflare Workers open a fresh connection per request. Against external Postgres (Neon, Supabase, RDS) the TLS + auth handshake dominates every cold hit, which is why we've seen ~9s TTFB on cold-start routes that query the DB.
Mitigate with Cloudflare Hyperdrive, which pools Postgres connections at the edge. Requires a TCP-based driver — pg, postgres, or Drizzle's node-postgres adapter. The HTTP-based @neondatabase/serverless driver does not go through Hyperdrive. Workers Paid plan only.
If you're hitting this and don't want to move to Hyperdrive, the Netlify preset above is a simpler path.
AWS Lambda
// vite.config.ts
export default defineConfig({
nitro: { preset: "aws_lambda" },
});
Deno Deploy
// vite.config.ts
export default defineConfig({
nitro: { preset: "deno_deploy" },
});
Environment Variables
| Variable | Description |
|---|---|
PORT |
Server port (Node.js only) |
NITRO_PRESET |
Override build preset at build time |
ACCESS_TOKEN |
Enable auth gating for production mode |
ANTHROPIC_API_KEY |
API key for embedded production agent |
FILE_SYNC_ENABLED |
Enable file sync for multi-instance |
APP_BASE_PATH |
Mount the app under a prefix (e.g. /mail). Set automatically by agent-native deploy; leave unset for standalone. |
Inside a workspace, the root .env is loaded into every app automatically, so shared keys like ANTHROPIC_API_KEY and A2A_SECRET only need to be set once. Per-app apps/<name>/.env wins on conflict.
File Sync in Production
By default, agent-native apps store state in local files. For multi-instance deployments (e.g., serverless or load-balanced), enable file sync to keep instances in sync:
FILE_SYNC_ENABLED=true
See File Sync for adapter configuration (Firestore, Supabase, Convex).