---
title: "Server Compatibility"
description: "Ship one module that serves Nuxt 4 and Nuxt 5, whichever server runtime is underneath."
canonical_url: "https://nuxt.com/docs/4.x/guide/modules/server-compatibility"
---
# Server Compatibility

> Ship one module that serves Nuxt 4 and Nuxt 5, whichever server runtime is underneath.

Nuxt supports multiple server runtimes. Nuxt 3 and 4 target Nitro v2 (`nitropack`), which uses `h3` v1; Nuxt 5 defaults to Nitro v3 (`nitro`), which uses `h3` v2. It is also possible to use a custom server runtime based on Web APIs, which is compatible everywhere (`nuxt/server`).

This guide covers how to write a module whose server code runs on all of them.

<note>

If your module has no server code, nothing here applies.

</note>

## Recommended Migration

[`nuxt/server`](https://nuxt.com/docs/4.x/guide/going-further/server-imports) is the import surface for writing agnostic server code based on Web APIs, and it ships in Nuxt 4.6+. Code written with it runs on `nitropack` v2, on Nitro v3, and under other server builders, such as `@nuxt/vite-server`.

### Event Handlers

If you are registering event handlers, keep your current handler (for compatibility with Nuxt <4.6) and add a new portable one using `nuxt/server` beside it, and register both. Nuxt will register the implementation the application can run: the portable file under Nitro v3 and under other builders, the v2 file on a host still running `nitropack` v2.

```ts [module.ts]
import { addServerHandler, createResolver, defineNuxtModule } from '@nuxt/kit'

export default defineNuxtModule({
  meta: { name: 'my-module' },
  setup () {
    const { resolve } = createResolver(import.meta.url)

    addServerHandler({
      route: '/api/my-module/status',
      handler: {
        nuxt: resolve('./runtime/server/status'),
        nitro2: resolve('./runtime/server/status.legacy'),
      },
    })
  },
})
```

In most cases, your handler files will differ only in their imports:

<code-group>

```ts [runtime/server/status.ts]
import { defineEventHandler, useRuntimeConfig } from 'nuxt/server'

export default defineEventHandler(() => ({
  version: useRuntimeConfig().public.myModule.version,
}))
```

```ts [runtime/server/status.legacy.ts]
// @ts-expect-error `#imports` is typed for the app, not the server build
import { useRuntimeConfig } from '#imports'
import { defineEventHandler } from 'h3'

export default defineEventHandler(event => ({
  version: useRuntimeConfig(event).public.myModule.version,
}))
```

</code-group>

The keys indicate which server API each file relies upon:

<table>
<thead>
  <tr>
    <th>
      Key
    </th>
    
    <th>
      The file imports
    </th>
    
    <th>
      Where it runs
    </th>
  </tr>
</thead>

<tbody>
  <tr>
    <td>
      <code>
        nuxt
      </code>
    </td>
    
    <td>
      <code>
        nuxt/server
      </code>
      
       only
    </td>
    
    <td>
      Any server builder, from Nuxt 4.6
    </td>
  </tr>
  
  <tr>
    <td>
      <code>
        nitro2
      </code>
    </td>
    
    <td>
      <code>
        h3
      </code>
      
      , <code>
        nitropack/runtime
      </code>
      
      , <code>
        #imports
      </code>
    </td>
    
    <td>
      <code>
        nitropack
      </code>
      
       v2
    </td>
  </tr>
  
  <tr>
    <td>
      <code>
        nitro3
      </code>
    </td>
    
    <td>
      <code>
        nitro
      </code>
      
      , <code>
        nitro/h3
      </code>
    </td>
    
    <td>
      The Nitro server builder, v3
    </td>
  </tr>
</tbody>
</table>

Nuxt will pick the most appropriate handler to use based on the server builder a user is using. If you register a handler which the user's server builder cannot run, it will be skipped with a warning.

<tip>

Use `nitro3` only for code that needs an API only Nitro v3 offers. Using `nuxt/server` will keep your code more portable in future.

</tip>

Using this pattern means you will support both old (<4.6) and new versions of Nuxt (including Nuxt 5).

<important>

Because this relies on `@nuxt/kit` utilities, make sure it is in your module's `dependencies`, which is what the [module starter](https://nuxt.com/docs/4.x/guide/modules/getting-started) sets up. If you moved `@nuxt/kit` to `peerDependencies`, the application's version applies.

</important>

### Server Plugins

Server plugins take variants too, although they are specific to Nitro so there is no generic `nuxt` variant to register.

<tip>

Because `defineNitroPlugin` is an identity function, you may be able to replace it with a type annotation to write code that's portable for both `nitro2` and `nitro3`.

</tip>

```ts [module.ts]
import { addNitroPlugin, createResolver, defineNuxtModule } from '@nuxt/kit'

export default defineNuxtModule({
  meta: { name: 'my-module' },
  setup () {
    const { resolve } = createResolver(import.meta.url)

    addNitroPlugin({
      nitro2: resolve('./runtime/server/plugin.legacy'),
      nitro3: resolve('./runtime/server/plugin'),
    })
  },
})
```

<note>

`addServerImports`, `addServerImportsDir` and `addServerTemplate` do not differ between Nitro 2 and 3. If you have code that differs, you can use `getNitroVersion` to conditionally register imports/templates differently.

</note>

### Declaring What You Cannot Infer

Nuxt reads a registered file's imports to decide which API it uses, so you don't need to declare your compatibility. But if this isn't sufficient, you can set `meta.compatibility.server`:

```ts [module.ts]
export default defineNuxtModule({
  meta: {
    name: 'my-module',
    compatibility: { server: 'nuxt' },
  },
})
```

If we can't tell, code will be treated as `nitro2`.

## Nitro-specific Code

`nuxt/server` covers request and response work: handlers, errors, query and body reading, headers, cookies, redirects, route rules and runtime config. But Nitro covers a lot more, so if you need any of these areas, you might need to stay on Nitro's own imports inside a `nitro2` or `nitro3` file.

Use the specifiers of the version the file is for. A file that imports `nitro/*` is read as Nitro 3 code, so a `nitro2` file must import `h3` and `nitropack/*` instead:

<table>
<thead>
  <tr>
    <th>
      Area
    </th>
    
    <th>
      In a <code>
        nitro2
      </code>
      
       file
    </th>
    
    <th>
      In a <code>
        nitro3
      </code>
      
       file
    </th>
  </tr>
</thead>

<tbody>
  <tr>
    <td>
      Storage: <code>
        useStorage()
      </code>
      
       and its driver configuration
    </td>
    
    <td>
      <code>
        nitropack/runtime
      </code>
    </td>
    
    <td>
      <code>
        nitro/storage
      </code>
    </td>
  </tr>
  
  <tr>
    <td>
      Sessions: <code>
        useSession
      </code>
      
      , <code>
        getSession
      </code>
      
      , <code>
        updateSession
      </code>
      
      , <code>
        clearSession
      </code>
    </td>
    
    <td>
      <code>
        h3
      </code>
    </td>
    
    <td>
      <code>
        nitro/h3
      </code>
    </td>
  </tr>
  
  <tr>
    <td>
      Caching: <code>
        defineCachedEventHandler()
      </code>
      
      , <code>
        defineCachedFunction()
      </code>
    </td>
    
    <td>
      <code>
        nitropack/runtime
      </code>
    </td>
    
    <td>
      <code>
        nitro/cache
      </code>
    </td>
  </tr>
</tbody>
</table>

Nitro's runtime hooks (`useNitroApp().hooks`, including `render:html`) and tasks are also not supported by `nuxt/server`.

<read-more to="https://nuxt.com/docs/4.x/guide/going-further/server-imports#reaching-past-the-surface">

See which h3 helpers work with the portable event, and which need Nitro-specific utilities.

</read-more>

## Notable Changes in Nitro v3

If a `nitro2` file is all you ship, it will keep running on Nuxt 5 through the Nitro v2 compatibility layer, which is documented in the [Nuxt 5 module server compatibility guide](https://nuxt.com/docs/5.x/guide/modules/server-compatibility) along with the behaviours that changed with Nitro v3.


## Sitemap

See the full [sitemap](https://nuxt.com/sitemap.md) for all pages.
