LangENKO

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.

since v0.24
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 codec
  • Bun.CSRFcrypto HMAC-SHA256 fallback
  • Bun.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

🤖 Agent Prompt — Netlify
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.
  • 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

AI hint

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`.

Invariants
  • 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
Guard scope
deploy