服务器兼容性
Nuxt 支持多种服务器运行时。Nuxt 3 和 4 以 Nitro v2(nitropack)为目标,它使用 h3 v1;Nuxt 5 默认使用 Nitro v3(nitro),它使用 h3 v2。用户也可以使用基于 Web API、可在各种环境中兼容运行的自定义服务器运行时(nuxt/server)。
为了帮助从 Nuxt 4 升级,针对 Nitro v2 编写的模块代码可以通过临时兼容层继续运行。本指南将介绍该兼容层的作用,以及如何迁移到对 Nuxt 4 和 5 的完整支持
兼容层
当模块的服务端代码文件从 h3、nitropack、nitropack/runtime、#internal/nitro 或 #imports 导入内容时,就会进入兼容层。(导入 nuxt/server 或 nitro/* 的文件不受影响。)
在此兼容层中,h3 会解析为基于 h3 v2 构建的 h3 v1 辅助工具接口,nitropack 标识符会解析为对应的 Nitro v3 实现,useRuntimeConfig(event) 会保持其按请求运行的行为,并且我们会注入 v1 代码可能会读取的 event.node、event.context.nitro 和 event.context._nitro.routeRules 结构。
为了帮助迁移,Nuxt 会记录应用了兼容层的模块:
WARN [NUXT_B9003] Nitro v2 compatibility was applied to server code from 2 modules, because of what it imports:
- @nuxtjs/robots (imports `h3`, `nitropack/runtime`, `#imports`)
- nuxt-site-config (imports `h3`, `nitropack/runtime`)nitropack v2 恰好提升到服务端打包文件中的包,例如 lru-cache 等。请务必在 package.json 中声明模块的依赖。推荐的迁移方式
nuxt/server 是基于 Web API 编写通用服务端代码的导入接口,且 Nuxt 4.6+ 已包含该接口。使用它编写的代码可在 nitropack v2、Nitro v3 以及其他服务器构建器(例如 @nuxt/vite-server)下运行。
事件处理程序
如果你要注册事件处理程序,请保留现有处理程序(以兼容 Nuxt <4.6),并在旁边使用 nuxt/server 添加一个新的可移植处理程序,然后将两者都注册。Nuxt 会注册应用能够运行的实现:在 Nitro v3 和其他构建器下使用可移植文件,在仍运行 nitropack v2 的主机上使用 v2 文件。
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'),
},
})
},
})两个文件的导入内容不同;如果处理程序使用了 h3 v1 辅助函数,或使用了 nuxt/server 处理方式不同的行为,也需要相应调整:
import { defineEventHandler, useRuntimeConfig } from 'nuxt/server'
export default defineEventHandler(() => ({
version: useRuntimeConfig().public.myModule.version,
}))// @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,
}))这些键表示每个文件所依赖的服务端 API:
| 键 | 文件导入内容 | 运行环境 |
|---|---|---|
nuxt | 仅 nuxt/server | Nuxt 4.6 起的任何服务器构建器 |
nitro2 | h3、nitropack/runtime、#imports | 直接运行于 nitropack v2,或通过兼容层运行于 Nitro v3 |
nitro3 | nitro、nitro/h3,单独使用或与 nuxt/server 一起使用 | Nitro 服务器构建器,v3 |
Nuxt 会根据用户使用的服务器构建器选择最合适的处理程序。如果你注册的处理程序无法由用户的服务器构建器运行,该处理程序会被跳过并发出警告。
nitro3。使用 nuxt/server 能让你的代码在未来保持更好的可移植性。使用这种模式意味着你将同时支持旧版(<4.6)和新版 Nuxt(包括 Nuxt 5)。
handler 的对象形式需要在模块的 dependencies 中使用 @nuxt/kit@^4.6,模块起始模板会为你完成此设置。如果 @nuxt/kit 只是 devDependency 并在构建时保持为外部依赖,或是 peerDependency,则会使用应用程序的版本;低于 4.6 的版本无法处理对象形式的 handler。要对 nuxt 文件进行类型检查,请将 nuxt@>=4.6 安装为 devDependency。要对 nitro3 文件进行类型检查,还需要安装 nitro。
如果模块自己的别名在两个 Nitro 主版本中都能解析到正确的代码,可以为两个键注册同一个文件({ nitro2: file, nitro3: file }),或设置 meta.compatibility.server。
在 nitropack v2 主机上,Nuxt 始终优先使用 nitro2 变体,因此只有在没有注册其他变体时才会运行 nuxt 变体。请在 Nuxt 5 上测试 nuxt 变体。
在 Nuxt 5 的 playground 和测试固件中,h3 自动导入的 defineEventHandler 不可用,请从 nuxt/server 导入它。
开发服务器处理程序
addDevServerHandler 使用相同的键,但每个变体都是模块代码中的函数,它运行在 Nuxt 进程中,而不是服务器构建中。nuxt/server 仅适用于服务器代码,因此不要从模块代码中导入它,即使是延迟导入也不行。
相反,请针对 Web Request 和 Response 编写处理程序,在 setup 中从 nuxt.options 读取配置,并将其适配到各个服务器 API:
import { addDevServerHandler, defineNuxtModule } from '@nuxt/kit'
import { defineEventHandler, toWebRequest } from 'h3'
export default defineNuxtModule({
meta: { name: 'my-module' },
setup (_options, nuxt) {
const greeting = nuxt.options.runtimeConfig.public.myModule.greeting
function greet (request: Request) {
const name = new URL(request.url).searchParams.get('name')
if (!name) {
return new Response('Missing name', { status: 400 })
}
return Response.json({ message: `${greeting}, ${name}!` })
}
addDevServerHandler({
route: '/_my-module/greet',
handler: {
nuxt: event => greet(event.req),
nitro2: defineEventHandler(event => greet(toWebRequest(event))),
},
})
},
})nuxt 变体接收 RequestEvent 并读取其中的 event.req,nitro2 变体则使用 toWebRequest() 转换 h3 v1 event。由于模块导入 h3 v1,请在模块的 dependencies 中添加 h3@^1。
开发服务器处理程序的路由在各个服务器 API 上并不匹配相同的路径。nuxt 和 nitro3 变体会精确匹配路由。nitro2 处理程序会像 Nitro v2 一样将其作为路径前缀匹配,因此 /_my-module 也会匹配 /_my-module/inter.woff2。Nuxt 将单个函数视为 nitro2 处理程序。
如果希望每个变体都处理某个路由下的路径,请在路由末尾加上 /**:
addDevServerHandler({
route: '/_my-module/assets/**',
handler: {
nuxt: event => serveAsset(event.req),
nitro2: defineEventHandler(event => serveAsset(toWebRequest(event))),
},
})这里的 serveAsset 接收一个 Request 并返回一个 Response,就像前面示例中的 greet。该路由匹配 /_my-module/assets 及其下的所有路径,因此无需再次注册基础路径。在 nitropack v2 主机上,模块的 dependencies 中需要 @nuxt/kit@^4.6.1。
nitro2 处理程序会读取相对于路由基础路径的 event.path,因为 Nitro v2 会移除基础路径。对于 /_my-module/assets/inter.woff2 请求,event.path 是 /inter.woff2。完整 URL 仍可从 toWebRequest() 获取,而 nuxt 变体从 event.req.url 读取它。
服务端插件
服务端插件也支持变体,不过它们专用于 Nitro,因此没有可注册的通用 nuxt 变体。
nitro 的 definePlugin() 和来自 nitropack/runtime 的 defineNitroPlugin() 都是恒等函数,你或许可以用类型注解替换它们,从而编写同时适用于 nitro2 和 nitro3 的代码。import { addNitroPlugin, createResolver, defineNuxtModule } from '@nuxt/kit'
export default defineNuxtModule({
meta: { name: 'my-module' },
setup () {
const { resolve } = createResolver(import.meta.url)
addNitroPlugin({
nitro3: resolve('./runtime/server/plugin'),
nitro2: resolve('./runtime/server/plugin.legacy'),
})
},
})其他变体
对于其他指向服务器代码的内容,例如别名、类型模板或读取服务器 event 的应用插件,可以使用 resolveServerVariant 解析适用于当前 Nuxt 版本的变体;如果没有可运行的变体,则返回 undefined。
addServerImports 的 from 属性也接受相同的变体。
import { addServerImports, createResolver, defineNuxtModule, resolveServerVariant } from '@nuxt/kit'
export default defineNuxtModule({
meta: { name: 'my-module' },
setup (_options, nuxt) {
const { resolve } = createResolver(import.meta.url)
const someServerFile = resolveServerVariant({
nuxt: resolve('./runtime/server'),
nitro2: resolve('./runtime/server.legacy'),
})
if (someServerFile) {
nuxt.options.alias['#my-module/server'] = someServerFile
}
addServerImports({
name: 'verifyToken',
from: { nuxt: resolve('./runtime/verify'), nitro2: resolve('./runtime/verify.legacy') },
})
},
})在 nitropack v2 上运行时,nitro2 始终优先于 nuxt;在 Nitro v3 上运行时,nitro3 始终优先于 nuxt。因此,如果需要测试 nuxt 变体,请在测试固件中将 server.builder 设置为非 Nitro 的构建器,例如 @nuxt/vite-server。
声明无法推断的信息
Nuxt 会读取每个已注册文件及其导入文件的导入内容,以判断它使用的 API,因此你无需声明兼容性。类型导入(import type)以及运行时目录中没有被已注册文件导入的文件都会被忽略。但如果这还不够,你可以设置 meta.compatibility.server:
export default defineNuxtModule({
meta: {
name: 'my-module',
compatibility: { server: 'nuxt' },
},
})如果我们无法判断,代码将被视为 nitro2。
Nuxt 只读取模块自身文件的导入内容,不会读取这些文件所调用的包。如果一个 nuxt 文件调用了内部导入 Nitro 的库,该文件仍会被接受,但在非 Nitro 构建器下会失败。
类型化运行时配置和 Event
要为模块添加的运行时配置键提供类型,请从 nuxt/schema 扩展 RuntimeConfig(或 PublicRuntimeConfig)。这些键随后会在 nuxt、nitro2 和 nitro3 文件中获得相同的类型支持。
declare module 'nuxt/schema' {
interface RuntimeConfig {
myModule: { apiKey: string }
}
}特定于构建器的类型(例如 useRequestEvent() 使用的 H3Event,或 nitro:config 钩子中的完整 Nitro 配置)来自生成的 .nuxt/nuxt.d.ts。没有该文件时,它们会解析为与构建器无关的类型(RequestEvent 和精简的 Nitro 配置)。要支持所有服务器构建器,请让类型工具接收基于 RequestEvent 的 event。
插件、存储和缓存
基于服务器插件、存储或缓存构建的模块没有 nuxt 变体,因为 nuxt/server 不涵盖这些领域。现在将其文件注册为 nitro2,迁移后再添加 nitro3 变体。
Nitro 专属代码
nuxt/server 涵盖请求和响应相关工作:处理程序、错误、路由器参数、查询和请求体的读取与验证、标头、CORS、Cookie、会话、重定向、路由规则、运行时配置、应用配置、使用 serverFetch() 的进程内 fetch,以及使用 useServerHooks() 的运行时钩子。但 Nitro 涵盖的内容要多得多,因此,如果你需要以下任何功能,可能需要在 nitro2 或 nitro3 文件中继续使用 Nitro 自身的导入项。
请使用与文件对应版本的标识符。导入 nitro/* 的文件会被视为 Nitro 3 代码,因此 nitro2 文件必须改为导入 h3 和 nitropack/*:
| 范围 | 在 nitro3 文件中 | 在 nitro2 文件中 |
|---|---|---|
存储:useStorage() 及其驱动程序配置 | nitro/storage | nitropack/runtime |
缓存:defineCachedHandler()、defineCachedFunction() | nitro/cache | nitropack/runtime |
| 服务端插件 | 来自 nitro 的 definePlugin() | 来自 nitropack/runtime 的 defineNitroPlugin() |
Nitro 自身的运行时钩子,例如 request、response 和 error | 来自 nitro/app 的 useNitroHooks() | 来自 nitropack/runtime 的 useNitroApp().hooks |
| 应用内相对 fetch | 来自 nitro 的 serverFetch() | 来自 nitropack/runtime 的 useNitroApp().localFetch |
延迟处理程序:lazyEventHandler() | nitro/h3 | h3 |
任务:defineTask()、runTask() | nitro/task | nitropack/runtime |
Nitro 中值得注意的变更
这些行为在 Nitro v3 中发生了变化,可能需要你进一步更新代码:
- 路由中间件不再对其子路径运行。
nitropackv2 会将中间件挂载到某个路由上,使其在该路由及其所有下级路径(例如/path/subpath)上运行,并从event.path中移除该路由。Nitro v3 则会像匹配其他处理程序一样进行精确匹配。更新时,如果你希望涵盖这两种情况,请将route扩展为/path/**,并从event.path中读取完整路径。 - 未注册
route的处理程序会被视为全局中间件。 Nitro v3 要求指定路由。如果需要中间件,请使用middleware: true并指定明确的路由,即使你希望它在所有路径/**上运行也是如此。 beforeResponse和afterResponse不是 Nitro v3 钩子。 它们在兼容层中可以正常工作,但你应该改用response钩子,该钩子会接收构建好的Response。请注意(即使使用兼容层),替换response.body或在流式传输期间读取它的钩子也不受支持。- Nitro 3 中不存在
globalThis.$fetch。 对于应用的路由,请使用来自nuxt/server的serverFetch(event, path)。你也可以使用event.$fetch,或从#imports/server导入$fetch。 import.meta.url不再指向服务端入口。 Nitro v2 会将它重写为指向入口的全局值,但 Nitro v3 会输出实际值。请改为从你控制的路径解析资源。