# How to ship a Nitro app on Vercel

**Author:** Ben Sabic

---

[Nitro](https://nitro.build/) is a TypeScript-first framework for building backends and full-stack apps. It gives you filesystem routing, code-splitting for fast startup, built-in caching, and storage that works across multiple drivers, and it produces small deployment output (often under 1 MB) that runs on any platform.

On Vercel, you can deploy a Nitro app with zero configuration: your server routes become [Vercel Functions](https://vercel.com/docs/functions) running on [Fluid compute](https://vercel.com/fluid), and you get preview deployments, observability, and [Vercel Firewall](https://vercel.com/docs/vercel-firewall) without extra setup.

This guide walks you through deploying a Nitro app to Vercel from a template, the [Vercel CLI](https://vercel.com/docs/cli), or a Git repository, and configuring features such as Incremental Static Regeneration, cron jobs, queues, and observability.

## Prerequisites

Before you begin, make sure you have:

- A [Vercel account](https://vercel.com/signup)
  
- Node.js 20+ and a package manager (e.g., npm)
  
- An existing Nitro project, or a new one created from a [Nitro template](https://vercel.com/templates/nitro)
  
- A Git repository on GitHub, GitLab, or Bitbucket (if you want Git-based deployments)
  
- Vercel CLI installed (`npm i -g vercel`)
  

## How it works

When you deploy a Nitro app, Vercel detects the framework and builds it for the Vercel runtime. Nitro compiles your server routes into Vercel Functions, which run on Fluid compute by default. Your app scales with traffic, and you pay only for the compute your functions use, not for idle time.

Because Vercel ships zero-configuration detection for Nitro, you don't set a build command or output directory. Vercel reads your project, identifies the framework, and applies the correct build settings.

## Deploy your Nitro app

You can ship a Nitro app to Vercel in three ways. Choose the one that fits where your code lives today.

### Option 1: Deploy from a template

The fastest way to ship a Nitro app is to start from a template. Browse the [Nitro templates gallery](https://vercel.com/templates/nitro), pick a starter, and deploy it. Vercel clones the template to your Git provider, creates a project, and deploys it with zero configuration.

Templates to start from include:

- [**Nitro Starter**](https://vercel.com/templates/nitro/nitro-starter): Deploy an API on Vercel with Nitro
  
- [**Nitro - Route Rules**](https://vercel.com/templates/nitro/nitro-route-rules): A Nitro app that uses route rules
  
- [**Nitro - Plugins**](https://vercel.com/templates/nitro/nitro-plugins): A Nitro app that uses plugins
  
- [**Slack Bolt with Nitro**](https://vercel.com/templates/nitro/slack-bolt-with-nitro): A Slack app built with Bolt for JavaScript and Nitro
  
- [**Durable iMessage AI Agent**](https://vercel.com/templates/nitro/durable-imessage-ai-agent): A durable iMessage agent built with [Chat SDK](https://vercel.com/kb/chat-sdk)
  

### Option 2: Start a new project with the Vercel CLI

To scaffold a new Nitro project locally, use the [Vercel CLI init command](https://vercel.com/docs/cli/init). It clones Vercel's Nitro example into a folder named `nitro`.

1. Create the project:
   
   `vercel init nitro`
   
2. Install dependencies:
   
   `cd nitro npm install`
   
3. Develop locally at `http://localhost:3000`:
   
   `npm run dev`
   
4. Create a [preview deployment](https://vercel.com/docs/deployments/environments#preview-environment-pre-production). The first run creates a Vercel project link:
   
   `vercel`
   
5. Promote your changes to production:
   
   `vercel --prod`
   

### Option 3: Deploy an existing Nitro app

If you already have a Nitro app, deploy it from Git or from the command line.

**From Git**: Push your project to GitHub, GitLab, or Bitbucket, then import it at [vercel.com/new](https://vercel.com/new). Vercel detects Nitro automatically and deploys it with zero configuration.

**From the CLI**: From your project's root directory, run `vercel` to create a preview deployment, then `vercel --prod` to go live. To pull project settings and environment variables for local development, run:

`vercel link vercel env pull`

> The configuration examples in this guide assume Nitro v3 (the `nitro` package, which replaced `nitropack`). New projects from a template or the Vercel CLI already use v3. If you're bringing an existing app, upgrade to v3 first using the [migration guide](https://nitro.build/docs/migration), or adjust the imports to `nitropack/config` for nitropack v2.

## Use Vercel features with Nitro

After your app is deployed, you can configure Vercel features directly from your Nitro config. The examples below import `defineNitroConfig` from `nitro/config`; `import { defineConfig } from "nitro"` works too.

### Server routes run as Vercel Functions

Each Nitro server route automatically becomes a [Vercel Function](https://vercel.com/docs/functions). These functions use Fluid compute by default, which runs multiple requests concurrently within a single instance to reduce cold starts and I/O-bound work costs, such as API calls and database queries. You don't configure anything to get this behavior.

### Cache responses with Incremental Static Regeneration (ISR)

[ISR](https://vercel.com/docs/incremental-static-regeneration) serves cached responses and regenerates them in the background, so you get static performance for dynamic content. Enable it per route with the `isr` route rule:

`import { defineNitroConfig } from "nitro/config"; export default defineNitroConfig({ routeRules: { "/products/**": { isr: { expiration: 60, allowQuery: ["q"], passQuery: true, }, }, }, });` By default, each unique query value is cached separately. Set `allowQuery` to an empty array to ignore query parameters for caching, or list specific parameters to cache only those. Query parameters aren't passed to your route handler unless you set `passQuery: true`. The `isr` rule accepts these options: | Option          | Type              | Default     | Description                                                                                                                                          | | --------------- | ----------------- | ----------- | ---------------------------------------------------------------------------------------------------------------------------------------------------- | | `expiration`    | `number \| false` | `false`     | Seconds before the cached response is regenerated by invoking the function. `false` (or `isr: true`) never expires.                                  | | `group`         | `number`          | —           | Group number for the asset. Assets in the same group revalidate together.                                                                            | | `allowQuery`    | `string[]`        | `undefined` | Query parameters cached independently. An empty array ignores queries. `undefined` caches each unique value. For `/**` rules, `url` is always added. | | `passQuery`     | `boolean`         | `false`     | Pass the query string to the function on the request argument. The `allowQuery` filter still applies.                                                | | `exposeErrBody` | `boolean`         | `false`     | Expose the response body for error status codes.                                                                                                     | To purge the cache for a route on demand, set a bypass token and send a revalidation request: 1. Generate a secret and store it as an [environment variable](https://vercel.com/docs/environment-variables) such as `VERCEL_BYPASS_TOKEN`:
   
   `openssl rand -base64 32`
   
2. Reference the secret in your config:
   
   `import { defineNitroConfig } from "nitro/config"; export default defineNitroConfig({ vercel: { config: { bypassToken: process.env.VERCEL_BYPASS_TOKEN, }, }, });`
   
3. Send a `GET` or `HEAD` request to the route with the header `x-prerender-revalidate: <bypassToken>`. Vercel revalidates the cache, and the next request returns a fresh response.
   

### Run scheduled tasks as cron jobs

Nitro converts its `scheduledTasks` configuration into [Vercel Cron Jobs](https://vercel.com/docs/cron-jobs) at build time, so you don't write any `vercel.json` cron configuration. Enable tasks and define your schedules:

`import { defineNitroConfig } from "nitro/config"; export default defineNitroConfig({ experimental: { tasks: true, }, scheduledTasks: { "0 * * * *": ["cms:update"], // every hour "0 0 * * *": ["db:cleanup"], // every day at midnight }, });` To prevent unauthorized access to the cron handler, set a `CRON_SECRET` environment variable in your [project settings](https://vercel.com/d?to=%2F%5Bteam%5D%2F%5Bproject%5D%2Fsettings%2Fenvironment-variables). When it's set, Nitro validates the `Authorization` header on every cron invocation.

### Process messages with Vercel Queues

Nitro integrates with [Vercel Queues](https://vercel.com/docs/queues) to process messages asynchronously. Define your topics in the config, then handle incoming messages with the `vercel:queue` hook in a [Nitro plugin](https://nitro.build/guide/plugins).

Define the topics:

`import { defineNitroConfig } from "nitro/config"; export default defineNitroConfig({ vercel: { queues: { triggers: [{ topic: "notifications" }, { topic: "orders", retryAfterSeconds: 60, initialDelaySeconds: 5 }, ], }, }, });` Handle messages in a plugin: ``export default defineNitroPlugin((nitro) => { nitro.hooks.hook("vercel:queue", ({ message, metadata, send }) => { console.log(`[${metadata.topicName}] ${metadata.messageId}:`, message); }); });`` Send messages with the `@vercel/queue` package: `import { send } from "@vercel/queue"; export default defineEventHandler(async (event) => { const order = await event.req.json(); const { messageId } = await send("orders", order); return { messageId }; });` Queues also work in `nitro dev`: `send()` delivers messages straight to your hook, so you can iterate without deploying. Run `vercel link` and `vercel env pull` first so the SDK can authenticate. ### Proxy requests at the CDN Nitro optimizes `proxy` route rules by generating [CDN-level rewrites](https://vercel.com/docs/rewrites) at build time. Matching requests are proxied through Vercel's CDN without invoking a function, thereby reducing latency and costs.

`import { defineNitroConfig } from "nitro/config"; export default defineNitroConfig({ routeRules: { "/api/**": { proxy: "https://api.example.com/**", }, }, });`

A proxy rule moves to a CDN rewrite when the target is an external URL (starting with `http://` or `https://`) and the rule sets no advanced `ProxyOptions`. If you use options such as `headers`, `forwardHeaders`, `fetchOptions`, cookie rewriting, or `onResponse`, Nitro keeps the proxy at runtime inside the function instead.

### Set per-route function configuration

Use `vercel.functionRules` to override function settings for specific routes. Each key is a route pattern, and its value merges with your base `vercel.functions` config. Array values like `regions` replace the base array rather than merging with it.

`import { defineNitroConfig } from "nitro/config"; export default defineNitroConfig({ vercel: { functionRules: { "/api/heavy-computation": { maxDuration: 800, memory: 4096, }, "/api/regional": { regions: ["lhr1", "cdg1"], }, }, }, });` Route patterns support wildcards, so `/api/slow/**` matches every route under `/api/slow/`. This is useful when certain routes need different resource limits, regions, or features like Vercel Queues triggers. ### Use the Bun runtime Nitro runs your functions on Node.js by default. To use [Bun](https://vercel.com/docs/functions/runtimes/bun) instead, set the runtime in `vercel.functions`:

`import { defineNitroConfig } from "nitro/config"; export default defineNitroConfig({ vercel: { functions: { runtime: "bun1.x", }, }, });`

Nitro also detects Bun automatically when you set a `bunVersion` in `vercel.json`:

`{ "$schema": "https://openapi.vercel.sh/vercel.json", "bunVersion": "1.x" }`

### Monitor performance with Observability

[Vercel Observability](https://vercel.com/products/observability) breaks down function performance by route, so you can find slow paths and optimization opportunities. Nitro generates the routing hints these insights need. Set a compatibility date of `2025-07-15` or later to turn this on:

`import { defineNitroConfig } from "nitro/config"; export default defineNitroConfig({ compatibilityDate: "2025-07-15", // or "latest" });`

## Best practices

### Put API routes in routes/api

Nitro's top-level `/api` directory isn't compatible with Vercel. Put your API handlers in `routes/api/` instead so they deploy correctly.

### Configure Nuxt under the nitro key

Nuxt is built on Nitro, so if you're using Nuxt, place these options under the `nitro` key in `nuxt.config.ts`:

`export default defineNuxtConfig({ nitro: { routeRules: { "/products/**": { isr: { expiration: 60 }, }, }, }, });`

## Resources and next steps

- Read the full [Nitro on Vercel documentation](https://vercel.com/docs/frameworks/backend/nitro)
  
- Configure Vercel-specific features through the [Deploy Nitro to Vercel](https://nitro.build/deploy/providers/vercel) provider docs
  
- Learn how [Vercel Functions](https://vercel.com/docs/functions) run your server code
  
- Understand pricing and scaling with [Fluid compute](https://vercel.com/docs/fluid-compute)
  
- Cache and revalidate content with [Incremental Static Regeneration](https://vercel.com/docs/incremental-static-regeneration)
  
- Explore [Nitro templates](https://vercel.com/templates/nitro) you can deploy in one step

---

[View full KB sitemap](/kb/sitemap.md)
