
Nitro
- 8 installs
- 329 repo stars
- Updated August 5, 2026
- getsentry/junior
nitro skill documents Build and deploy universal JavaScript servers with Nitro v3.
About
nitro skill documents Build and deploy universal JavaScript servers with Nitro v3. Use when working with nitro.config.ts, defineNitroConfig, defineHandler, defineConfig, server.ts entry, filesystem routing, route rules, useStorage, defineCachedHandler, useDatabase, definePlugin, runtime hooks, Vercel/Cloudflare deploymen. name: nitro description: Build and deploy universal JavaScript servers with Nitro v3. Use when working with nitro.config.ts, defineNitroConfig, defineHandler, defineConfig, server.ts entry, filesystem routing, route rules, useStorage, defineCachedHandler, useDatabase, definePlugin, runtime hooks, Vercel/Cloudflare deployment, or migrating from Nitro v2/nitropack.
- Build and deploy universal JavaScript servers with Nitro v3.
- Platform-specific setup patterns for nitro.
- Evidence-backed steps from upstream SKILL.md.
- When-to-use criteria for nitro versus alternatives.
Nitro by the numbers
- 8 all-time installs (skills.sh)
- Ranked #839 of 1,352 Code Review & Quality skills by installs in the Skillselion catalog
- Data as of Aug 5, 2026 (Skillselion catalog sync)
nitro capabilities & compatibility
- Capabilities
- nitro quick start · nitro when to use guidance · nitro integration patterns
- Works with
- sentry
- Use cases
- code review
What nitro says it does
Build, configure, and deploy Nitro v3 applications using correct APIs and patterns.
| Request type | Read first |
npx skills add https://github.com/getsentry/junior --skill nitroAdd your badge
Show developers this skill is listed on Skillselion. Paste this into your README.
| Installs | 8 |
|---|---|
| repo stars | ★ 329 |
| Last updated | August 5, 2026 |
| Repository | getsentry/junior ↗ |
How do I use nitro correctly?
Build and deploy universal JavaScript servers with Nitro v3. Use when working with nitro.config.ts, defineNitroConfig, defineHandler, defineConfig, server.ts entry, filesystem routing, route rules, us
Who is it for?
Teams implementing nitro workflows from the catalog.
Skip if: Skip when requirements clearly match a different specialized stack.
When should I use this skill?
User asks about nitro, build and deploy universal javascript servers with nitro v3. use when working with nitro.c.
What you get
Working nitro setup with validated configuration and next steps.
Files
Build, configure, and deploy Nitro v3 applications using correct APIs and patterns.
Step 1: Classify the request
| Request type | Read first |
|---|---|
| API surface, handler signatures, imports, config options | references/api-surface.md |
| Setup, routing, caching, storage, plugins, frameworks, common patterns | references/common-use-cases.md |
| Build failures, runtime errors, deployment issues, migration from v2 | references/troubleshooting-workarounds.md |
Load only the reference(s) matching the request. If the task spans categories, load relevant files.
Step 2: Apply core guardrails
1. Import defineHandler from "nitro", not defineEventHandler (v2 API). 2. Import config helpers from subpaths: "nitro/config", "nitro/cache", "nitro/storage", "nitro/database", "nitro/runtime-config", "nitro/types". 3. Use web standard event.req (Request) for body/headers — not v2 utilities like readBody or getHeader. 4. Never return from middleware unless intentionally terminating the request. 5. Only GET/HEAD requests are cached by defineCachedHandler; other methods bypass automatically. 6. useDatabase and defineTask require experimental feature flags. 7. Use "nitro" package name, not "nitropack" (v2).
Step 3: Implement
1. For new projects, use defineConfig from "nitro" in nitro.config.ts or add nitro() plugin from "nitro/vite" to vite.config.ts. 2. For server entry, export a web-compatible fetch(Request): Response handler from server.ts, or use server.node.ts for Express/Fastify. 3. For filesystem routes, place handlers in routes/ or api/ with [param] for dynamic segments and .get.ts/.post.ts for method-specific routes. 4. For caching, use defineCachedHandler from "nitro/cache" with maxAge and swr options. 5. For storage, use useStorage(namespace) from "nitro/storage" and configure drivers via storage config. 6. For plugins, create files in plugins/ directory using definePlugin and hook into request, response, error, or close. 7. For deployment, set preset in config or use NITRO_PRESET env var; Vercel/Netlify/Cloudflare are auto-detected.
Step 4: Validate
1. Run nitro dev and verify routes respond correctly. 2. Run nitro build and check .output/server/ contains expected files. 3. For cached routes, verify cache headers (etag, cache-control) and 304 responses. 4. For storage, verify data persists across requests with configured driver. 5. For deployment, verify the preset produces correct output format.
Step 5: Troubleshoot
1. defineEventHandler is not defined → use defineHandler from "nitro" (v3 API). 2. Cannot find module 'nitropack' → rename to "nitro" in imports and package.json. 3. Route not matched → check file is in routes/ or api/, or verify routes config mapping. 4. Middleware returning responses unexpectedly → ensure middleware does not return a value. 5. For detailed diagnostics, read references/troubleshooting-workarounds.md.
Nitro v3 API Surface
Contents
- Configuration
- Handler definition
- Routing
- Route rules
- Server entry
- Caching
- Storage
- Database
- Plugins and hooks
- Tasks
- WebSocket
- Assets
- Runtime config
- Modules (build-time)
- Deployment presets
---
Configuration
Standalone (nitro.config.ts)
import { defineNitroConfig } from "nitro/config";
export default defineNitroConfig({
preset: "vercel",
serverDir: "./server",
routeRules: { "/api/**": { cache: true } },
});defineNitroConfig is also aliased as defineConfig from "nitro":
import { defineConfig } from "nitro";
export default defineConfig({ preset: "node" });Vite plugin (vite.config.ts)
import { defineConfig } from "vite";
import { nitro } from "nitro/vite";
export default defineConfig({
plugins: [nitro()],
nitro: {
serverDir: "./server",
},
});Environment-specific config
Uses c12 conventions with $development and $production keys:
export default defineNitroConfig({
$development: { debug: true },
$production: { minify: true },
});Key config options
| Option | Default | Description |
|---|---|---|
preset | auto-detected | Deployment target (vercel, cloudflare_pages, node_server, etc.) |
compatibilityDate | "latest" | Locks preset behavior to a date |
serverDir | false | Server source directory ("./server" or "./" to enable scanning) |
baseURL | "/" | Server base URL |
apiBaseURL | "/api" | API routes prefix |
imports | false | Auto-imports config (set to {} to enable) |
modules | [] | Build-time Nitro modules |
plugins | [] | Runtime plugins (auto-scanned from plugins/) |
routes | {} | Programmatic route-to-handler mapping |
handlers | [] | Programmatic handler/middleware registration |
routeRules | {} | Pattern-based route rules |
runtimeConfig | {} | Runtime config (env override via NITRO_ prefix) |
storage / devStorage | {} | Storage driver configuration |
database / devDatabase | {} | Database connector configuration |
features.websocket | false | Enable WebSocket support |
experimental.database | false | Enable database layer |
experimental.tasks | false | Enable tasks |
experimental.openAPI | false | Enable OpenAPI endpoints |
builder | auto | "rollup" / "rolldown" / "vite" |
minify | false | Minify production bundle |
sourcemap | false | Source maps |
ignore | [] | Glob patterns to ignore during route scanning |
Directory options
| Option | Default | Description |
|---|---|---|
rootDir | . | Project root |
serverDir | false | Server source dir |
buildDir | node_modules/.nitro | Build artifacts |
output.dir | .output | Production output |
output.serverDir | .output/server | Server output |
output.publicDir | .output/public | Public assets output |
Environment variables
| Variable | Description |
|---|---|
NITRO_PRESET | Override deployment preset |
NITRO_COMPATIBILITY_DATE | Set compatibility date |
NITRO_APP_BASE_URL | Override base URL |
---
Handler definition
import { defineHandler } from "nitro";
export default defineHandler((event) => {
return { hello: "world" };
});The event is an H3Event with web standard properties:
| Property | Type | Description |
|---|---|---|
event.req | Request | Web standard Request object |
event.res | ResponseInit | Response headers/status |
event.url | URL | Parsed URL |
event.path | string | URL pathname |
event.method | string | HTTP method |
event.context | object | Shared context (params, custom data) |
event.context.params | object | Route parameters |
Reading request body (web standard)
const json = await event.req.json();
const text = await event.req.text();
const formData = await event.req.formData();
const stream = event.req.body;Reading/setting headers (web standard)
event.req.headers.get("x-foo");
event.res.headers.set("x-foo", "bar");Errors
import { HTTPError } from "nitro";
throw new HTTPError({ status: 404, message: "Not found" });Middleware
import { defineMiddleware } from "nitro";
export default defineMiddleware((event) => {
event.context.auth = { user: "admin" };
// Do NOT return — returning terminates the request
});---
Routing
Filesystem routing
Files in routes/ or api/ are automatically mapped to URL paths:
routes/
api/
test.ts → /api/test
hello.get.ts → /hello (GET only)
hello.post.ts → /hello (POST only)
users/[id].ts → /users/:id
pages/[...slug].ts → /pages/* (catch-all)
[...].ts → /* (default catch-all)Dynamic parameters
- Single:
[param]— accessed viaevent.context.params.param - Multiple:
[param1]/[param2]— each segment is a separate folder - Catch-all:
[...param]— captures remaining path including slashes
HTTP method suffix
Append .get.ts, .post.ts, .put.ts, .delete.ts, .patch.ts, etc.
Route groups
Parenthesized folders (groupname)/ organize files without affecting URL paths:
routes/api/(admin)/users.ts → /api/users
routes/api/(public)/index.ts → /apiEnvironment-specific handlers
.dev.ts, .prod.ts, .prerender.ts suffixes restrict to specific build environments.
Programmatic routes
export default defineNitroConfig({
routes: {
"/api/hello": "./server/routes/api/hello.ts",
"/api/custom": {
handler: "./server/routes/api/hello.ts",
method: "POST",
lazy: true,
},
},
});Route entry options: handler, method, lazy, format ("web" | "node"), env.
Programmatic handlers (middleware)
export default defineNitroConfig({
handlers: [
{
route: "/api/**",
handler: "./server/middleware/api-auth.ts",
middleware: true,
},
],
});Handler entry options: route, handler, method, middleware, lazy, format, env.
Middleware
Auto-registered from middleware/ directory. Execution order follows alphabetical sort (prefix with numbers: 01.logger.ts, 02.auth.ts).
Code splitting
Each route handler gets its own chunk, loaded on demand at first request.
---
Route rules
Pattern-based rules applied via config. Pattern matching follows rou3.
export default defineNitroConfig({
routeRules: {
"/blog/**": { swr: 600 },
"/assets/**": { headers: { "cache-control": "s-maxage=0" } },
"/api/v1/**": { cors: true },
"/old-page": { redirect: "/new-page" },
"/proxy/**": { proxy: "https://api.example.com/**" },
"/admin/**": { basicAuth: { username: "admin", password: "secret" } },
},
});Available rule options
| Option | Type | Description |
|---|---|---|
headers | Record<string, string> | Custom response headers |
redirect | `string \ | { to, status? }` |
proxy | `string \ | { to, ...proxyOptions }` |
cors | boolean | Permissive CORS headers |
cache | `object \ | false` |
swr | `boolean \ | number` |
static | `boolean \ | number` |
basicAuth | `{ username, password, realm? } \ | false` |
prerender | boolean | Prerender at build time |
isr | `boolean \ | number \ |
Rules merge from least to most specific. Use false to disable an inherited rule.
Runtime route rules
Override via runtimeConfig.nitro.routeRules and environment variables.
---
Server entry
server.ts is auto-detected in project root and acts as a catch-all handler for unmatched routes.
Web-compatible framework
// server.ts — Hono
import { Hono } from "hono";
const app = new Hono();
app.get("/", (c) => c.text("Hello from Hono!"));
export default app;Node.js framework (server.node.ts)
// server.node.ts — Express
import Express from "express";
const app = Express();
app.use("/", (_req, res) => res.send("Hello from Express!"));
export default app;Config options
export default defineNitroConfig({
serverEntry: "./custom-server.ts",
// or object:
serverEntry: { handler: "./server.ts", format: "node" },
// or disable:
serverEntry: false,
});Lifecycle position
Server entry runs after routes, middleware, and static assets — it catches unmatched requests before the renderer.
---
Caching
Powered by ocache, built on the storage layer.
Cached handler
import { defineCachedHandler } from "nitro/cache";
export default defineCachedHandler(
(event) => {
return { data: expensiveComputation() };
},
{ maxAge: 3600, swr: true },
);Cached function
import { defineCachedFunction } from "nitro/cache";
const cachedFetch = defineCachedFunction(
async (url: string) => {
return fetch(url).then((r) => r.json());
},
{ maxAge: 3600, name: "apiFetch", getKey: (url) => url },
);Cache options (shared)
| Option | Default | Description |
|---|---|---|
base | "cache" | Storage mount point |
name | guessed | Cache entry name |
group | "nitro/handlers" or "nitro/functions" | Cache group |
getKey | built-in hash | Custom cache key function |
integrity | function code hash | Invalidation token |
maxAge | 1 | TTL in seconds |
staleMaxAge | 0 | Max stale age (-1 for unlimited) |
swr | true | Stale-while-revalidate |
shouldInvalidateCache | — | Invalidation predicate |
shouldBypassCache | — | Bypass predicate |
Handler-only options
| Option | Description |
|---|---|
headersOnly | Skip full caching, only handle conditional requests (304) |
varies | Header names to vary cache key on |
Function-only options
| Option | Description |
|---|---|
transformEntry | Transform cache entry before returning |
validate | Validate cache entry, return false to re-resolve |
Automatic HTTP headers
Cached handlers auto-set etag, last-modified, and cache-control. Conditional requests (if-none-match, if-modified-since) return 304.
Cache key pattern
${base}:${group}:${name}:${getKey(...)}.jsonManual invalidation
import { useStorage } from "nitro/storage";
await useStorage("cache").removeItem("nitro/functions:name:key.json");Route rules caching
routeRules: {
"/blog/**": { swr: 600 },
"/api/**": { cache: { maxAge: 60, base: "redis" } },
}---
Storage
Built on unstorage. In-memory by default.
import { useStorage } from "nitro/storage";
await useStorage().setItem("key", value);
const val = await useStorage().getItem("key");
// Namespaced
const redis = useStorage("redis");
await redis.setItem("foo", "bar");Methods
| Method | Description |
|---|---|
getItem(key) | Get value (null if missing) |
setItem(key, value) | Set value |
hasItem(key) | Check existence |
removeItem(key) | Delete key |
getKeys(base?) | List keys |
clear(base?) | Clear keys |
getItemRaw(key) | Get raw binary |
setItemRaw(key, value) | Set raw binary |
getMeta(key) | Get metadata |
mount(base, driver) | Dynamic mount |
unmount(base) | Unmount driver |
watch(callback) | Watch changes |
Configuration
export default defineNitroConfig({
storage: {
redis: { driver: "redis", url: "redis://localhost:6379" },
},
devStorage: {
redis: { driver: "fs", base: "./.data/redis" },
},
});Built-in mount points
assets/server— read-only bundled server assets- Root (no base) — in-memory, not persisted
Runtime mounting via plugin
import { useStorage } from "nitro/storage";
import { definePlugin } from "nitro";
import redisDriver from "unstorage/drivers/redis";
export default definePlugin(() => {
useStorage().mount("redis", redisDriver({ host: process.env.REDIS_HOST }));
});---
Database
Experimental. Built on db0. SQLite by default (.data/db.sqlite).
export default defineNitroConfig({
experimental: { database: true },
database: {
default: { connector: "sqlite" },
users: { connector: "postgresql", options: { url: "..." } },
},
});Usage
import { useDatabase } from "nitro/database";
const db = useDatabase();
const { rows } = await db.sql`SELECT * FROM users WHERE id = ${id}`;
await db.exec("CREATE TABLE ...");Connectors
sqlite, better-sqlite3, postgresql, mysql2, pglite, libsql, planetscale, cloudflare-d1, and more.
---
Plugins and hooks
Plugins execute once at server startup. Auto-registered from plugins/ directory.
import { definePlugin } from "nitro";
export default definePlugin((nitroApp) => {
nitroApp.hooks.hook("request", (event) => {
/* ... */
});
nitroApp.hooks.hook("response", (res, event) => {
/* ... */
});
nitroApp.hooks.hook("error", (error, { event, tags }) => {
/* ... */
});
nitroApp.hooks.hook("close", () => {
/* cleanup */
});
});nitroApp context
| Property | Type | Description |
|---|---|---|
hooks | HookableCore | Hook system |
h3 | H3Core | Underlying H3 app |
fetch | (req) => Response | Internal fetch |
captureError | (error, context) => void | Manual error capture |
Runtime hooks
| Hook | Signature | When |
|---|---|---|
request | (event) => void | Start of each request |
response | (res, event) => void | After response created |
error | (error, { event?, tags? }) => void | On error capture |
close | () => void | Server shutdown |
Error tags: "request", "response", "cache", "plugin", "unhandledRejection", "uncaughtException".
---
Tasks
Experimental. File-based in tasks/ directory.
import { defineTask } from "nitro/task";
export default defineTask({
meta: { name: "db:migrate", description: "Run migrations" },
run({ payload, context }) {
return { result: "done" };
},
});Scheduled tasks
export default defineNitroConfig({
experimental: { tasks: true },
scheduledTasks: {
"0 * * * *": ["cms:update"],
"0 0 * * *": ["db:cleanup"],
},
});On Vercel, scheduled tasks auto-convert to Vercel Cron Jobs. Secure with CRON_SECRET env var.
---
WebSocket
Enable with features: { websocket: true }.
import { defineWebSocketHandler } from "nitro";
export default defineWebSocketHandler({
open(peer) {
peer.send("Welcome!");
peer.subscribe("chat");
},
message(peer, message) {
peer.publish("chat", message.toString());
},
close(peer) {
/* ... */
},
});Route convention: routes/_ws.ts. Built on crossws.
Peer API: peer.send(), peer.publish(channel, msg), peer.subscribe(channel).
---
Assets
Public assets
Files in public/ are served statically. Config via publicAssets array. Supports pre-compression (gzip, brotli, zstd).
Server assets
Files in assets/ are bundled and accessible via useStorage("assets/server").
const content = await useStorage("assets/server").getItem("data.json");Custom asset dirs via serverAssets config with baseName and dir.
---
Runtime config
Define defaults in config, override with NITRO_-prefixed env vars at runtime:
export default defineNitroConfig({
runtimeConfig: { apiKey: "", database: { host: "localhost" } },
});import { useRuntimeConfig } from "nitro/runtime-config";
const config = useRuntimeConfig();
// config.apiKey — overridden by NITRO_API_KEY
// config.database.host — overridden by NITRO_DATABASE_HOSTOnly keys defined in runtimeConfig are considered. .env files only loaded in development.
Custom prefix: set runtimeConfig.nitro.envPrefix: "APP_" to also check APP_* vars.
---
Modules (build-time)
Build-time extension mechanism:
interface NitroModule {
name?: string;
setup: (nitro: Nitro) => void | Promise<void>;
}Registered via modules config or as Vite plugins with a nitro property:
export default defineConfig({
plugins: [
nitro(),
{
name: "my-plugin",
nitro: {
setup(nitro) {
nitro.options.routes["/"] = "#virtual";
nitro.options.virtual["#virtual"] =
`export default () => new Response("Hi")`;
},
},
},
],
});---
Deployment presets
Zero-config auto-detection
Vercel, Netlify, Cloudflare, AWS Amplify, Azure, Firebase App Hosting, StormKit, Zeabur.
Manual preset
export default defineNitroConfig({ preset: "cloudflare_pages" });Or via env: NITRO_PRESET=cloudflare_pages nitro build.
Vercel-specific
/apidirectory incompatible — useroutes/api/instead- Proxy route rules auto-optimized to CDN rewrites
scheduledTasksauto-converted to Vercel Cron Jobs- ISR via
isrroute rule withexpiration,group,allowQuery,passQuery - On-demand ISR revalidation via
x-prerender-revalidateheader withbypassToken - Bun runtime via
vercel.functions.runtime: "bun1.x" - Custom build output via
vercel.config
Node.js server
Default production preset. Output: .output/server/index.mjs.
Env vars: NITRO_PORT (default 3000), NITRO_HOST, NITRO_UNIX_SOCKET, NITRO_SSL_CERT/NITRO_SSL_KEY.
Cluster mode preset: node_cluster with NITRO_CLUSTER_WORKERS.
Common Use Cases
Contents
- 1. Create a new Nitro project
- 2. Add a web framework as server entry
- 3. Build a REST API with filesystem routing
- 4. Cache expensive endpoints
- 5. Add KV storage with Redis
- 6. Create runtime plugins for cross-cutting concerns
- 7. Add WebSocket support
- 8. Configure runtime environment variables
- 9. Deploy to Vercel with ISR
- 10. Use the database layer
- 11. Schedule background tasks
- 12. Add middleware for authentication
---
1. Create a new Nitro project
Standalone
// package.json
{
"type": "module",
"scripts": {
"dev": "nitro dev",
"build": "nitro build",
"preview": "node .output/server/index.mjs"
},
"devDependencies": { "nitro": "latest" }
}// nitro.config.ts
import { defineConfig } from "nitro";
export default defineConfig({});// server.ts
export default {
fetch(req: Request) {
return new Response("Hello Nitro!");
},
};// tsconfig.json
{ "extends": "nitro/tsconfig" }With Vite
// vite.config.ts
import { defineConfig } from "vite";
import { nitro } from "nitro/vite";
export default defineConfig({ plugins: [nitro()] });Options go under the nitro key in vite.config.ts instead of a separate nitro.config.ts.
---
2. Add a web framework as server entry
Nitro auto-detects server.ts in the project root. Export any object with a fetch(Request): Response method.
Hono
// server.ts
import { Hono } from "hono";
const app = new Hono();
app.get("/", (c) => c.text("Hello from Hono!"));
export default app;Elysia
// server.ts
import { Elysia } from "elysia";
const app = new Elysia();
app.get("/", () => "Hello from Elysia!");
export default app.compile();Express (Node.js — use server.node.ts)
// server.node.ts
import Express from "express";
const app = Express();
app.use("/", (_req, res) => res.send("Hello from Express!"));
export default app;The .node.ts suffix tells Nitro to convert the Node.js handler to a web-compatible one.
Fastify (Node.js — use server.node.ts)
// server.node.ts
import Fastify from "fastify";
const app = Fastify();
app.get("/", () => "Hello from Fastify!");
await app.ready();
export default app.routing;---
3. Build a REST API with filesystem routing
routes/
api/
users/
index.ts → GET /api/users (list)
index.post.ts → POST /api/users (create)
[id].get.ts → GET /api/users/:id (read)
[id].put.ts → PUT /api/users/:id (update)
[id].delete.ts → DELETE /api/users/:id (delete)// routes/api/users/index.ts
import { defineHandler } from "nitro";
export default defineHandler(() => {
return [{ id: "1", name: "Alice" }];
});// routes/api/users/index.post.ts
import { defineHandler } from "nitro";
export default defineHandler(async (event) => {
const body = await event.req.json();
return { created: true, user: body };
});// routes/api/users/[id].get.ts
import { defineHandler } from "nitro";
export default defineHandler((event) => {
return { id: event.context.params.id, name: "Alice" };
});Enable scanning with serverDir:
// nitro.config.ts
import { defineConfig } from "nitro";
export default defineConfig({ serverDir: "./" });---
4. Cache expensive endpoints
Cache entire route response
// routes/api/stats.ts
import { defineCachedHandler } from "nitro/cache";
export default defineCachedHandler(
async () => {
const data = await fetch("https://api.example.com/stats").then((r) =>
r.json(),
);
return data;
},
{ maxAge: 3600 }, // 1 hour
);Cache a reusable function
import { defineCachedFunction } from "nitro/cache";
import { defineHandler } from "nitro";
const cachedGHStars = defineCachedFunction(
async (repo: string) => {
const data = await fetch(`https://api.github.com/repos/${repo}`).then((r) =>
r.json(),
);
return data.stargazers_count;
},
{ maxAge: 3600, name: "ghStars", getKey: (repo) => repo },
);
export default defineHandler(async (event) => {
const stars = await cachedGHStars(event.context.params.repo);
return { stars };
});Cache via route rules (no code changes)
// nitro.config.ts
export default defineNitroConfig({
routeRules: {
"/api/stats/**": { swr: 600 },
"/api/realtime/**": { cache: false },
},
});Bypass cache conditionally
defineCachedHandler(handler, {
shouldBypassCache: ({ req }) => req.url.includes("skipCache=true"),
});---
5. Add KV storage with Redis
// nitro.config.ts
import { defineNitroConfig } from "nitro/config";
export default defineNitroConfig({
storage: {
redis: { driver: "redis", url: "redis://localhost:6379" },
},
devStorage: {
redis: { driver: "fs", base: "./.data/redis" },
},
});// routes/api/counter.ts
import { defineHandler } from "nitro";
import { useStorage } from "nitro/storage";
export default defineHandler(async () => {
const storage = useStorage("redis");
const count = ((await storage.getItem<number>("count")) ?? 0) + 1;
await storage.setItem("count", count);
return { count };
});Dynamic mounting in a plugin
// plugins/storage.ts
import { definePlugin } from "nitro";
import { useStorage } from "nitro/storage";
import redisDriver from "unstorage/drivers/redis";
export default definePlugin(() => {
useStorage().mount(
"redis",
redisDriver({
host: process.env.REDIS_HOST,
port: Number(process.env.REDIS_PORT),
}),
);
});---
6. Create runtime plugins for cross-cutting concerns
Request logging
// plugins/logger.ts
import { definePlugin } from "nitro";
export default definePlugin((nitroApp) => {
nitroApp.hooks.hook("request", (event) => {
console.log(`[${event.method}] ${event.path}`);
});
});Error reporting
// plugins/errors.ts
import { definePlugin } from "nitro";
export default definePlugin((nitroApp) => {
nitroApp.hooks.hook("error", (error, { event, tags }) => {
console.error(`Error [${tags?.join(",")}] on ${event?.path}:`, error);
// Send to error tracking service
});
});Security headers
// plugins/security.ts
import { definePlugin } from "nitro";
export default definePlugin((nitroApp) => {
nitroApp.hooks.hook("response", (res) => {
res.headers.set("x-content-type-options", "nosniff");
res.headers.set("x-frame-options", "DENY");
});
});Graceful shutdown
// plugins/cleanup.ts
import { definePlugin } from "nitro";
export default definePlugin((nitroApp) => {
nitroApp.hooks.hook("close", async () => {
await closeDatabaseConnections();
});
});---
7. Add WebSocket support
// nitro.config.ts
import { defineConfig } from "nitro";
export default defineConfig({
serverDir: "./",
features: { websocket: true },
});// routes/_ws.ts
import { defineWebSocketHandler } from "nitro";
export default defineWebSocketHandler({
open(peer) {
peer.send("Welcome!");
peer.subscribe("chat");
},
message(peer, message) {
const msg = message.toString();
if (msg === "ping") {
peer.send("pong");
} else {
peer.publish("chat", msg);
}
},
close(peer) {
peer.publish("chat", `${peer} disconnected`);
},
});Client connects to ws://localhost:3000/_ws.
---
8. Configure runtime environment variables
// nitro.config.ts
export default defineNitroConfig({
runtimeConfig: {
apiKey: "",
database: { host: "localhost", port: 5432 },
},
});// routes/api/config.ts
import { defineHandler } from "nitro";
import { useRuntimeConfig } from "nitro/runtime-config";
export default defineHandler(() => {
const config = useRuntimeConfig();
return { host: config.database.host };
});Override in production via env vars:
NITRO_API_KEY=secret
NITRO_DATABASE_HOST=db.example.com
NITRO_DATABASE_PORT=5433Custom prefix:
runtimeConfig: {
nitro: { envPrefix: "APP_" },
apiKey: "",
}
// Now both NITRO_API_KEY and APP_API_KEY work---
9. Deploy to Vercel with ISR
// nitro.config.ts
export default defineNitroConfig({
preset: "vercel",
routeRules: {
"/products/**": {
isr: {
expiration: 60,
allowQuery: ["q"],
passQuery: true,
},
},
"/api/**": { proxy: "https://api.example.com/**" }, // CDN-level rewrite
},
});On-demand revalidation:
export default defineNitroConfig({
vercel: {
config: { bypassToken: process.env.VERCEL_BYPASS_TOKEN },
},
});Trigger revalidation with x-prerender-revalidate: <bypassToken> header.
---
10. Use the database layer
// nitro.config.ts
export default defineNitroConfig({
experimental: { database: true },
database: {
default: { connector: "sqlite" },
},
devDatabase: {
default: { connector: "sqlite", options: { name: "dev-db" } },
},
});// routes/api/users.ts
import { defineHandler } from "nitro";
import { useDatabase } from "nitro/database";
export default defineHandler(async () => {
const db = useDatabase();
const { rows } = await db.sql`SELECT * FROM users`;
return { users: rows };
});// tasks/db/migrate.ts
import { defineTask } from "nitro/task";
import { useDatabase } from "nitro/database";
export default defineTask({
meta: { description: "Run database migrations" },
async run() {
const db = useDatabase();
await db.sql`CREATE TABLE IF NOT EXISTS users (id TEXT PRIMARY KEY, name TEXT)`;
return { result: "Migrations complete" };
},
});---
11. Schedule background tasks
// nitro.config.ts
export default defineNitroConfig({
experimental: { tasks: true },
scheduledTasks: {
"0 * * * *": ["cache:warm"],
"0 0 * * *": ["db:cleanup"],
},
});// tasks/cache/warm.ts
import { defineTask } from "nitro/task";
export default defineTask({
meta: { name: "cache:warm", description: "Warm API caches" },
async run() {
await fetch("http://localhost:3000/api/stats");
return { result: "Cache warmed" };
},
});On Vercel, set CRON_SECRET env var to secure cron endpoints.
---
12. Add middleware for authentication
Global middleware (all routes)
// middleware/01.auth.ts
import { defineMiddleware } from "nitro";
export default defineMiddleware((event) => {
const token = event.req.headers.get("authorization");
if (event.path.startsWith("/api/protected") && !token) {
return new Response("Unauthorized", { status: 401 });
}
if (token) {
event.context.user = { token };
}
});Route-scoped middleware (config-based)
// nitro.config.ts
export default defineNitroConfig({
handlers: [
{
route: "/api/admin/**",
handler: "./server/middleware/admin-auth.ts",
middleware: true,
},
],
});Basic auth via route rules (no code)
export default defineNitroConfig({
routeRules: {
"/admin/**": {
basicAuth: { username: "admin", password: "secret", realm: "Admin" },
},
"/admin/public/**": { basicAuth: false },
},
});Troubleshooting & Workarounds
Contents
- Common errors
- Routing issues
- Caching issues
- Storage issues
- Deployment issues
- Migration from v2
- Diagnostic checklist
---
Common errors
1. Cannot find module 'nitropack'
Cause: Using the v2 package name. Nitro v3 renamed nitropack to nitro.
Fix: Update package.json and all imports:
- "nitropack": "latest"
+ "nitro": "latest"- import { defineNitroConfig } from "nitropack/config"
+ import { defineNitroConfig } from "nitro/config"2. defineEventHandler is not a function / defineEventHandler is not defined
Cause: Using the v2 handler API. Nitro v3 uses defineHandler.
Fix:
- import { defineEventHandler } from "nitropack/runtime"
+ import { defineHandler } from "nitro"3. readBody is not a function
Cause: Using v2 body utilities. Nitro v3 / H3 v2 uses web standard Request methods.
Fix:
- const body = await readBody(event);
+ const body = await event.req.json();Other replacements:
readRawBody(event)→event.req.text()orevent.req.body(stream)readFormData(event)→event.req.formData()
4. getHeader is not a function / setHeader is not a function
Cause: Using v2 header utilities. H3 v2 uses web standard Headers API.
Fix:
- getHeader(event, "x-foo")
+ event.req.headers.get("x-foo")
- setHeader(event, "x-foo", "bar")
+ event.res.headers.set("x-foo", "bar")5. createError is not a function
Cause: Using v2 error utility. Nitro v3 uses HTTPError.
Fix:
- import { createError } from "nitro/h3"
- throw createError({ statusCode: 404, statusMessage: "Not found" })
+ import { HTTPError } from "nitro"
+ throw new HTTPError({ status: 404, message: "Not found" })6. useAppConfig is not a function
Cause: App config was removed in Nitro v3.
Fix: Use a regular .ts file in your server directory and import it directly, or use runtimeConfig.
7. useNitroApp().hooks is undefined
Cause: useNitroApp().hooks may be undefined outside of plugins in v3.
Fix: Use useNitroHooks() instead:
- useNitroApp().hooks.hook("request", handler)
+ import { useNitroHooks } from "nitro/app"
+ useNitroHooks().hook("request", handler)8. Type 'X' is not exported from 'nitro'
Cause: Nitro v3 exports types only from nitro/types.
Fix:
- import type { NitroRuntimeConfig } from "nitro"
+ import type { NitroRuntimeConfig } from "nitro/types"---
Routing issues
9. Route returns 404
Possible causes and fixes:
1. File not in scanned directory: Routes must be in routes/, api/, or the directory set by serverDir. Verify file location. 2. `serverDir` not enabled: If using routes/ under a server/ directory, set serverDir: "./server" or serverDir: "./". 3. Programmatic route not registered: Check routes or handlers config for typos in pattern or handler path. 4. Route file ignored: Check ignore config for patterns that might exclude the file.
10. Middleware runs on unwanted routes
Cause: Global middleware in middleware/ runs on all routes.
Fix: Either:
- Add path checks inside the middleware:
if (!event.path.startsWith("/api")) return; - Use route-scoped middleware via
handlersconfig withmiddleware: trueand a specificroutepattern.
11. Middleware response terminates request unexpectedly
Cause: Middleware returns a value, which closes the request pipeline.
Fix: Do not return from middleware unless intentionally terminating. Set context instead:
- return { user: "admin" };
+ event.context.user = "admin";12. /api directory not working on Vercel
Cause: Nitro's /api directory conflicts with Vercel's built-in /api directory.
Fix: Use routes/api/ instead of a top-level api/ directory.
---
Caching issues
13. POST/PUT/DELETE requests not cached
Cause: defineCachedHandler only caches GET and HEAD requests. Other methods bypass automatically.
Fix: This is by design. If you need to cache results of non-GET operations, use defineCachedFunction on the underlying logic.
14. Cached response ignores request headers
Cause: Request headers are dropped by default in cached responses.
Fix: Use the varies option to include specific headers in the cache key:
defineCachedHandler(handler, {
varies: ["host", "x-forwarded-host", "authorization"],
});15. Cache not invalidating
Cause: Cache entries persist until maxAge expires or manual invalidation.
Fix: Manual invalidation:
import { useStorage } from "nitro/storage";
await useStorage("cache").removeItem("nitro/handlers:routeName:keyHash.json");Or use shouldInvalidateCache option for conditional invalidation.
16. Stale cache served indefinitely
Cause: With swr: true (default), stale values are served while revalidating in background.
Fix: Set swr: false to wait for fresh values, or set staleMaxAge to limit stale duration:
defineCachedHandler(handler, {
maxAge: 3600,
swr: true,
staleMaxAge: 600, // Serve stale for max 10 minutes
});---
Storage issues
17. Storage data lost on restart
Cause: Default storage uses in-memory driver, which does not persist.
Fix: Mount a persistent driver:
storage: {
data: { driver: "fs", base: "./.data" },
}18. Storage driver not available in development
Cause: Production driver (e.g., managed Redis) not accessible locally.
Fix: Use devStorage to override with a local driver:
devStorage: {
redis: { driver: "fs", base: "./.data/redis" },
}---
Deployment issues
19. Vercel function timeout (504)
Cause: Function exceeds maxDuration limit.
Fix: Increase maxDuration in Vercel config:
export default defineNitroConfig({
vercel: {
functions: { maxDuration: 300 },
},
});20. Preset not auto-detected
Cause: CI environment variables not available, or Turborepo strict mode interfering.
Fix: Set preset explicitly:
export default defineNitroConfig({ preset: "vercel" });Or via env: NITRO_PRESET=vercel.
21. Build output missing files
Cause: Files not traced by the bundler (dynamic imports, non-JS assets).
Fix: Check .output/server/ for expected files. For untraceable files, verify they are either:
- Included in
serverAssetsconfig - Referenced by static imports
- Copied via a custom Nitro module hook
22. Vercel proxy rules invoking serverless function
Cause: Proxy rule uses advanced ProxyOptions (headers, forwardHeaders, cookieDomainRewrite, etc.).
Fix: Remove advanced options to use CDN-level rewrites. Only simple proxy: "https://..." rules are offloaded to CDN.
23. Scheduled tasks not running on Vercel
Cause: CRON_SECRET not set, or cron configuration not generated.
Fix:
1. Ensure experimental.tasks: true is set. 2. Set CRON_SECRET env var in Vercel project settings. 3. Verify cron config in .vercel/output/config.json after build.
24. Cloudflare bindings not accessible
Cause: Nitro v3 changed the access path for Cloudflare bindings.
Fix:
- const binding = event.context.cloudflare.env.MY_BINDING
+ const { env } = event.req.runtime.cloudflare
+ const binding = env.MY_BINDING---
Migration from v2
Quick reference
| v2 | v3 |
|---|---|
"nitropack" | "nitro" |
import { ... } from "nitropack/runtime/*" | import { ... } from "nitro/*" |
defineEventHandler / eventHandler | defineHandler from "nitro" |
readBody(event) | event.req.json() |
getHeader(event, name) | event.req.headers.get(name) |
setHeader(event, name, val) | event.res.headers.set(name, val) |
createError({ statusCode, statusMessage }) | new HTTPError({ status, message }) |
event.node.req / event.node.res | event.req (web Request) |
event.web | event.req |
sendRedirect(event, url) | return redirect(event, url) |
send(event, value) | return value |
useAppConfig() | removed — use runtimeConfig or direct imports |
Types from "nitropack" | Types from "nitro/types" |
useNitroApp().hooks | useNitroHooks() from "nitro/app" |
defineNodeListener | defineNodeHandler from "nitro/h3" |
fromNodeMiddleware | fromNodeHandler from "nitro/h3" |
toNodeListener | toNodeHandler from "nitro/h3" |
Preset renames
| v2 Preset | v3 Preset |
|---|---|
node | node_middleware |
cloudflare / cloudflare_worker | cloudflare_module |
vercel-edge | vercel (fluid compute) |
azure / azure_functions | azure_swa |
firebase | firebase_app_hosting |
deno | deno_deploy |
netlify-builder | netlify or netlify_edge |
iis | iis_handler |
edgio | discontinued |
cli | removed |
service_worker | removed |
Minimum Node.js version
Nitro v3 requires Node.js 20+.
---
Diagnostic checklist
1. Check Nitro version: Confirm "nitro" (not "nitropack") in package.json. 2. Check imports: All runtime utilities use nitro/* subpath exports. 3. Check handler API: defineHandler from "nitro", not defineEventHandler. 4. Check body/header API: Web standard event.req.* methods, not H3 v1 utilities. 5. Check dev server: Run nitro dev and verify routes at http://localhost:3000. 6. Check build output: Run nitro build and inspect .output/server/. 7. Check preset: Verify with NITRO_PRESET env var or preset config option. 8. Check route scanning: Verify serverDir is set if routes are in a subdirectory. 9. Check feature flags: experimental.database, experimental.tasks enabled if used. 10. Check storage drivers: Verify storage/devStorage config matches runtime environment.
Sources
Retrieved: 2026-04-04 Skill class: integration-documentation Selected profile: references/examples/documentation-skill.md
Source inventory
| Source | Trust tier | Confidence | Contribution | Usage constraints |
|---|---|---|---|---|
node_modules/nitro/skills/nitro/docs/docs/routing.md | canonical | high | Filesystem routing, dynamic params, middleware, route rules, programmatic routes | Vendored with nitro@3.0.260311-beta |
node_modules/nitro/skills/nitro/docs/docs/configuration.md | canonical | high | Config file formats, env-specific config, directory options, runtime config | Vendored with nitro@3.0.260311-beta |
node_modules/nitro/skills/nitro/docs/config/index.md | canonical | high | Full config option reference (1,147 lines) | Vendored with nitro@3.0.260311-beta |
node_modules/nitro/skills/nitro/docs/docs/cache.md | canonical | high | defineCachedHandler, defineCachedFunction, SWR, cache keys, invalidation | Vendored with nitro@3.0.260311-beta |
node_modules/nitro/skills/nitro/docs/docs/storage.md | canonical | high | useStorage API, drivers, mount points, server assets | Vendored with nitro@3.0.260311-beta |
node_modules/nitro/skills/nitro/docs/docs/database.md | canonical | high | useDatabase, SQL template literals, connectors, devDatabase | Vendored with nitro@3.0.260311-beta |
node_modules/nitro/skills/nitro/docs/docs/plugins.md | canonical | high | definePlugin, nitroApp context, runtime hooks, error capture | Vendored with nitro@3.0.260311-beta |
node_modules/nitro/skills/nitro/docs/docs/lifecycle.md | canonical | high | Request lifecycle order, error handling, hooks reference | Vendored with nitro@3.0.260311-beta |
node_modules/nitro/skills/nitro/docs/docs/server-entry.md | canonical | high | server.ts auto-detection, framework compatibility, config | Vendored with nitro@3.0.260311-beta |
node_modules/nitro/skills/nitro/docs/docs/tasks.md | canonical | high | defineTask, scheduled tasks, task config | Vendored with nitro@3.0.260311-beta |
node_modules/nitro/skills/nitro/docs/docs/assets.md | canonical | high | Public assets, server assets, compression | Vendored with nitro@3.0.260311-beta |
node_modules/nitro/skills/nitro/docs/docs/migration.md | canonical | high | v2→v3 breaking changes, renamed APIs, preset updates | Vendored with nitro@3.0.260311-beta |
node_modules/nitro/skills/nitro/docs/deploy/providers/vercel.md | canonical | high | Vercel preset, ISR, cron jobs, proxy rules, build output | Vendored with nitro@3.0.260311-beta |
node_modules/nitro/skills/nitro/docs/deploy/index.md | canonical | medium | Deployment overview, zero-config providers, preset selection | Vendored with nitro@3.0.260311-beta |
node_modules/nitro/skills/nitro/docs/deploy/runtimes/node.md | canonical | medium | Node.js preset, env vars, cluster mode | Vendored with nitro@3.0.260311-beta |
node_modules/nitro/skills/nitro/docs/examples/hono.md | canonical | high | Hono framework integration example | Vendored with nitro@3.0.260311-beta |
node_modules/nitro/skills/nitro/docs/examples/api-routes.md | canonical | high | Filesystem routing code examples | Vendored with nitro@3.0.260311-beta |
node_modules/nitro/skills/nitro/docs/examples/middleware.md | canonical | high | Middleware definition and context usage | Vendored with nitro@3.0.260311-beta |
node_modules/nitro/skills/nitro/docs/examples/plugins.md | canonical | high | Plugin hooks and response modification | Vendored with nitro@3.0.260311-beta |
node_modules/nitro/skills/nitro/docs/examples/cached-handler.md | canonical | high | Cache bypass pattern | Vendored with nitro@3.0.260311-beta |
node_modules/nitro/skills/nitro/docs/examples/websocket.md | canonical | high | WebSocket handler with pub/sub | Vendored with nitro@3.0.260311-beta |
node_modules/nitro/skills/nitro/docs/examples/runtime-config.md | canonical | high | Runtime config with env override | Vendored with nitro@3.0.260311-beta |
node_modules/nitro/skills/nitro/docs/examples/database.md | canonical | high | Database queries and migration tasks | Vendored with nitro@3.0.260311-beta |
node_modules/nitro/skills/nitro/docs/examples/vite-nitro-plugin.md | canonical | medium | Vite plugin with virtual routes | Vendored with nitro@3.0.260311-beta |
node_modules/nitro/skills/nitro/docs/docs/renderer.md | canonical | low | HTML rendering, rendu preprocessor | Vendored; niche feature, low priority |
node_modules/nitro/skills/nitro/docs/docs/index.md | canonical | medium | Feature overview and landing page | Vendored with nitro@3.0.260311-beta |
node_modules/nitro/skills/nitro/docs/docs/quick-start.md | canonical | medium | Project setup steps | Vendored with nitro@3.0.260311-beta |
Decisions
| Decision | Status | Evidence |
|---|---|---|
| Use web standard APIs exclusively for handler examples | adopted | migration.md: H3 v2 uses web Request/Response; all v2 utils deprecated |
| Cover Vercel deployment in depth over other providers | adopted | Most common deployment target in repo usage; vercel.md has ISR/cron/proxy features |
| Include migration reference table in troubleshooting | adopted | migration.md: many v2→v3 breaking changes require lookup table |
| Mark database and tasks as experimental | adopted | database.md, tasks.md: both require experimental feature flags |
Document defineMiddleware alongside defineHandler | adopted | routing.md: middleware is a distinct concept with different return semantics |
| Omit renderer/rendu in main SKILL.md | adopted | Niche feature; available in vendored docs if needed |
| Include Hono as primary framework example | adopted | Example app uses Hono; hono.md shows the pattern |
Coverage matrix
| Dimension | Coverage | Evidence |
|---|---|---|
| API surface and behavior contracts | complete | api-surface.md covers all core APIs: defineHandler, routing, cache, storage, database, plugins, tasks, WebSocket, config, modules, deployment |
| Configuration/runtime options | complete | api-surface.md config tables, runtime config section |
| Common downstream use cases | complete | common-use-cases.md: 12 use cases covering project setup through deployment |
| Known issues/failure modes with workarounds | complete | troubleshooting-workarounds.md: 24 issues covering errors, routing, caching, storage, deployment, and migration |
| Version/migration variance | complete | troubleshooting-workarounds.md migration section: full v2→v3 API and preset mapping |
Open gaps
- Cloudflare Workers deployment details beyond binding access migration (low priority — vendored docs cover this)
- OpenAPI/Swagger experimental feature (incomplete upstream docs)
- Detailed config/index.md options (1,147 lines) not fully inlined — available via vendored docs
Stopping rationale
All five required integration-documentation dimensions are covered at complete status. The vendored Nitro docs (78 files, ~13K lines) have been read and synthesized into the three required reference files. Additional retrieval would yield diminishing returns — the remaining uncovered material (individual deployment providers, rendu renderer details) represents niche features accessible via the vendored node_modules/nitro/skills/nitro/docs/ when needed.
Related skills
FAQ
What does nitro do?
nitro skill documents Build and deploy universal JavaScript servers with Nitro v3.
When should I use nitro?
User asks about nitro, build and deploy universal javascript servers with nitro v3. use when working with nitro.c.
Is this skill safe to install?
Review the Security Audits panel on this page before installing in production.