Netlify
`mandu deploy --target=netlify` emits a `netlify.toml` + a Node SSR function at `netlify/functions/ssr.ts`. With `--execute` and `netlify ≥ 17.0.0` installed, Mandu triggers a deploy.
On this page
Netlify
mandu deploy --target=netlify emits a netlify.toml and a Node
Function at netlify/functions/ssr.ts. With --execute, Mandu drives
netlify deploy --prod using a token stored in Bun.secrets.
# Emit artifacts only
mandu deploy --target=netlify
# Set up the auth token
mandu deploy --target=netlify --set-secret NETLIFY_AUTH_TOKEN=nfp_...
# First-time link
netlify link
# Full deploy (requires netlify CLI + token)
mandu deploy --target=netlify --execute
Prerequisites
# Install Netlify CLI
bun add -g netlify-cli
# Log in (once)
netlify login
Minimum version: netlify 17.0.0. Older versions exit with CLI_E206.
Emitted files
.
├── netlify.toml
└── netlify/
└── functions/
└── ssr.ts # Node SSR function entry
The netlify.toml
# netlify.toml
[build]
command = "bun run build"
publish = "public"
functions = "netlify/functions"
[build.environment]
NODE_ENV = "production"
NETLIFY_USE_YARN = "false"
NPM_FLAGS = "--version"
[[redirects]]
from = "/*"
to = "/.netlify/functions/ssr"
status = 200
force = false # false = static files in /public win first
[functions]
node_bundler = "esbuild"
included_files = [".mandu/**"]
[functions."ssr"]
timeout = 26 # Netlify free-tier cap
The SSR entry — netlify/functions/ssr.ts
// netlify/functions/ssr.ts
import type { Handler } from "@netlify/functions";
import manifest from "../../.mandu/routes.manifest.json";
import "../../.mandu/netlify/register.js";
import { createFetchHandler } from "@mandujs/core/adapters/fetch";
const fetchHandler = createFetchHandler(manifest, {
cssPath: "/.mandu/client/globals.css",
});
export const handler: Handler = async (event, context) => {
const req = new Request(
`https://${event.headers.host}${event.path}${
event.rawQuery ? `?${event.rawQuery}` : ""
}`,
{
method: event.httpMethod,
headers: event.headers as HeadersInit,
body: event.body ?? undefined,
},
);
const res = await fetchHandler(req);
return {
statusCode: res.status,
headers: Object.fromEntries(res.headers.entries()),
body: await res.text(),
};
};
This file is auto-regenerated. Fork the adapter if you need a custom shape.
Runtime: Node.js Functions
Phase 13.1 ships the Node Functions path only (not Edge Functions). Bun-specific APIs map to Node equivalents:
Bun.CookieMap→ Node-compatible cookie codecBun.CSRF→cryptoHMAC-SHA256 fallbackBun.password→ argon2 JS shim (slower)Bun.serve→ replaced by the Netlify handler shim
Edge Functions support (Deno runtime) lands in Phase 15.3 via
the @mandujs/edge/netlify adapter. For cold-start-sensitive routes
today, use Cloudflare Workers — see edge adapter.
Required secrets
| Name | Purpose | Store |
|---|---|---|
NETLIFY_AUTH_TOKEN |
netlify CLI auth for non-interactive deploys | Bun.secrets → OS keychain |
App-runtime env vars go through the Netlify CLI or dashboard, NOT
--set-secret:
netlify env:set DATABASE_URL "postgres://..."
netlify env:set SESSION_SECRET "$(openssl rand -hex 32)"
netlify env:set JWT_SECRET "$(openssl rand -hex 32)"
Netlify stores these encrypted and injects them at build/runtime.
Project linking
# Link this directory to a Netlify site
netlify link
# Or create a new site + link in one step
netlify init
The .netlify/ directory holds the site binding — gitignore it.
Function timeouts
Netlify's free tier caps Node Functions at 26 seconds. The emitted
netlify.toml sets timeout = 26 — the safe maximum. Paid plans can
raise this to 10 minutes.
Long-running requests (upload proxies, streaming responses) should use Background Functions or Edge Functions (Phase 15.3).
Cold start
Typical cold start:
- First request: ~900–1400ms (Node boot + handler init)
- Warm request: ~80–200ms (SSR + DB)
Netlify spins down functions after ~5 minutes of inactivity. For
always-warm behavior, use scheduled functions to ping /healthz every
minute — but note that's a workaround, not a supported pattern.
Common errors
CLI_E205: required provider CLI netlify is missing — install
with bun add -g netlify-cli.
CLI_E207: required secret NETLIFY_AUTH_TOKEN is not present —
run mandu deploy --target=netlify --set-secret NETLIFY_AUTH_TOKEN=nfp_....
CLI_E213: Edge-runtime compatibility warning — an imported
module uses Bun/Node-specific APIs that won't work in future Edge
Functions support. Warning-only for now (Node runtime is unaffected).
404 on every route — check netlify.toml [[redirects]]. The
emitted file uses status = 200 (not 301/302); otherwise CSS/static
assets from public/ might not serve correctly.
🤖 Agent Prompt
Apply the guidance from the Mandu docs page at https://mandujs.com/docs/deploy/netlify to my project.
Summary of the page:
Netlify adapter: emits netlify.toml with a redirect to Node Functions at netlify/functions/ssr.ts. Static assets from `public/`; every other request → SSR function. Edge Functions adapter deferred to Phase 15.3. Secret: `NETLIFY_AUTH_TOKEN`.
Required invariants — must hold after your changes:
- Runtime is Node.js Functions (nodejs20.x) — not Edge Functions in Phase 13.1
- Static routes served from `public/`; dynamic requests go through `netlify/functions/ssr.ts`
- Required secret: `NETLIFY_AUTH_TOKEN` — stored in Bun.secrets
- Minimum netlify CLI version: 17.0.0
- Environment variables are set via `netlify env:set` — the emitted netlify.toml never contains secret values
Then:
1. Make the change in my codebase consistent with the page.
2. Run `bun run guard` and `bun run check` to verify nothing
in src/ or app/ breaks Mandu's invariants.
3. Show me the diff and any guard violations.
Related
- Deploy index — full adapter matrix.
- Edge adapter — Cloudflare Workers for true edge; Netlify Edge Functions coming in Phase 15.3.
For Agents
{
"schema": "mandu.deploy.netlify/v0.24",
"command": "mandu deploy --target=netlify",
"artifacts": ["netlify.toml", "netlify/functions/ssr.ts"],
"provider_cli": { "binary": "netlify", "min_version": "17.0.0" },
"runtime": "nodejs20.x (Node Functions)",
"edge_functions": "deferred to Phase 15.3",
"timeout_s_free_tier": 26,
"secrets_mandu_side": ["NETLIFY_AUTH_TOKEN"],
"secrets_app_side": "via `netlify env:set`, NOT `--set-secret`",
"rules": [
"Run `netlify link` once before first `--execute`",
"Use Cloudflare Workers (`@mandujs/edge`) for edge runtime today",
"App-runtime env vars go via `netlify env:set`"
]
}For Agents
Netlify adapter: emits netlify.toml with a redirect to Node Functions at netlify/functions/ssr.ts. Static assets from `public/`; every other request → SSR function. Edge Functions adapter deferred to Phase 15.3. Secret: `NETLIFY_AUTH_TOKEN`.
- Runtime is Node.js Functions (nodejs20.x) — not Edge Functions in Phase 13.1
- Static routes served from `public/`; dynamic requests go through `netlify/functions/ssr.ts`
- Required secret: `NETLIFY_AUTH_TOKEN` — stored in Bun.secrets
- Minimum netlify CLI version: 17.0.0
- Environment variables are set via `netlify env:set` — the emitted netlify.toml never contains secret values