Vercel

Deploy Nitro apps to Vercel.

Preset: vercel

Read more in Vercel Framework Support.
Integration with this provider is possible with zero configuration.

Getting started

Deploying to Vercel comes with the following features, among others:

Learn more in the Vercel documentation.

Deploy with Git

Vercel supports Nitro with zero configuration. Deploy Nitro to Vercel now.

API routes

Nitro's top-level /api directory isn't compatible with Vercel. Use the routes/api/ directory instead.

Bun runtime

Read more in Vercel.

You can use Bun instead of Node.js by specifying the runtime using the vercel.functions key inside nitro.config:

nitro.config.ts
import { defineConfig } from "nitro";

export default defineConfig({
  vercel: {
    functions: {
      runtime: "bun1.x"
    }
  }
})

Alternatively, Nitro also detects Bun automatically if you specify a bunVersion property in your vercel.json:

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

Per-route function configuration

Use vercel.functionRules to override serverless function settings for specific routes. Each key is a route pattern and its value is a partial function configuration object that gets merged with the base vercel.functions config.

Array properties (e.g., regions) from route config replace the base config arrays rather than merging with them.

This is useful when certain routes need different resource limits, regions, or features like Vercel Queues triggers.

nitro.config.ts
import { defineConfig } from "nitro";

export default defineConfig({
  vercel: {
    functionRules: {
      "/api/heavy-computation": {
        maxDuration: 800,
        memory: 4096,
      },
      "/api/regional": {
        regions: ["lhr1", "cdg1"],
      },
      "/api/queues/process-order": {
        experimentalTriggers: [{ type: "queue/v2beta", topic: "orders" }],
      },
    },
  },
});

Route patterns support wildcards via rou3 matching (e.g., /api/slow/** matches all routes under /api/slow/).

Proxy route rules

Nitro automatically optimizes proxy route rules on Vercel by generating CDN-level rewrites at build time. This means matching requests are proxied at the edge without invoking a serverless function, reducing latency and cost.

nitro.config.ts
import { defineConfig } from "nitro";

export default defineConfig({
  routeRules: {
    // Proxied at CDN level — no function invocation
    "/api/**": {
      proxy: "https://api.example.com/**",
    },
  },
});

When CDN rewrites apply

A proxy rule is offloaded to a Vercel CDN rewrite when all of the following are true:

  • The target is an external URL (starts with http:// or https://).
  • No advanced ProxyOptions are set on the rule.

Fallback to runtime proxy

When the proxy rule uses any of the following ProxyOptions, Nitro keeps it as a runtime proxy handled by the serverless function:

  • headers — custom headers on the outgoing request to the upstream
  • forwardHeaders / filterHeaders — header filtering
  • fetchOptions — custom fetch options
  • cookieDomainRewrite / cookiePathRewrite — cookie manipulation
  • onResponse — response callback
Response headers defined on the route rule via the headers option are still applied to CDN-level rewrites. Only request-level ProxyOptions.headers (sent to the upstream) require a runtime proxy.

Scheduled tasks (Cron Jobs)

Read more in Vercel Cron Jobs.

Nitro automatically converts your scheduledTasks configuration into Vercel Cron Jobs at build time. Define your schedules in your Nitro config and deploy — no manual vercel.json cron configuration required.

nitro.config.ts
import { defineConfig } from "nitro";

export default defineConfig({
  experimental: {
    tasks: true
  },
  scheduledTasks: {
    // Run `cms:update` every hour
    '0 * * * *': ['cms:update'],
    // Run `db:cleanup` every day at midnight
    '0 0 * * *': ['db:cleanup']
  }
})

Secure cron job endpoints

Read more in Securing cron jobs.

To prevent unauthorized access to the cron handler, set a CRON_SECRET environment variable in your Vercel project settings. When CRON_SECRET is set, Nitro validates the Authorization header on every cron invocation.

Queues

Read more in Vercel Queues.

Nitro integrates with Vercel Queues to process messages asynchronously. Define your queue topics in the Nitro config and handle incoming messages with the vercel:queue runtime hook.

nitro.config.ts
import { defineConfig } from "nitro";

export default defineConfig({
  // Standalone Nitro scans `serverDir` for routes and plugins
  serverDir: "./server",
  vercel: {
    queues: {
      triggers: [
        // Only `topic` is required
        { topic: "notifications" },
        { topic: "orders", retryAfterSeconds: 60, initialDelaySeconds: 5 },
      ],
    },
  },
});

Handling messages

Use the vercel:queue hook in a Nitro plugin to process incoming queue messages:

server/plugins/queues.ts
import { definePlugin } from "nitro";

export default definePlugin((nitroApp) => {
  nitroApp.hooks.hook("vercel:queue", ({ message, metadata, send }) => {
    console.log(`[${metadata.topicName}] Message ${metadata.messageId}:`, message);
  });
});

Running tasks from queue messages

You can use queue messages to trigger Nitro tasks:

server/plugins/queues.ts
import { definePlugin } from "nitro";
import { runTask } from "nitro/task";

export default definePlugin((nitroApp) => {
  nitroApp.hooks.hook("vercel:queue", async ({ message, metadata }) => {
    if (metadata.topicName === "orders") {
      await runTask("orders:fulfill", { payload: message });
    }
  });
});

Sending messages

Use the @vercel/queue package directly to send messages to a topic:

server/routes/api/orders.post.ts
import { defineHandler } from "nitro";
import { send } from "@vercel/queue";

export default defineHandler(async (event) => {
  const order = await event.req.json();
  const { messageId } = await send("orders", order);
  return { messageId };
});

Local development

Queues work in nitro devsend() delivers messages straight to your vercel:queue hook, so you can iterate without deploying. Pull your Vercel environment first with vercel link and vercel env pull so the SDK can authenticate.

If your hook throws, the message is retried locally. Retries honor retryAfterSeconds from each trigger when set.

Custom build output configuration

You can provide additional build output configuration using the vercel.config key inside nitro.config. It will be merged with the built-in auto-generated config.

Other preset options

Additional options are available under the vercel key in your Nitro config:

  • entryFormat: Handler format for Vercel Functions. "web" (default) or "node" — the node format enables compatibility with Node.js specific APIs (e.g., req.runtime.node).
  • regions: List of regions for edge functions.
  • skewProtection: Set to false to disable the Nitro skew protection integration (enabled by default when skew protection is enabled in the Vercel dashboard).
  • cronHandlerRoute: Route path for the Vercel cron handler endpoint used with scheduledTasks (default: "/_vercel/cron").

On-demand incremental static regeneration (ISR)

On-demand revalidation lets you purge the cache for an ISR route whenever you want, without waiting for the interval used by background revalidation.

To revalidate a page on demand:

Create an environment variable to store a revalidation secret
  • You can use the command openssl rand -base64 32 or Generate a Secret to generate a random value.

Update your configuration:
nitro.config.ts
import { defineConfig } from "nitro";

export default defineConfig({
  vercel: {
    config: {
      bypassToken: process.env.VERCEL_BYPASS_TOKEN
    }
  }
})

To revalidate a path to a Prerender Function on demand, make a GET or HEAD request to that path with an x-prerender-revalidate: <bypassToken> header. When the Prerender Function endpoint is accessed with this header set, the cache is revalidated, and the next request to that function should return a fresh response.

Fine-grained ISR config via route rules

By default, query params affect cache keys but are not passed to the route handler unless specified.

You can pass an options object to the isr route rule to configure caching behavior:

  • expiration: Expiration time (in seconds) before the cached asset is re-generated by invoking the Serverless Function. Setting the value to false (or the isr: true route rule) means it never expires.
  • group: Group number of the asset. Prerendered assets with the same group number are all re-validated at the same time.
  • allowQuery: List of query string parameter names that are cached independently.
    • If an empty array, query values are not considered for caching.
    • If undefined, each unique query value is cached independently.
    • For wildcard /** route rules, url is always added.
  • passQuery: When true, the query string is present on the request argument passed to the invoked function. The allowQuery filter still applies.
  • exposeErrBody: When true, expose the response body regardless of status code, including error status codes (default: false).
nitro.config.ts
import { defineConfig } from "nitro";

export default defineConfig({
  routeRules: {
    "/products/**": {
      isr: {
        allowQuery: ["q"],
        passQuery: true,
        exposeErrBody: true
      },
    },
  },
});