---
title: "Deployment"
description: "Build deployable output with Farm's deploy config and Nitro presets, from first-class targets to custom Nitro output."
canonical_url: "https://mcp.farmjs.dev/docs/deployment"
markdown_url: "https://mcp.farmjs.dev/docs/deployment.md"
last_updated: "2026-10-20"
x_farming_labs_generated_preamble: true
---

# Deployment
URL: /docs/deployment
LLM index: /llms.txt
Description: Build deployable output with Farm's deploy config and Nitro presets, from first-class targets to custom Nitro output.

# Deployment

Build deployable output with Farm's deploy config and Nitro presets. Farm owns route discovery, framework conventions, and app bundling; Nitro owns the final server output shape.

Scheduled routes are configured through framework [Cron](/docs/cron). Vercel builds receive native Build Output API cron entries, Cloudflare Worker builds receive Wrangler triggers, and other adapters can consume `.farm/cron-manifest.json`.

Dynamic pages and APIs can select their execution runtime, regions, and duration with file exports, layout defaults, programmatic routes, or route rules. See [Route Runtime](/docs/route-runtime) for precedence, provider support, and generated deployment output.

## Target-based deploy config

**farm.config.ts**

```ts
export default defineConfig({
  deploy: {
    target: "vercel",
    output: ".vercel/output",
  },
});
```

## First-class targets

| Target     | Preset           | Default output                         |
| ---------- | ---------------- | -------------------------------------- |
| vercel     | vercel           | `.vercel/output`                       |
| cloudflare | cloudflare-pages | `.farm/.output`                        |
| netlify    | netlify          | `dist` + `.netlify/functions-internal` |
| node       | node-server      | `.farm/.output`                        |

These targets get the most polished Farm defaults. They map `deploy.target` to a Nitro preset, output directory, and the matching `farm deploy` command when Farm has a deploy wrapper for that platform.

When no target or preset is configured, Farm recognizes platform CI through `VERCEL`, `NETLIFY`,
and `CF_PAGES`. Detection is enabled only by the values `1` or `true` (case-insensitive); unset,
empty, `0`, `false`, and other values do not select a platform. An explicit target, preset, or CLI
override remains authoritative.

### Immutable fingerprinted assets on Vercel

Farm's Vercel Build Output automatically serves content-hashed files under `assets/` and `chunks/`
with `Cache-Control: public, max-age=31536000, immutable`. This covers fingerprinted JavaScript, CSS,
fonts, and images, including nested asset directories and applications with a `basePath`.

The rule intentionally excludes HTML, unhashed public files, and stable entry URLs such as
`/farm-client.js` and `/farm-client.css`. Those URLs can change between deployments and must remain
revalidatable. The immutable header route runs before Vercel's filesystem handler, so matching files
are still served directly from the CDN and never enter the Farm server function.

## Preset showcase

Use `deploy.target` when you want one config file to control the platform output. Farm maps that target to the Nitro preset, default output directory, and deploy command shape.

| Platform         | Config                 | Build command                              | Deploy command                        | Output to inspect                      |
| ---------------- | ---------------------- | ------------------------------------------ | ------------------------------------- | -------------------------------------- |
| Vercel           | `target: "vercel"`     | `FARM_DEPLOY_TARGET=vercel farm build`     | `farm deploy --vercel --prod`         | `.vercel/output`                       |
| Cloudflare Pages | `target: "cloudflare"` | `FARM_DEPLOY_TARGET=cloudflare farm build` | `farm deploy --cloudflare`            | `.farm/.output`                        |
| Netlify          | `target: "netlify"`    | `FARM_DEPLOY_TARGET=netlify farm build`    | `farm deploy --netlify`               | `dist` + `.netlify/functions-internal` |
| Self-hosted Node | `target: "node"`       | `FARM_DEPLOY_TARGET=node farm build`       | `node .farm/.output/server/index.mjs` | `.farm/.output`                        |

**farm.config.ts**

```ts
import { defineConfig } from "@farm.js/core";

const target = process.env.FARM_DEPLOY_TARGET ?? "vercel";

export default defineConfig({
  deploy: {
    target,
    cloudflare: {
      projectName: process.env.CLOUDFLARE_PAGES_PROJECT,
    },
    netlify: {
      site: process.env.NETLIFY_SITE_ID,
    },
  },
});
```

**package.json**

```json
{
  "scripts": {
    "build": "farm build",
    "build:vercel": "FARM_DEPLOY_TARGET=vercel farm build",
    "build:cloudflare": "FARM_DEPLOY_TARGET=cloudflare farm build",
    "build:netlify": "FARM_DEPLOY_TARGET=netlify farm build",
    "build:self-host": "FARM_DEPLOY_TARGET=node farm build",
    "start:self-host": "node .farm/.output/server/index.mjs",
    "deploy:vercel": "farm deploy --vercel --prod",
    "deploy:cloudflare": "farm deploy --cloudflare",
    "deploy:netlify": "farm deploy --netlify"
  }
}
```

The same shape lives in `examples/deployment-presets` so the preset behavior can be tested from a real app.

## Chunk error recovery

Farm installs client-side chunk recovery automatically in the generated browser runtime. When the browser fails to load a JavaScript or CSS chunk after a deployment, Farm reloads the current page once so the app can pick up the newest asset manifest.

This is meant for stale deploy assets, where a user has an older HTML page open while the server now points at newer chunks. Farm stores a short session guard per page before reloading, so repeated chunk failures do not trap the user in a reload loop. Ordinary runtime errors are left alone and should still be handled with route `error.tsx`, programmatic `error` components, monitoring, and tests.

Farm's browser error overlay and its source-map diagnostics are development-only and are excluded from production client bundles. Production client-plugin error hooks and chunk recovery remain available; no configuration change is required.

## Rolling deployment safety

Farm embeds a deployment ID in each HTML document and sends it with SPA data, RSC navigation, and server action requests. The server also stores the ID in an HttpOnly, `SameSite=Lax` cookie so a progressively enhanced form action carries the same protection before JavaScript loads.

During a rolling deployment, an open page can belong to release A while its next request reaches release B. Farm handles that mismatch according to whether replay is safe:

| Request                                      | Mismatch behavior                                                              |
| -------------------------------------------- | ------------------------------------------------------------------------------ |
| SPA page data, RSC navigation, or HTML fetch | Emit `farm:deployment-mismatch` and perform a full document navigation.        |
| JavaScript server action                     | Return `409 FARM_DEPLOYMENT_MISMATCH`; never retry the mutation automatically. |
| Form server action without JavaScript        | Return the same non-cacheable `409` response using the deployment cookie.      |

Mutation requests are rejected before their action body is decoded or the handler runs. This prevents a stale action from being replayed against a new release and accidentally creating duplicate writes. The user can refresh and deliberately submit again.

Farm resolves the ID from explicit config, `FARM_DEPLOYMENT_ID`, `VERCEL_GIT_COMMIT_SHA`, or `CF_PAGES_COMMIT_SHA`. Otherwise, production uses `generateBuildId` and development uses `"development"`.

```ts
import { defineConfig } from "@farm.js/core";

export default defineConfig({
  deploymentId: process.env.RELEASE_ID,
});
```

Use a commit SHA or CI release ID shared by every server instance in one deployment. A random value created independently when each process starts will make healthy instances reject one another's requests.

Applications can observe recovery and report it without replacing Farm's default behavior:

```ts
window.addEventListener("farm:deployment-mismatch", (event) => {
  const error = (event as CustomEvent).detail;
  reportError(error.code, {
    clientDeploymentId: error.clientDeploymentId,
    serverDeploymentId: error.serverDeploymentId,
  });
});
```

The deployment ID is a consistency marker, not a secret or an authorization boundary. Continue to authenticate and authorize every server action.

## Self-host a Farm app

Use `target: "node"` when you want to run the app on your own server, VPS, container, or platform that expects a long-running Node process. Farm builds the app with Nitro's Node server preset.

**farm.config.ts**

```ts
import { defineConfig } from "@farm.js/core";

export default defineConfig({
  deploy: {
    target: "node",
    output: ".output",
  },
});
```

**package.json**

```json
{
  "scripts": {
    "build": "farm build",
    "start": "node .output/server/index.mjs"
  }
}
```

Build once, then run the generated server:

```bash
pnpm build
HOST=0.0.0.0 PORT=3000 pnpm start
```

`farm start` is equivalent for the `node` target and adds `--port`/`--host` flags plus clear errors when the configured target has no local server. It runs through the `farm` CLI, which is a devDependency, so on hosts that remove devDependencies before starting the app, use the `node` command instead. New starters use it as their `start` script.

For a VPS, run the same start command behind nginx, Caddy, systemd, or a process manager such as PM2. Environment variables should be provided by the host at runtime, not committed into the bundle.

### Docker

`farm build` writes a self-contained server, so a multi-stage image installs everything to build and copies only the output into the runtime stage:

```dockerfile
FROM node:22-slim AS build

WORKDIR /app
COPY package*.json ./
RUN npm ci
COPY . ./
RUN npm run build

FROM node:22-slim

WORKDIR /app
COPY --from=build /app/.farm/.output ./.farm/.output

ENV NODE_ENV=production
EXPOSE 3000
CMD ["node", ".farm/.output/server/index.mjs"]
```

The build stage keeps devDependencies because `farm build` runs the CLI. The runtime stage does not need `node_modules`. This file uses the default output directory, `.farm/.output`; if you set `deploy.output`, copy and start that directory instead. Add a `.dockerignore` with `node_modules`, `.farm`, and `.git` so local builds stay out of the image.

### Hosting platforms

Any platform that builds from a Git repository and runs a long-lived Node.js process can host a Farm app, including Coolify, Dokploy, Railway, Render, Fly.io, Zeabur, Clever Cloud, and Heroku. They need the same settings:

| Setting       | Value                                                                    |
| ------------- | ------------------------------------------------------------------------ |
| Node.js       | 22.13 or later                                                           |
| Build command | `npm run build` (runs `farm build`)                                      |
| Start command | `npm start` (runs `node .farm/.output/server/index.mjs` in new starters) |
| Port          | The server reads `PORT` (or `NITRO_PORT`) from the platform, else `3000` |

Buildpack builders such as Nixpacks and Railpack detect a Node.js app and run these scripts without extra configuration. Platforms that build from a container image can use the Dockerfile above.

A few details decide whether the first deploy works:

- **Install devDependencies for the build.** `farm build` needs `@farm.js/cli`. If the platform installs production dependencies only, enable devDependencies for the build step or deploy with the Dockerfile.
- **Start with `node`, not `farm start`, when devDependencies are pruned.** Some builders remove devDependencies before the app starts, and `farm start` comes from the CLI.
- **Build for Node.js.** If `farm.config.ts` sets `deploy.target` to another platform, such as `vercel`, remove it or set it to `node`. A platform target builds output that only that platform can serve.

## Nitro preset pass-through

Farm can also build for any Nitro preset that Nitro can resolve. Use `deploy.preset` when the target is not one of Farm's first-class shortcuts, or pass `--preset` from the CLI for a one-off build.

**farm.config.ts**

```ts
import { defineConfig } from "@farm.js/core";

export default defineConfig({
  deploy: {
    preset: process.env.NITRO_PRESET || "node-server",
    output: ".output",
  },
});
```

**Terminal**

```bash
NITRO_PRESET=aws-lambda farm build
# Or, in an app that does not also set deploy.target:
farm build --preset deno-deploy
farm build --preset my-company-preset
```

For built-in Nitro presets, the generated output follows Nitro's provider shape. For custom presets, Farm passes the preset name through to Nitro, so the preset must be installed or otherwise resolvable by Nitro in the project.

### Nitro version compatibility

Farm pins the Nitro and H3 versions used by its production adapters. Do not override either package independently: Nitro beta releases can change builder exports, runtime entry points, event shapes, and provider output layouts together.

The current Farm dependency uses a patched Nitro v3 beta newer than `3.0.260429-beta`, the first v3 release containing the wildcard-redirect and encoded proxy-traversal fixes. Applications upgrading from a Farm release that used the Nitro v3 alpha should remove any app-level Nitro override or alpha compatibility patch, reinstall dependencies to refresh the lockfile, and rerun type-checking plus a production build for every deployment target. Applications without those overrides need no configuration migration.

## Nitro coverage

Nitro's official deploy docs list these runtime and provider families. Farm's first-class deploy commands cover the common Vercel, Cloudflare Pages, and Netlify path, while `deploy.preset` / `farm build --preset` lets advanced apps target the rest.

| Family                               | Nitro supports                                                                                                                                                                                            |
| ------------------------------------ | --------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- |
| Runtimes                             | Node.js, Bun, Deno                                                                                                                                                                                        |
| Zero-config providers in Nitro CI/CD | AWS Amplify, Azure, Cloudflare, Firebase App Hosting, Netlify, StormKit, Vercel, Zeabur                                                                                                                   |
| Other Nitro provider docs            | Alwaysdata, AWS Lambda, Cleavr, Deno Deploy, DigitalOcean, EdgeOne Pages, Firebase, Flightcontrol, Genezio, GitHub Pages, GitLab Pages, Heroku, IIS, Koyeb, Platform.sh, Render.com, Zephyr Cloud, Zerops |

Common preset names include `node-server`, `bun`, `deno-server`, `deno-deploy`, `aws-lambda`, `aws-amplify`, `azure-swa`, `cloudflare-pages`, `cloudflare-module`, `firebase-app-hosting`, `github-pages`, `gitlab-pages`, `netlify`, `netlify-edge`, `vercel`, `zeabur`, and `zerops`. Nitro can add or rename presets over time, so check Nitro's deploy docs when targeting a provider directly.

## Build

**Terminal**

```bash
pnpm build
FARM_DEPLOY_TARGET=vercel farm build
farm build --preset aws-lambda
farm deploy --cloudflare
```

## Compact config

Farm reads deployment settings from `farm.config.ts`, so a minimal project does not need `vercel.json`, `wrangler.toml`, or Netlify config just to choose an output target.

**farm.config.ts**

```ts
import { defineConfig } from "@farm.js/core";

export default defineConfig({
  deploy: {
    target: "vercel",
  },
});
```

When `target` is `vercel`, Farm uses the Vercel Nitro preset and writes Build Output API output to `.vercel/output`. Netlify writes the server function to `.netlify/functions-internal`. Other targets default to `.farm/.output` unless you override `output` or `outputDir`. When `preset` is set directly, Farm passes that value through to Nitro.

`farm deploy --vercel` requires `.vercel/output` because the Vercel CLI reads that fixed directory
for `--prebuilt` uploads. Keep the default output for Farm-managed Vercel deploys. A custom Vercel
output directory is only suitable when another deployment workflow uploads the build itself.

For Netlify, Nitro writes static assets to `dist` and the server function to
`.netlify/functions-internal`, the directory Netlify reads functions from without configuration. A
Git-connected Netlify site needs only `farm build` as the build command and `dist` as the publish
directory. `farm deploy --netlify` uploads the same output with `--no-build`, so the provider deploys
the exact artifact Farm just verified instead of starting a second build. With a custom Netlify
`output`, set `[build] functions` to `<output>/server` in `netlify.toml`.
`farm deploy --netlify` passes that path for you.

## Override output

Use `output` for the compact form or `outputDir` when you want the explicit option name.

```ts
export default defineConfig({
  deploy: {
    target: "netlify",
    output: ".output",
  },
});
```

Both options belong inside `deploy`. A top-level `output: "standalone"`, `"static"`, or `"export"`
does not select Farm rendering behavior. Choose a deployment target here, then configure static
pages with `dynamic = "force-static"` or `routeRules` as described in
[Server Rendering](/docs/server-rendering).

## Platform deploys

| Command                                | What it expects                                               |
| -------------------------------------- | ------------------------------------------------------------- |
| `FARM_DEPLOY_TARGET=vercel farm build` | Builds `.vercel/output` for Vercel prebuilt deploys.          |
| `FARM_DEPLOY_TARGET=node farm build`   | Builds `.farm/.output/server/index.mjs` for self-hosted Node. |
| `farm build --preset <nitro-preset>`   | Builds with any Nitro preset that Nitro can resolve.          |
| `farm deploy --vercel`                 | Uses `vercel deploy --prebuilt`.                              |
| `farm deploy --cloudflare`             | Deploys the Cloudflare Pages output with Wrangler.            |
| `farm deploy --netlify`                | Deploys the Netlify output with Netlify CLI.                  |
| `farm deploy --plan`                   | Prints the resolved build and deploy operations only.         |

For other Nitro presets, use the host's documented deploy command or CI workflow after `farm build` writes the Nitro output.

Use `farm deploy --plan` before a release when you want to verify the selected target, preset, runtime, output directory, and exact build and provider commands. Planning is read-only: it does not run the build, require a provider login, inspect existing output, or deploy anything.

## Environment variables

Keep environment variables in the platform's environment manager or local `.env` files. Farm config can reference `process.env`, and integrations should validate required provider keys during setup.

Server values declared under `env.server` are validated when the app builds, but they are not
stored in the build output. The production server reads them from its own environment when code
calls `getEnv` or `serverEnv`, so set them wherever the server runs, and a restart picks up a
rotated secret. On Cloudflare Workers, Farm also reads the worker's bindings and secrets. A value
read at runtime keeps the type its parser produced at build time, such as a number or boolean. When a
parser does more than convert the raw string to a string, number, boolean, or JSON value, such as
splitting a list or scaling a number, Farm cannot repeat it at runtime, so that parsed value stays in
the build output. Values declared under `env.public` are part of the client bundle, so never put a
secret there.

```ts
export default defineConfig({
  integrations: {
    billing: stripe({
      secretKey: process.env.STRIPE_SECRET_KEY,
    }),
  },
});
```

## Preview deployments

Set `agent.noindexPreviews` to keep preview deployments out of search results:

```ts
export default defineConfig({
  agent: { noindexPreviews: true },
});
```

On a preview, every response from Farm's server (pages, API routes, docs, errors) gets
`X-Robots-Tag: noindex, nofollow`. Production deployments are unaffected. The option lives with the
other [agent readiness](/docs/configuration#agent-readiness) settings.

A deployment is a preview when:

| Signal                                      | Where                                             |
| ------------------------------------------- | ------------------------------------------------- |
| `FARM_PREVIEW=1` (`0` turns it off)         | Any host, `farm dev`, `farm build`, or the server |
| `VERCEL_ENV=preview`                        | Vercel previews and custom environments           |
| `CONTEXT=deploy-preview` or `branch-deploy` | Netlify, with `NETLIFY` set, read at build time   |
| `IS_PULL_REQUEST=true`                      | Render pull request previews                      |

Farm reads these when it builds and again when the server starts, so a preview build stays one at
runtime (Netlify sets `CONTEXT` only during the build); an explicit `FARM_PREVIEW` at runtime wins
over the build. Cloudflare Pages exposes no variable that tells a preview from production, so set
`FARM_PREVIEW=1` in its preview environment; Cloudflare already sends `X-Robots-Tag: noindex` on
`*.pages.dev` preview URLs. Run `FARM_PREVIEW=1 farm dev` to check the behavior locally.

Files a platform serves straight from its CDN or static file server, such as prerendered pages and
assets, do not pass through Farm's server and keep their platform's headers. Vercel, Netlify, and
Cloudflare Pages already mark their generated preview URLs as noindex; a preview on a custom
domain only gets the header on responses from Farm's server.

## Production checklist

- Run `farm build` before `farm deploy`.
- For self-hosting, run the generated server (`node .farm/.output/server/index.mjs` with the default output directory) behind your process manager or container runtime.
- Confirm the selected target or preset matches `deploy.target`, `deploy.preset`, or the CLI/env override.
- For non-first-class platforms, confirm the Nitro preset name and provider deploy command from Nitro's docs.
- Check generated output exists at the resolved output directory.
- Run provider-specific login commands such as `vercel login`, `wrangler login`, or `netlify login` before deploying.
- Keep provider secrets out of client bundles and UI registry components.

## Sitemap

See the full [sitemap](/sitemap.md) for all pages.
Docs-scoped sitemap: [/docs/sitemap.md](/docs/sitemap.md).
Well-known sitemap: [/.well-known/sitemap.md](/.well-known/sitemap.md).
