生命周期

了解 Nitro 如何运行并为你的应用提供传入请求服务。

#服务器启动

当服务器启动时,在接受任何请求之前,Nitro 会:

执行服务器入口中的顶层代码。
创建 Nitro 应用,并按顺序运行运行时插件。

在无服务器和边缘预设中,这会在每次冷启动时发生。

#请求生命周期

请求可以在以下任何层中被拦截和终止(无论是否有响应),按以下顺序:

#request 钩子

request 钩子是每个传入请求运行的第一段代码。它通过运行时插件进行注册:

plugins/request-hook.ts
import { definePlugin } from "nitro";

export default definePlugin((nitroApp) => {
  nitroApp.hooks.hook("request", (event) => {
    console.log(`Incoming request on ${event.req.url}`);
  });
});

Note

在 request 钩子中抛出的错误会被 error 钩子捕获,不会终止请求管道。

#路由规则

接下来执行 Nitro 配置中的匹配路由规则。路由规则作为中间件运行,在所有全局中间件之前执行,并且其中大多数会修改响应而不会终止请求(例如添加标头或设置缓存策略)。匹配的规则会在任何中间件运行之前解析,并且在每个中间件中都可通过 event.context.routeRules 访问。

nitro.config.ts
import { defineConfig } from "nitro";

export default defineConfig({
  routeRules: {
    '/**': { headers: { 'x-nitro': 'first' } }
  }
})
Read more in 路由 > 路由规则.

#静态资源

启用静态资源服务时(大多数预设的默认设置),Nitro 会将静态资源处理程序注册为第一个全局中间件。它会在其他全局中间件和路由处理程序运行之前,检查请求是否匹配 public/ 目录中的文件。

如果找到匹配项,静态文件会立即使用适当的 Content-Type、ETag、Last-Modified 和 Cache-Control 标头进行响应。请求会被终止,不会再执行其他中间件或路由。

静态资源还通过 Accept-Encoding 标头支持对预压缩文件(gzip、brotli、zstd)的内容协商。

#全局中间件

接下来会运行 middleware/ 目录中定义的所有全局中间件:

middleware/info.ts
import { defineHandler } from "nitro";

export default defineHandler((event) => {
  event.context.info = { name: "Nitro" };
});

Warning

从中间件返回将关闭请求,应尽可能避免。

Read more in Docs > Routing#middleware.

#路由中间件

针对特定路由模式注册的中间件(通过 handlers 配置)会在全局中间件之后运行。附加到匹配路由本身的任何中间件会最后运行,就在路由处理程序之前。

#路由

Nitro 会将传入请求与 routes/ 文件夹中定义的路由进行匹配。

routes/api/hello.ts
export default (event) => ({ world: true })
Read more in Docs > Routing#filesystem Routing.

#服务器入口

如果定义了服务器入口,它会捕获所有未匹配任何路由的请求,充当 /** 路由处理程序。

server.ts
import { defineHandler } from "nitro";

export default defineHandler((event) => {
  if (event.url.pathname === "/") {
    return "Home page";
  }
});
Read more in Docs > Server Entry.

#渲染器

如果没有匹配的路由,Nitro 将查找渲染器处理程序(已定义或自动检测)来处理请求。

Read more in Docs > Renderer.

#response 钩子

在创建响应后(来自上述任何层),response 钩子运行。此钩子接收最终的 Response 对象和事件,可用于检查或修改响应头:

plugins/response-hook.ts
import { definePlugin } from "nitro";

export default definePlugin((nitroApp) => {
  nitroApp.hooks.hook("response", (res, event) => {
    console.log(`Response ${res.status} for ${event.req.url}`);
  });
});

Note

response 钩子为每个响应运行,包括静态资源、中间件终止的请求和错误响应。

#错误处理

当在请求生命周期的任何点发生错误时,Nitro:

调用 error 钩子,传入错误和上下文(包括事件和源标签)。
将错误传递给错误处理程序,将其转换为 HTTP 响应。

#抛出错误

从任何处理程序或中间件中抛出 HTTPError,以使用特定的状态码、消息和可选数据结束请求:

routes/signup.ts
import { defineHandler, HTTPError } from "nitro";

export default defineHandler((event) => {
  // Message and details
  throw new HTTPError("Invalid user input", { status: 400 });

  // Status code shortcut
  throw HTTPError.status(400, "Bad Request");

  // Full object form
  throw new HTTPError({
    status: 400,
    message: "Invalid user input",
    data: { field: "email" },
  });
});

Note

任何其他抛出的值(例如普通的 new Error())都会被视为未处理错误:响应始终为 500,并且出于安全考虑,消息、数据和堆栈不会发送给客户端。

#默认错误响应

在生产环境中,默认错误处理程序始终返回 JSON:

{
  "error": true,
  "status": 400,
  "message": "Invalid user input",
  "data": { "field": "email" }
}

在开发环境中,程序化请求会收到相同的 JSON 格式;但当请求的 Accept 标头包含 text/html 时(浏览器请求),Nitro 会渲染一个带有源映射堆栈跟踪的交互式 HTML 错误页面。

#自定义错误处理程序

要控制错误响应格式,请在 Nitro 配置中将 errorHandler 设置为一个文件,该文件使用 defineErrorHandler 导出处理程序:

import { defineConfig } from "nitro";

export default defineConfig({
  errorHandler: "~/error",
});

返回 Response 以将其发送给客户端,或不返回任何内容以继续交由下一个处理程序处理。errorHandler 还接受按顺序运行的处理程序数组:第一个返回响应的处理程序获胜,并且内置默认处理程序始终作为最后一项追加,因此始终存在备用响应:

nitro.config.ts
import { defineConfig } from "nitro";

export default defineConfig({
  errorHandler: ["~/error/known-errors", "~/error/fallback"],
});

如果处理程序自身抛出错误,则会记录该失败,并继续使用下一个处理程序。每个处理程序还会在其第三个参数中以 { defaultHandler } 的形式接收内置的 defaultHandler,因此你可以在自己的逻辑中复用默认渲染。

Read more in Examples > Custom Error Handler.

要仅自定义开发服务器的错误渲染(不影响生产环境构建),请将处理程序函数作为 devErrorHandler 传入:

nitro.config.ts
import { defineConfig } from "nitro";
import errorHandler from "./error.ts";

export default defineConfig({
  devErrorHandler: errorHandler,
});

#观察错误

要在不更改响应的情况下观察错误,请使用来自插件的 error 运行时钩子:

plugins/errors.ts
import { definePlugin } from "nitro";

export default definePlugin((nitroApp) => {
  nitroApp.hooks.hook("error", (error, context) => {
    console.error("Captured error:", error);
    // context.event - H3 事件(如果可用)
    // context.tags - 错误源标签,如 "request"、"response"、"plugin"
  });
});

你还可以通过 nitroApp.captureError 以编程方式将错误送入同一管道。错误也会按请求记录在 event.req.context.nitro.errors 中,以便在后续钩子中检查。

此外,进程级别的未处理 Promise 拒绝和未捕获异常会自动捕获到 error 钩子中,并带有 "unhandledRejection" 和 "uncaughtException" 标签。

#服务器关闭

当 Nitro 服务器关闭时,会调用 close 钩子。使用它来清理资源,如数据库连接、定时器或外部服务句柄:

plugins/cleanup.ts
import { definePlugin } from "nitro";

export default definePlugin((nitroApp) => {
  nitroApp.hooks.hook("close", async () => {
    // 清理资源
  });
});

Note

HTTP 服务器停止接受连接后会等待该钩子完成。长时间运行的服务器预设(Node.js、Bun 和 Deno)会在 SIGINT 和 SIGTERM 上调用它。无服务器和边缘预设没有关闭信号,因此只有在平台关闭运行时的情况下才会运行该钩子。

#钩子参考

所有运行时钩子都通过运行时插件使用 nitroApp.hooks.hook() 注册。

钩子签名运行时机
request(event: HTTPEvent) => void | Promise<void>每个请求开始时,路由之前。
response(res: Response, event: HTTPEvent) => void | Promise<void>响应创建后、发送前。
error(error: Error, context: { event?, tags? }) => void在生命周期期间捕获到任何错误时。
close() => void当 Nitro 服务器关闭时。

Note

NitroRuntimeHooks 接口是可扩展的。部署预设(如 Cloudflare)可以用平台特定的钩子来扩展它。

Read more in Docs > Plugins.