Skip to content

How to ship a Nitro app on Vercel

Deploy a Nitro app to Vercel with zero configuration. Learn how to ship from a template, the Vercel CLI, or Git, and configure ISR, cron jobs, Vercel Queues, per-route function settings, and Observability.

7 min read
Last updated June 7, 2026

Nitro 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 running on Fluid compute, and you get preview deployments, observability, and Vercel Firewall without extra setup.

This guide walks you through deploying a Nitro app to Vercel from a template, the Vercel CLI, or a Git repository, and configuring features such as Incremental Static Regeneration, cron jobs, queues, and observability.

Before you begin, make sure you have:

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

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.

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

The fastest way to ship a Nitro app is to start from a template. Browse the Nitro templates gallery, 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:

To scaffold a new Nitro project locally, use the Vercel CLI init command. It clones Vercel's Nitro example into a folder named nitro.

  1. Create the project:
    Terminal
    vercel init nitro
  2. Install dependencies:
    Terminal
    cd nitro
    npm install
  3. Develop locally at http://localhost:3000:
    Terminal
    npm run dev
  4. Create a preview deployment. The first run creates a Vercel project link:
    Terminal
    vercel
  5. Promote your changes to production:
    Terminal
    vercel --prod

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

Terminal
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, or adjust the imports to nitropack/config for nitropack v2.

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.

Each Nitro server route automatically becomes a Vercel Function. 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.

ISR 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:

nitro.config.ts
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:

OptionTypeDefaultDescription
expirationnumber | falsefalseSeconds before the cached response is regenerated by invoking the function. false (or isr: true) never expires.
groupnumberGroup number for the asset. Assets in the same group revalidate together.
allowQuerystring[]undefinedQuery parameters cached independently. An empty array ignores queries. undefined caches each unique value. For /** rules, url is always added.
passQuerybooleanfalsePass the query string to the function on the request argument. The allowQuery filter still applies.
exposeErrBodybooleanfalseExpose 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 such as VERCEL_BYPASS_TOKEN:
    Terminal
    openssl rand -base64 32
  2. Reference the secret in your config:
    nitro.config.ts
    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.

Nitro converts its scheduledTasks configuration into Vercel Cron Jobs at build time, so you don't write any vercel.json cron configuration. Enable tasks and define your schedules:

nitro.config.ts
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. When it's set, Nitro validates the Authorization header on every cron invocation.

Nitro integrates with Vercel Queues to process messages asynchronously. Define your topics in the config, then handle incoming messages with the vercel:queue hook in a Nitro plugin.

Define the topics:

nitro.config.ts
import { defineNitroConfig } from "nitro/config";
export default defineNitroConfig({
vercel: {
queues: {
triggers: [
{ topic: "notifications" },
{ topic: "orders", retryAfterSeconds: 60, initialDelaySeconds: 5 },
],
},
},
});

Handle messages in a plugin:

server/plugins/queues.ts
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:

server/routes/api/orders.post.ts
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.

Nitro optimizes proxy route rules by generating CDN-level rewrites at build time. Matching requests are proxied through Vercel's CDN without invoking a function, thereby reducing latency and costs.

nitro.config.ts
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.

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.

nitro.config.ts
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.

Nitro runs your functions on Node.js by default. To use Bun instead, set the runtime in vercel.functions:

nitro.config.ts
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:

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

Vercel 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:

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

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

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

nuxt.config.ts
export default defineNuxtConfig({
nitro: {
routeRules: {
"/products/**": {
isr: { expiration: 60 },
},
},
},
});

Was this helpful?

supported.