Migration Guide
Most migrations follow the same order:
Rename the nitropack dependency to nitro and upgrade to Node.js 20+.
Add explicit imports and update type and subpath imports.
Update your config: enable server directory scanning and review removed features.
Review route rules and runtime utilities.
Update handlers for H3 v2 API changes.
Check your deployment preset and platform-specific access.
nitropack Is Renamed to nitro
The NPM package nitropack (v2) has been renamed to nitro (v3).
Migration: Update the nitropack dependency to nitro in package.json:
{
"dependencies": {
-- "nitropack": "latest"
++ "nitro": "latest"
}
}
{
"dependencies": {
-- "nitropack": "latest"
++ "nitro": "npm:nitro-nightly@latest"
}
}
See the nightly channel guide for more about nightly builds.
Migration: Search your codebase and rename all instances of nitropack to nitro:
-- import { defineNitroConfig } from "nitropack/config"
++ import { defineConfig } from "nitro"
Minimum Supported Node.js Version: 20
Nitro now requires a minimum Node.js version of 20, as Node.js 18 reached end-of-life in April 2025.
Please upgrade to the latest LTS version (>= 20).
Migration:
- Check your local Node.js version using
node --versionand update if necessary. - If you use a CI/CD system for deployment, ensure that your pipeline is running Node.js 20 or higher.
- If your hosting provider manages the Node.js runtime, make sure it's set to version 20, 22, or later.
Auto-Imports Are Disabled by Default
Nitro v2 automatically made utilities such as defineEventHandler, useStorage, useRuntimeConfig, defineCachedFunction, and defineNitroPlugin available in server code without imports. In Nitro v3, auto-imports are disabled by default — using these globals will result in build or runtime errors.
Migration: Add explicit imports everywhere. Common utilities and where they come from:
import { defineHandler, definePlugin, defineErrorHandler, HTTPError } from "nitro";
import { useStorage } from "nitro/storage";
import { useRuntimeConfig } from "nitro/runtime-config";
import { defineCachedFunction, defineCachedHandler } from "nitro/cache";
import { useDatabase } from "nitro/database";
import { defineTask, runTask } from "nitro/task";
import { useNitroApp, useNitroHooks } from "nitro/app";
import { getQuery, getCookie } from "nitro/h3";
imports option in your Nitro config, but explicit imports are recommended for better ecosystem compatibility.Type Imports
Nitro types are now only exported from nitro/types.
Migration: Import types from nitro/types instead of nitro:
-- import { NitroRuntimeConfig } from "nitropack"
++ import { NitroRuntimeConfig } from "nitro/types"
Changed Nitro Subpath Imports
Nitro v2 introduced multiple subpath exports, some of which have been removed or updated:
nitropack/rollup,nitropack/core(usenitro/builder)nitropack/runtime/*(usenitro/*)nitropack/kit(removed)nitropack/presets(removed)
An experimental nitropack/kit was introduced but has now been removed. A standalone Nitro Kit package may be introduced in the future with clearer objectives.
Migration:
- Use
NitroModulefromnitro/typesinstead ofdefineNitroModulefrom the kit. - Prefer built-in Nitro presets (external presets are only for evaluation purposes).
Server Directory Scanning Is Opt-In
In Nitro v2, srcDir defaulted to the project root, and directories such as routes/, api/, middleware/, plugins/, and tasks/ were always scanned.
In Nitro v3:
- The
srcDiroption is deprecated in favor ofserverDir(usingsrcDirstill works but logs a warning). serverDirdefaults tofalse, meaning no directory scanning happens until you explicitly enable it — scanned routes, middleware, plugins, and tasks won't be registered otherwise.
Migration: Set serverDir in your Nitro config:
import { defineConfig } from "nitro";
export default defineConfig({
serverDir: "./server", // or "." to keep the v2 root layout
});
Setting serverDir: true is a shortcut for "server". Paths are resolved relative to the project root.
App Config Support Removed
Nitro v2 supported a bundled app config that allowed defining configurations in app.config.ts and accessing them at runtime via useAppConfig().
This feature has been removed.
Migration:
Use a regular .ts file in your server directory and import it directly.
Route Rules
Route rule matching and handling moved to h3-rules. The rule shapes for redirect, proxy, cors, headers, cache, and swr are now defined by h3-rules, while isr, prerender, and static remain Nitro-specific rules.
The NitroRouteConfig and NitroRouteRules types are deprecated in favor of RouteRuleConfig and RouteRules, re-exported from nitro/types:
-- import { NitroRouteConfig, NitroRouteRules } from "nitropack/types"
++ import { RouteRuleConfig, RouteRules } from "nitro/types"
Runtime Utilities
Runtime utilities have been moved to individual nitro/* subpath exports. Refer to docs for usage.
-- import { useStorage } from "nitropack/runtime/storage"
++ import { useStorage } from "nitro/storage"
| Utilities | Nitro v3 Import |
|---|---|
useStorage | nitro/storage |
defineCachedFunction, defineCachedHandler | nitro/cache |
useDatabase | nitro/database |
useRuntimeConfig | nitro/runtime-config |
defineTask, runTask | nitro/task |
useNitroApp, useNitroHooks, getRouteRules | nitro/app |
Internal Server Fetch
To make internal requests to your own server (the v2 useNitroApp().localFetch pattern), use the serverFetch utility exported from nitro. There is also a fetch export that routes absolute paths (starting with /) to the server and everything else to global fetch.
import { serverFetch } from "nitro";
const res = await serverFetch("/api/hello"); // returns a web Response
Plugins
The defineNitroPlugin utility has been renamed to definePlugin and is now imported from nitro.
-- export default defineNitroPlugin((nitroApp) => {
++ import { definePlugin } from "nitro"
++
++ export default definePlugin((nitroApp) => {
Optional Hooks
If you were using useNitroApp().hooks outside of Nitro plugins before, it might be undefined. Use new useNitroHooks() to guarantee having an instance.
import { useNitroHooks } from "nitro/app";
useNitroHooks().hook("request", (event) => {
/* ... */
});
Error Handler
The defineNitroErrorHandler utility has been renamed to defineErrorHandler and is now imported from nitro.
-- export default defineNitroErrorHandler((error, event) => {
++ import { defineErrorHandler } from "nitro"
++
++ export default defineErrorHandler((error, event) => {
H3 v2
Nitro v3 upgrades to H3 v2, which includes API changes. All H3 utilities are imported from nitro/h3.
Web Standards
H3 v2 is rewritten based on web standard primitives (URL, Headers, Request, and Response).
Access to event.node.{req,res} is only available in Node.js runtime. event.web is renamed to event.req (instance of web Request).
Response Handling
You should always explicitly return the response body or throw an error:
-- import { send, sendRedirect, sendStream } from "nitro/h3"
-- send(event, value)
-- sendStream(event, stream)
-- sendRedirect(event, location, code)
++ import { redirect } from "nitro/h3"
++ return value
++ return stream
++ return redirect(event, location, code)
Other changes:
sendError(event, error)→throw error(orthrow new HTTPError(...))sendNoContent(event)→return noContent(event)sendProxy(event, target)→return proxy(event, target)
Request Body
Most body utilities can be replaced with native event.req methods:
-- import { readBody, readRawBody, readFormData } from "nitro/h3"
++ // Use native Request methods
++ const json = await event.req.json()
++ const text = await event.req.text()
++ const formData = await event.req.formData()
++ const stream = event.req.body
Headers
H3 now uses standard web Headers. Header values are always plain string (no null, undefined, or string[]).
-- import { getHeader, setHeader, getResponseStatus } from "nitro/h3"
-- getHeader(event, "x-foo")
-- setHeader(event, "x-foo", "bar")
++ event.req.headers.get("x-foo")
++ event.res.headers.set("x-foo", "bar")
++ event.res.status // instead of getResponseStatus(event)
Handler Utils
-- import { eventHandler, defineEventHandler } from "nitro/h3"
++ import { defineHandler } from "nitro"
lazyEventHandler→defineLazyEventHandleruseBase→withBase
Error Utils
-- import { createError, isError } from "nitro/h3"
++ import { HTTPError } from "nitro"
++ throw new HTTPError({ status: 404, message: "Not found" })
++ HTTPError.isError(error)
Node.js Utils
-- import { defineNodeListener, fromNodeMiddleware, toNodeListener } from "nitro/h3"
++ import { defineNodeHandler, fromNodeHandler, toNodeHandler } from "nitro/h3"
Preset Updates
Nitro presets have been updated for the latest compatibility.
Some (legacy) presets have been removed or renamed.
| Old Preset | New Preset |
|---|---|
node (Node.js middleware) | node_middleware (export changed to middleware) |
cloudflare, cloudflare_worker, cloudflare_module_legacy | cloudflare_module |
deno-server-legacy | deno_server with Deno v2 |
netlify-builder | netlify or netlify_edge |
vercel-edge | vercel with Fluid compute enabled |
azure, azure_functions | azure_swa |
firebase | firebase_app_hosting |
iis | iis_handler |
deno (Deno Deploy) | deno_deploy |
edgio | Discontinued |
cli | Removed due to lack of use |
service_worker | Removed due to instability |
node and deno preset names still exist in v3, but they now point to different targets:- In v2, the
nodepreset produced a reusable Node.js middleware. In v3,nodeis an alias ofnode_server, which builds a full standalone Node.js server. If you keeppreset: "node", you now get a standalone server — usenode_middlewareto keep the v2 middleware output. - In v2, the
denopreset targeted Deno Deploy. In v3,denois an alias ofdeno_server, which builds a self-hosted Deno server — usedeno_deployto keep deploying to Deno Deploy.
Cloudflare Bindings Access
In Nitro v2, Cloudflare environment variables and bindings were accessible via event.context.cloudflare.env.
In Nitro v3, the Cloudflare runtime context is attached to the request's runtime object instead.
Migration:
-- const { cloudflare } = event.context
-- const binding = cloudflare.env.MY_BINDING
++ const { env } = event.req.runtime.cloudflare
++ const binding = env.MY_BINDING