# Migration Guide

> [!NOTE]
> This is a living document for migrating from Nitro 2 to 3. Please check it regularly while using the beta version.

Nitro v3 introduces intentional backward-incompatible changes. This guide helps you migrate from Nitro v2.

## `nitropack` is renamed to `nitro`

The NPM package [nitropack](https://www.npmjs.com/package/nitropack) (v2) has been renamed to [nitro](https://www.npmjs.com/package/nitro) (v3).

**Migration:** Update the `nitropack` dependency to `nitro` in `package.json`:

```diff [release channel]
{
  "dependencies": {
--    "nitropack": "latest"
++    "nitro": "latest"
  }
}
```

```diff [nightly channel]
{
  "dependencies": {
--    "nitropack": "latest"
++    "nitro": "npm:nitro-nightly"
  }
}
```

**Migration:** Search your codebase and rename all instances of nitropack to nitro:

```diff
-- import { defineNitroConfig } from "nitropack/config"
++ import { defineConfig } from "nitro"
```

## nitro/runtime

Runtime utils had been moved to individual `nitro/*` subpath exports. Refer to docs for usage.

```diff
-- import { useStorage } from "nitropack/runtime/storage"
++ import { useStorage } from "nitro/storage"
```

## Minimum Supported Node.js Version: 20

Nitro now requires a minimum Node.js version of 20, as Node.js 18 reaches end-of-life in [April 2025](https://nodejs.org/en/about/previous-releases).

Please upgrade to the [latest LTS](https://nodejs.org/en/download) version (>= 20).

**Migration:**

- Check your local Node.js version using `node --version` and 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.

## Type Imports
Nitro types are now only exported from `nitro/types`.

**Migration:** Import types from nitro/types instead of nitro:

```diff
-- import { NitroRuntimeConfig } from "nitropack"
++ import { NitroRuntimeConfig } from "nitro/types"
```

## 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 had been removed.

**Migration:**

Use a regular `.ts` file in your server directory and import it directly.

## 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_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` |
| `edgio` | Discontinued |
| `cli` | Removed due to lack of use |
| `service_worker` | Removed due to instability |

## 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:**

```diff
-- const { cloudflare } = event.context
-- const binding = cloudflare.env.MY_BINDING
++ const { env } = event.req.runtime.cloudflare
++ const binding = env.MY_BINDING
```

## Changed nitro subpath imports

Nitro v2 introduced multiple subpath exports, some of which have been removed or updated:

- `nitro/rollup`, `nitropack/core` (use `nitro/builder`)
- `nitropack/runtime/*` (use `nitro/*`)
- `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 `NitroModule` from `nitro/types` instead of `defineNitroModule` from the kit.
- Prefer built-in Nitro presets (external presets are only for evaluation purposes).

## H3 v2
Nitro v3 upgrades to [H3 v2](https://h3.dev), which includes API changes. All H3 utilities are imported from `nitro/h3`.

### Web Standards

H3 v2 is rewritten based on web standard primitives ([`URL`](https://developer.mozilla.org/en-US/docs/Web/API/URL), [`Headers`](https://developer.mozilla.org/en-US/docs/Web/API/Headers), [`Request`](https://developer.mozilla.org/en-US/docs/Web/API/Request), and [`Response`](https://developer.mozilla.org/en-US/docs/Web/API/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`](https://developer.mozilla.org/en-US/docs/Web/API/Request)).

### Response Handling

You should always explicitly **return** the response body or **throw** an error:

```diff
-- 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 createError(error)`
- `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:

```diff
-- 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`](https://developer.mozilla.org/en-US/docs/Web/API/Headers). Header values are always plain `string` (no `null`, `undefined`, or `string[]`).

```diff
-- 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

```diff
-- import { eventHandler, defineEventHandler } from "nitro/h3"
++ import { defineHandler } from "nitro"
```

- `lazyEventHandler` → `defineLazyEventHandler`
- `useBase` → `withBase`

### Error Utils

```diff
-- import { createError, isError } from "nitro/h3"
++ import { HTTPError } from "nitro"
++ throw new HTTPError({ status: 404, message: "Not found" })
++ HTTPError.isError(error)
```

### Node.js Utils

```diff
-- import { defineNodeListener, fromNodeMiddleware, toNodeListener } from "nitro/h3"
++ import { defineNodeHandler, fromNodeHandler, toNodeHandler } from "nitro/h3"
```

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