# Nitro 中文文档
> 构建可部署到任何地方的全栈服务器,支持零配置部署到任何你喜欢的托管平台。
---
# Home
::page-hero
#title
构建 [服务器]{.text-brand}
#description
Nitro 构建可在任何地方运行的生产就绪服务器。
编写 API 路由,然后无需任何配置即可将同一代码库部署到 Node.js、Bun、Deno 或无服务器环境。
#links
:app-hero-links
::
::stat-strip
---
# `animateFrom` 仅表示每个计数器开始滚动时的数值。它是一种视觉
# 效果,没有实际含义,绝不代表真实测量值。
stats:
- value: "3.8 kB"
label: Gzip 压缩后输出
animateFrom: 100
- value: "0"
label: 运行时依赖
animateFrom: 35
- value: "25+"
label: 部署目标
animateFrom: 0
- value: "~50 ms"
label: 冷启动
animateFrom: 350
---
::
::feature-section
---
eyebrow: 路由
link: /docs/routing
link-label: 路由文档
points:
- 路由会被编译,包中不会包含运行时路由器
- 动态参数、通配符和按方法区分的文件
- 路由组可以整理文件,而不会改变 URL
---
#title
文件即路由
#description
将文件放入 `routes/`,它就会成为一个路由。在文件名后追加 HTTP 方法来限定它,将文件夹嵌套以表示路径参数,并用括号包裹文件夹来对路由分组,而不会影响 URL。
#visual
:::route-map
---
dir: routes/
routes:
- file: hello.get.ts
method: GET
route: /hello
- file: hello.post.ts
method: POST
route: /hello
- file: api/test.ts
route: /api/test
- file: api/[org]/[repo].ts
route: /api/:org/:repo
- file: (admin)/users.ts
route: /users
---
:::
::
::feature-section
---
eyebrow: 服务器入口
reverse: true
link: /docs/server-entry
link-label: 服务器入口文档
---
#title
使用你自己的框架
#description
任何支持 Web `fetch(request): Response` 接口的框架都可以作为你的服务器入口,只需在 `server.ts` 中导出它,它就会在匹配路由之前处理每个请求。Node 风格的 `(req, res)` 框架同样适用:将文件命名为 `server.node.ts`,Nitro 会对其进行适配。
#visual
:::tabs
::::tab{label="H3" icon="i-unjs-h3"}
```ts [server.ts]
import { H3 } from "h3";
const app = new H3();
app.get("/", () => "⚡️ Hello from H3!");
export default app;
```
::::
::::tab{label="Hono" icon="i-logos-hono"}
```ts [server.ts]
import { Hono } from "hono";
const app = new Hono();
app.get("/", (c) => c.text("🔥 Hello from Hono!"));
export default app;
```
::::
::::tab{label="Elysia" icon="i-skill-icons-elysia-dark"}
```ts [server.ts]
import { Elysia } from "elysia";
const app = new Elysia();
app.get("/", () => "🦊 Hello from Elysia!");
export default app.compile();
```
::::
::::tab{label="Express" icon="i-simple-icons-express"}
```ts [server.node.ts]
import Express from "express";
const app = Express();
app.use("/", (_req, res) => {
res.send("Hello from Express with Nitro!");
});
export default app;
```
::::
:::
::
::feature-section
---
eyebrow: 部署
link: /deploy
link-label: 所有部署目标
points:
- CI 中会自动检测提供商,无需安装适配器
- 通过单个 `preset` 选项切换目标
- 兼容性日期可让提供商行为长期保持稳定
---
#title
一个代码库,适用于所有平台
#description
同一个服务器可以构建到 Node.js、Deno、Bun、边缘 Worker 和无服务器函数。Nitro 会生成每个主机所需的输出格式,因此在它们之间迁移无需修改代码!
#visual
:::deploy-grid
---
more: 25+ 个目标
moreTo: /deploy
targets:
- name: Node.js
icon: i-simple-icons-nodedotjs
to: /deploy/runtimes/node
- name: Deno
icon: i-simple-icons-deno
to: /deploy/runtimes/deno
- name: Bun
icon: i-simple-icons-bun
to: /deploy/runtimes/bun
- name: Cloudflare
icon: i-simple-icons-cloudflare
to: /deploy/providers/cloudflare
- name: Vercel
icon: i-simple-icons-vercel
to: /deploy/providers/vercel
- name: Netlify
icon: i-simple-icons-netlify
to: /deploy/providers/netlify
- name: AWS Lambda
icon: i-simple-icons-awslambda
to: /deploy/providers/aws
- name: Azure
icon: i-simple-icons-microsoftazure
to: /deploy/providers/azure
- name: Firebase
icon: i-simple-icons-firebase
to: /deploy/providers/firebase
- name: DigitalOcean
icon: i-simple-icons-digitalocean
to: /deploy/providers/digitalocean
- name: Render
icon: i-simple-icons-render
to: /deploy/providers/render
---
:::
::
::feature-section
---
eyebrow: 输出
reverse: true
link: /docs/quick-start
link-label: 构建你的第一个服务器
---
#title
小到足以阅读
#description
一个最小 Nitro 服务器的生产构建仅包含三个文件,无需在旁边安装任何内容。依赖会被打包并进行 Tree Shaking,因此你部署的就是自己编写的代码,以及让它运行所需的少量代码。
#visual
:::bar-chart
---
title: 生产服务器输出
data:
- label: 未压缩
value: 16.2
display: 16.2 kB
- label: 已压缩
value: 8.8
display: 8.8 kB
- label: 已压缩 + Gzip 压缩
value: 3.8
display: 3.8 kB
highlight: true
---
:::
::
::feature-section
---
eyebrow: 缓存
link: /docs/cache
link-label: 缓存文档
points:
- 默认使用 stale-while-revalidate 响应
- 开箱即用地支持 ETag、last-modified 和 304 处理
- 对同一键的并发请求共享单次调用
---
#title
与存储相匹配的缓存
#description
包装一个处理器或任意异步函数,Nitro 就会在你的应用已经使用的同一存储层上缓存其结果:开发环境中使用内存,生产环境中则使用 Redis、Cloudflare KV、Vercel Blob 存储或文件系统。代码相同,后端不同。
#visual
```ts [routes/stars.ts]
import { defineCachedHandler } from "nitro/cache";
export default defineCachedHandler(
async () => {
const res = await fetch("https://api.github.com/repos/nitrojs/nitro");
const { stargazers_count } = await res.json();
return { stars: stargazers_count };
},
{ maxAge: 60 * 60 }
);
```
::
::feature-grid
---
eyebrow: 内置功能
title: 服务器所需的一切
description: 大多数应用都会用到的功能都已随 Nitro 一起提供,并且在每个部署目标上的行为都相同。
features:
- title: KV 存储
description: 在文件系统、Redis、Cloudflare KV 等之上提供统一的键值 API。
icon: i-lucide-hard-drive
to: /docs/storage
- title: 数据库
description: 由 db0 驱动的 SQL 层,已预配置 SQLite。
icon: i-lucide-database
badge: 实验性
to: /docs/database
- title: 任务
description: 一次性运行时操作,可从 CLI 运行或按 cron 计划运行。
icon: i-lucide-list-checks
badge: 实验性
to: /docs/tasks
- title: WebSockets
description: 基于 crossws 构建的跨运行时 WebSocket 支持。
icon: i-lucide-radio
to: /docs/websocket
- title: 插件
description: 从 plugins/ 目录接入服务器生命周期。
icon: i-lucide-plug
to: /docs/plugins
- title: OpenAPI
description: 根据你的处理器生成规范,并通过 Scalar 或 Swagger UI 提供服务。
icon: i-lucide-file-json
badge: 实验性
to: /docs/openapi
- title: 资源
description: 直接提供公共文件,在运行时读取服务器资源。
icon: i-lucide-image
to: /docs/assets
- title: 渲染器
description: 用于处理未匹配路由的兜底处理器,可用于 SSR、SPA 外壳或纯 HTML。
icon: i-lucide-layout-template
to: /docs/renderer
- title: 生命周期
description: 请求按顺序经过的每一层,以及拦截请求的位置。
icon: i-lucide-activity
to: /docs/lifecycle
---
::
::call-to-action{command="create-nitro-app"}
#title
从一条命令开始
#description
创建项目脚手架,或将 Vite 插件添加到你已有的应用中。
#actions
:app-hero-links
::
::sponsors
---
# 简介
> Nitro 是一个全栈服务器框架,兼容任何运行环境和任何部署目标。
Nitro 为您提供了一个可用于生产环境的服务器,具备文件系统路由、代码拆分,以及对存储、缓存和数据库的内置支持,完全与运行时无关,并且可以部署到任何地方。
## 什么是 Nitro?
在 `routes/` 目录中创建服务器和 API 路由。每个文件都会直接映射到一个 URL 路径,而 Nitro 会处理其余工作:[路由](/docs/routing)、代码拆分和优化构建。
您也可以通过创建一个 `server.ts` [服务器入口](/docs/server-entry)来完全掌控服务器。Nitro 与运行时无关的方式让您可以使用任何 HTTP 库,例如 [Elysia](https://elysiajs.com/)、[H3](https://h3.dev) 或 [Hono](https://hono.dev)。
### 性能
Nitro 在构建时编译您的路由,无需运行时路由器。只需加载和执行处理每个传入请求所需的代码。这使其非常适合无服务器托管,无论项目大小如何,都能实现接近 0 毫秒的启动时间。
### 随处部署
将您的服务器构建到经过优化的 `.output/` 文件夹中,无需任何配置即可兼容 Node.js、Bun、Deno 和许多托管平台:Cloudflare Workers、Netlify、Vercel 等。无需更改一行代码,即可利用 ESR、ISR 和 SWR 等平台功能。
### 服务端渲染
使用您喜欢的模板引擎渲染 HTML,或直接在服务器上使用 React、Vue 或 Svelte 等组件库。通过客户端注水实现完全通用渲染。Nitro 提供基础架构和渐进式方法来达成您的目标。
### 存储
Nitro 开箱即用地提供了与运行时无关的键值 [存储层](/docs/storage)。它默认使用内存存储,但您可以连接 20 多种不同的驱动程序(FS、Redis、S3 等),将它们附加到不同的命名空间,并在不更改代码的情况下进行替换。
### 缓存
Nitro 支持服务器路由和服务器函数的[缓存](/docs/cache),并直接由服务器存储提供支持(通过 `cache` 命名空间)。
### 数据库
Nitro 还内置了 SQL [数据库](/docs/database)。它默认使用 SQLite,但您可以使用相同的 API 连接并查询 10 多种数据库(Postgres、MySQL、PGLite 等)。
### 元框架基础
Nitro 可用作构建您自己元框架的基础。Nuxt、SolidStart 和 TanStack Start 等流行框架都完全或部分地利用了 Nitro。
## Vite 集成
Nitro 以插件形式与 [Vite](https://vite.dev) 无缝集成。如果您正在使用 Vite 构建前端应用,添加 Nitro 后即可获得 API 路由、服务端渲染和完整的生产服务器,并通过 `vite build` 一起完成构建。
```ts [vite.config.ts]
import { defineConfig } from "vite";
import { nitro } from "nitro/vite";
export default defineConfig({
plugins: [nitro()],
});
```
使用 Nitro 时,`vite build` 会生成一个经过优化的 `.output/` 文件夹,其中同时包含您的前端和后端,可随处部署。
准备好尝试了吗?进入 [快速开始](/docs/quick-start)。
---
# 快速开始
> 从一个全新的 Nitro 项目开始,或在现有的 Vite 项目中采用它。
## 在线试用 Nitro
在我们的游乐场中通过浏览器体验 Nitro。
[在 StackBlitz 中试用 Nitro](https://stackblitz.com/github/nitrojs/starter/tree/v3-vite?file=index.html,server.ts){target="_blank"}
## 创建 Nitro 项目
创建 Nitro 应用最快的方式是使用 `create-nitro-app`。
> [!NOTE]
> 请确保你已安装最新的 LTS 版本 [Node.js](https://nodejs.org/en)、[Bun](https://bun.sh/) 或 [Deno](https://deno.com/)。
:pm-x{command="create-nitro-app"}
预览
按照 CLI 指引操作,你就可以启动开发服务器了。
## 添加到 Vite 项目
你可以将 Nitro 添加到任何现有的 Vite 项目中,以获得 API 路由、服务端渲染等功能。
::steps{level="3"}
### 安装 `nitro` 和 `vite`
:pm-install{name="nitro vite"}
### 向 Vite 添加 Nitro 插件
将 Nitro 插件添加到你的 `vite.config.ts` 中:
```ts [vite.config.ts] {2,6}
import { defineConfig } from "vite";
import { nitro } from "nitro/vite";
export default defineConfig({
plugins: [
nitro()
],
});
```
### 配置 Nitro
创建 `nitro.config.ts` 来配置服务器目录:
```ts [nitro.config.ts]
import { defineConfig } from "nitro";
export default defineConfig({
serverDir: "./server",
});
```
`serverDir` 选项告诉 Nitro 在哪里查找服务器路由。在此示例中,所有路由都将位于 `server/` 目录中。默认情况下,`serverDir` 为 `false`,因此在设置它(或 `scanDirs`)之前不会扫描任何目录。
### 创建 API 路由
在 `server/api/test.ts` 创建你的第一个 API 路由:
::code-tree{defaultValue="server/api/test.ts"}
```ts [server/api/test.ts]
import { defineHandler } from "nitro";
export default defineHandler(() => {
return { message: "Hello Nitro!" };
});
```
```ts [nitro.config.ts]
import { defineConfig } from "nitro";
export default defineConfig({
serverDir: "./server",
});
```
```ts [vite.config.ts]
import { defineConfig } from "vite";
import { nitro } from "nitro/vite";
export default defineConfig({
plugins: [nitro()],
});
```
::
文件路径会直接映射到路由 URL:`server/api/test.ts` 会变成 `/api/test`。处理器可以返回字符串、JSON 对象、`Response` 实例或可读流。
::tip
作为文件系统路由的替代方案,你可以使用 `routes` 配置选项以编程方式声明路由。查看 [程序化路由处理器](/docs/routing#programmatic-route-handlers) 了解更多详情。
::
### 启动开发服务器
:pm-run{script="dev"}
你的 API 路由现在可以通过 `http://localhost:3000/api/test` 访问 :sparkles:
::
## 后续步骤
- 在 [路由](/docs/routing) 中了解动态路由、方法和中间件。
- 使用[渲染器](/docs/renderer)为未匹配的路由提供 HTML 或单页应用。
- 通过[服务器入口](/docs/server-entry)完全控制每个请求。
---
# Nitro 渲染器
> 使用渲染器处理所有未匹配的路由,并返回自定义 HTML 或使用模板系统。
渲染器是 Nitro 中的一个特殊处理程序,用于捕获所有未与任何特定 API 或路由处理程序匹配的路由。它通常用于服务端渲染(SSR)、提供单页应用(SPA)或创建自定义 HTML 响应。
## 配置
渲染器通过 Nitro 配置中的 `renderer` 选项进行配置:
```ts [nitro.config.ts]
import { defineConfig } from "nitro";
export default defineConfig({
renderer: {
template: './index.html', // Path to HTML template file
handler: './renderer.ts', // Path to custom renderer handler
static: false, // Force dynamic template processing (auto-detected when unset)
}
})
```
| 选项 | 类型 | 描述 |
| --- | --- | --- |
| `template` | `string` | 用作渲染器模板的 HTML 文件路径。 |
| `handler` | `string` | 自定义渲染器处理程序模块的路径。 |
| `static` | `boolean` | 当设为 `true` 时,跳过 rendu 模板处理并按原样提供 HTML。如未设置,将根据模板语法自动检测。 |
在配置中设置 `renderer: false` 以完全禁用渲染器(包括自动检测 `index.html`)。
## HTML 模板
### 自动检测 `index.html`
默认情况下,Nitro 会自动在项目根目录或扫描目录之一中查找 `index.html` 文件。
如果找到,Nitro 会将其用作渲染器模板,并为所有未匹配的路由提供服务。
::code-group
```html [index.html]
我的 Vite + Nitro 应用
```
```ts [routes/api/hello.ts]
import { defineHandler } from "nitro";
export default defineHandler((event) => {
return { hello: "API" };
});
```
::
::tip
当检测到 `index.html` 时,Nitro 会自动在终端中记录:`Using index.html as renderer template.`
::
在此配置下:
- `/api/hello` → 由您的 API 路由处理
- `/about`、`/contact` 等 → 通过 `index.html` 提供服务
### 自定义 HTML 文件
您可以使用 Nitro 配置中的 `renderer.template` 选项来指定自定义 HTML 模板文件。
::code-group
```ts [nitro.config.ts]
import { defineConfig } from "nitro";
export default defineConfig({
renderer: {
template: './app.html'
}
})
```
```html [app.html]
自定义模板
加载中...
```
::
### 静态模板
默认情况下,Nitro 会自动检测您的 HTML 模板是否包含 [rendu](#超文本预处理器实验性) 语法。如果包含,则会在每次请求时动态处理模板;如果不包含,则作为静态 HTML 提供。
您可以使用 `static` 选项覆盖此行为:
```ts [nitro.config.ts]
import { defineConfig } from "nitro";
export default defineConfig({
renderer: {
template: './index.html',
static: true // 强制静态提供,跳过模板处理
}
})
```
在生产环境中,静态模板会被内联到服务器包中并直接提供,以获得最佳性能。
### 超文本预处理器(实验性)
对于动态模板,Nitro 使用 [rendu](https://github.com/h3js/rendu) 超文本预处理器,这是一种使用 JavaScript 表达式创建动态 HTML 模板的简单而强大的方式。
#### 输出表达式
- `{{ expression }}`:HTML 转义输出
- `{{{ expression }}}` 或 `= expression ?>`:原始(未转义)输出
```html
你好 {{ $URL.pathname }}
{{{ '原始 html' }}}
```
#### 控制流
使用 ` ... ?>` 进行 JavaScript 控制流:
```html
if ($METHOD === 'POST') { ?>
表单已提交!
} else { ?>
} ?>
for (const item of ['a', 'b', 'c']) { ?>
- {{ item }}
} ?>
```
#### 服务端脚本
使用 `
{{ JSON.stringify(data) }}
```
#### 流式内容
使用 `echo()` 函数进行内容流式传输。它接受字符串、函数、Promise、Response 对象或 ReadableStream:
```html
```
#### 全局变量
在模板中访问请求上下文:
| 变量 | 描述 |
| --- | --- |
| `$REQUEST` | 传入的 `Request` 对象 |
| `$METHOD` | HTTP 方法(`GET`、`POST` 等) |
| `$URL` | 请求 `URL` 对象 |
| `$HEADERS` | 请求头 |
| `$RESPONSE` | 响应配置对象 |
| `$COOKIES` | 包含请求 Cookie 的只读对象 |
#### 内置函数
| 函数 | 描述 |
| --- | --- |
| `htmlspecialchars(str)` | 转义 HTML 字符(在 `{{ }}` 语法中自动应用) |
| `setCookie(name, value, options?)` | 在响应中设置 Cookie |
| `redirect(url)` | 将用户重定向到另一个 URL |
| `echo(content)` | 将内容流式传输到响应 |
```html [index.html]
动态模板
你好 {{ $REQUEST.url }}
欢迎,= $COOKIES["user"] || "访客" ?>!
```
:read-more{to="https://github.com/h3js/rendu" title="Rendu 文档"}
## 自定义渲染器处理程序
对于更复杂的场景,您可以创建以编程方式生成响应的自定义渲染器处理程序。
该处理程序是一个默认导出的函数,接收一个 H3 事件对象。您可以通过 `event.req` 访问传入的 `Request`:
```ts [renderer.ts]
export default function renderer({ req }: { req: Request }) {
const url = new URL(req.url);
return new Response(
/* html */ `
自定义渲染器
来自自定义渲染器的问候!
当前路径:${url.pathname}
`,
{ headers: { "content-type": "text/html; charset=utf-8" } }
);
}
```
然后,在 Nitro 配置中指定渲染器入口:
```ts [nitro.config.ts]
import { defineConfig } from "nitro";
export default defineConfig({
renderer: {
handler: './renderer.ts'
}
})
```
::note
当设置了 `renderer.handler` 时,它将完全控制渲染。`renderer.template` 选项将被忽略。
::
## 渲染器优先级
渲染器始终充当捕获所有路由的通配符路由(`/**`),并具有**最低优先级**。这意味着:
1. 特定的 API 路由首先匹配(例如 `/api/users`)
2. 特定的服务器路由随后匹配(例如 `/about`)
3. 渲染器捕获其他所有内容
```md
api/
users.ts → /api/users(首先匹配)
routes/
about.ts → /about(其次匹配)
renderer.ts → /**(捕获所有其他路由)
```
::note
如果您定义了自己的捕获所有路由(`[...].ts`),它会与其他 `/**` 处理程序串联,而不是取代它们:捕获所有处理程序会按照注册顺序运行(基于文件的捕获所有处理程序,然后是[服务器入口](/docs/server-entry),最后是渲染器),第一个返回响应的处理程序获胜。渲染器只会处理您的捕获所有路由未作出响应的请求。
::
:read-more{to="/docs/lifecycle" title="生命周期"}
## Vite 集成
将 Nitro 与 Vite 一起使用时,渲染器会与 Vite 的构建管道和开发服务器集成。
### 开发模式
在开发过程中,每次请求都会从磁盘读取渲染器模板,因此对 `index.html` 的更改会立即生效,无需重启服务器。Vite 的 `transformIndexHtml` 钩子会被应用,以注入 HMR 客户端脚本和其他开发时转换。
### 使用 `` 的 SSR
在使用带有 `ssr` 服务的 Vite 环境时,您可以在 `index.html` 中添加 `` 注释。Nitro 会在渲染期间将其替换为您的 SSR 入口的输出:
```html [index.html]
SSR 应用
```
### 生产构建
在生产构建期间,Vite 会通过其构建管道处理 `index.html`(解析脚本、CSS 和其他资源),然后 Nitro 将转换后的 HTML 内联到服务器包中。
## 使用场景
### 单页应用(SPA)
为所有路由提供 SPA 的 `index.html` 以启用客户端路由:
> [!TIP]
> 这是 Nitro 与 Vite 一起使用时的默认行为。
---
# 路由
> Nitro 支持文件系统路由,可自动将文件映射到路由。路由会在构建时进行编译和代码分割,因此无需运行时路由器。
## 路由处理器
路由处理器是一个接收 `event` 对象([H3Event](https://h3.dev/guide/api/h3event#h3event-properties))并返回响应的函数。
::code-group
```ts [单个函数]
import type { H3Event } from "nitro";
export default (event: H3Event) => {
return "world";
}
```
```ts [defineHandler]
import { defineHandler } from "nitro";
// 为了更好的类型推断
export default defineHandler((event) => {
return "world";
});
```
::
## 文件系统路由
文件会自动映射到 [h3 路由](https://h3.dev/guide/basics/routing)。定义路由非常简单,只需在 [`serverDir`](/docs/configuration) 的 `api/` 或 `routes/` 目录中创建文件。
::note
文件系统路由要求在配置中设置 [`serverDir`](/docs/configuration)(或 `scanDirs`);默认情况下不会扫描任何目录。
::
每个文件定义一个处理器,你可以在文件名后[追加 HTTP 方法](#specific-request-method),以匹配特定的请求方法。
```
routes/
api/
test.ts <-- /api/test
hello.get.ts <-- /hello (仅 GET)
hello.post.ts <-- /hello (仅 POST)
vite.config.ts
```
你可以通过创建子目录来嵌套路由。
```txt
routes/
api/
[org]/
[repo]/
index.ts <-- /api/:org/:repo
issues.ts <-- /api/:org/:repo/issues
index.ts <-- /api/:org
package.json
```
### 路由组
要组织相关的路由文件而不影响最终生成的 URL,可以将它们放在用括号 `(` 和 `)` 包裹的文件夹中。
例如:
```txt
routes/
api/
(admin)/
users.ts <-- /api/users
reports.ts <-- /api/reports
(public)/
index.ts <-- /api
package.json
```
> [!NOTE] 路由组不会成为路由路径的一部分,仅用于组织文件。
### 静态路由
在 `routes/` 或 `routes/api/` 目录中创建文件。文件路径会成为路由路径。然后导出一个路由处理器:
```ts [routes/api/test.ts]
import { defineHandler } from "nitro";
export default defineHandler(() => {
return { hello: "API" };
});
```
### 动态路由
#### 单参数
要定义带参数的路由,请使用 `[]` 语法,其中 `` 是参数名称。参数可以通过 `event.context.params` 对象访问。
```ts [routes/hello/[name\\].ts]
import { defineHandler } from "nitro";
export default defineHandler((event) => {
const { name } = event.context.params;
return `Hello ${name}!`;
});
```
调用 `/hello/nitro` 会返回:
```txt [响应]
Hello nitro!
```
#### 多参数
你可以使用 `[]/[]` 语法在路由中定义多个参数,其中每个参数都是一个文件夹。**不能**在单个文件名或文件夹中定义多个参数。
```ts [routes/hello/[name\\]/[age\\].ts]
import { defineHandler } from "nitro";
export default defineHandler((event) => {
const { name, age } = event.context.params;
return `Hello ${name}! You are ${age} years old.`;
});
```
#### 捕获所有参数
你可以使用 `[...]` 语法捕获 URL 的所有剩余部分。这将把 `/` 包含在参数中。
```ts [routes/hello/[...name\\].ts]
import { defineHandler } from "nitro";
export default defineHandler((event) => {
const { name } = event.context.params;
return `Hello ${name}!`;
});
```
调用 `/hello/nitro/is/hot` 会返回:
```txt [响应]
Hello nitro/is/hot!
```
### 特定请求方法
你可以在文件名后追加 HTTP 方法来强制路由仅匹配特定的 HTTP 请求方法,例如 `hello.get.ts` 将只匹配 `GET` 请求。你可以使用任何你想要的 HTTP 方法。
支持的方法:`get`、`post`、`put`、`delete`、`patch`、`head`、`options`、`query`、`connect`、`trace`。
::note
`query` 匹配 [`QUERY` 方法](https://datatracker.ietf.org/doc/draft-ietf-httpbis-safe-method-w-body/),这是一种携带请求体的安全且幂等的 `GET` 替代方法。在代理、CDN 和部署平台中的支持仍不一致,因此对于公共 API,请保留 `GET` 或 `POST` 回退方案。
::
::code-group
```ts [GET]
// routes/users/[id].get.ts
import { defineHandler } from "nitro";
export default defineHandler(async (event) => {
const { id } = event.context.params;
// 对 id 进行一些操作
return `User profile!`;
});
```
```ts [POST]
// routes/users.post.ts
import { defineHandler } from "nitro";
export default defineHandler(async (event) => {
const body = await event.req.json();
// 对 body 进行一些操作,比如保存到数据库
return { updated: true };
});
```
::
### 捕获所有路由
你可以创建一个特殊路由,用于匹配未被其他路由处理的所有请求,适合作为默认路由或回退路由。
要创建捕获所有路由,请创建一个名为 `[...].ts` 的文件。
```ts [routes/[...\\].ts]
import { defineHandler } from "nitro";
export default defineHandler((event) => {
return `Hello ${event.url.pathname}!`;
});
```
::note
对于未命名的捕获所有路由(`[...].ts`),匹配到的通配符片段可以通过 `event.context.params._` 访问。
::
[服务器入口](/docs/server-entry)和[渲染器](/docs/renderer)也会作为捕获所有处理器,并在基于文件的捕获所有路由之后串联执行。
### 特定环境的处理器
你可以在文件名中添加 `.dev`、`.prod` 或 `.prerender` 后缀,使路由仅包含在特定构建中,例如:`routes/test.get.dev.ts` 或 `routes/test.get.prod.ts`。
该后缀放在方法后缀之后(如果有的话):
```txt
routes/
env/
index.dev.ts <-- /env (仅开发环境)
index.get.prod.ts <-- /env (GET, 仅生产环境)
```
> [!TIP]
> 要针对多个环境,或使用预设名称作为环境,请通过 [`routes`](#routes-config) 配置以编程方式注册路由。
### 忽略文件
你可以使用 `ignore` 配置选项来排除文件不被路由扫描。它接受相对于服务器目录的 glob 模式数组。
```ts [nitro.config.ts]
import { defineConfig } from "nitro";
export default defineConfig({
ignore: [
"routes/api/**/_*", // 忽略 api/ 中以 _ 开头的文件
"middleware/_*.ts", // 忽略以 _ 开头的中间件
"routes/_*.ts", // 忽略根路由中以 _ 开头的文件
],
});
```
### 路由元数据
你可以在路由处理器文件中使用 `defineRouteMeta` 宏,在构建时定义路由元数据。
> [!IMPORTANT]
> 此功能目前处于实验阶段。
```ts [routes/api/test.ts]
import { defineRouteMeta } from "nitro";
import { defineHandler } from "nitro";
defineRouteMeta({
openAPI: {
tags: ["test"],
description: "Test route description",
parameters: [{ in: "query", name: "test", required: true }],
},
});
export default defineHandler(() => "OK");
```
::read-more{to="https://swagger.io/specification/v3/"}
此功能目前可用于指定 OpenAPI 元数据。有关可用的 OpenAPI 选项,请参阅 Swagger 规范。
::
## 程序化路由处理器
除了文件系统路由外,你还可以使用 `routes` 配置选项以程序化方式注册路由处理器。
### `routes` 配置
`routes` 选项允许你将路由模式映射到处理器:
```ts [nitro.config.ts]
import { defineConfig } from "nitro";
export default defineConfig({
routes: {
"/api/hello": "./server/routes/api/hello.ts",
"/api/custom": {
handler: "./server/routes/api/hello.ts",
method: "POST",
lazy: true,
},
"/virtual": {
handler: "#virtual-route",
},
},
});
```
每个路由条目可以是一个简单的字符串(处理器路径)或一个具有以下选项的对象:
| 选项 | 类型 | 描述 |
|--------|------|-------------|
| `handler` | `string` | 事件处理器文件或虚拟模块 ID 的路径 |
| `method` | `string` | 要匹配的 HTTP 方法(`get`、`post` 等) |
| `lazy` | `boolean` | 使用懒加载导入处理器 |
| `format` | `"web" \| "node"` | 处理器类型。`"node"` 处理器会被转换为兼容 web 的格式 |
| `env` | `string \| string[]` | 包含此处理器的环境(`"dev"`、`"prod"`、`"prerender"` 或预设名称) |
### `handlers` 配置
`handlers` 数组适用于注册具有路由匹配控制权的中间件:
```ts [nitro.config.ts]
import { defineConfig } from "nitro";
export default defineConfig({
handlers: [
{
route: "/api/**",
handler: "./server/utils/api-auth.ts",
middleware: true,
},
],
});
```
每个处理器条目支持以下选项:
| 选项 | 类型 | 描述 |
|--------|------|-------------|
| `route` | `string` | HTTP 路径名模式(例如 `/test`、`/api/:id`、`/blog/**`) |
| `handler` | `string` | 事件处理器文件或虚拟模块 ID 的路径 |
| `method` | `string` | 要匹配的 HTTP 方法(`get`、`post` 等) |
| `middleware` | `boolean` | 在路由处理器之前作为中间件运行处理器 |
| `lazy` | `boolean` | 使用懒加载导入处理器 |
| `format` | `"web" \| "node"` | 处理器类型。`"node"` 处理器会被转换为兼容 web 的格式 |
| `env` | `string \| string[]` | 包含此处理器的环境(`"dev"`、`"prod"`、`"prerender"` 或预设名称) |
## 中间件
中间件在路由处理器之前运行,让你可以检查或扩展传入的请求。有关中间件在请求管道中的位置,请参阅[生命周期](/docs/lifecycle)。
::tip
中间件可以在请求被处理之前修改请求,但不能在请求处理之后修改。
::
中间件会从 `middleware/` 目录自动注册。
```md
middleware/
auth.ts
logger.ts
...
routes/
hello.ts
```
### 简单中间件
中间件的定义方式与路由处理器完全相同,但有一个例外:它们不应返回任何内容。
```ts [middleware/auth.ts]
import { defineHandler } from "nitro";
export default defineHandler((event) => {
// Extend or modify the event
event.context.user = { name: "Nitro" };
});
```
`middleware/` 目录中的中间件会自动为所有路由注册。要为特定路由模式注册中间件,请参阅[路由作用域中间件](#route-scoped-middleware)。
::warning
从中间件返回值会关闭请求:该值会成为响应,并且不会继续运行后续处理器。请避免从中间件返回内容;应改用路由处理器。
::
### 执行顺序
中间件按目录列表顺序执行。
```md
middleware/
auth.ts <-- 第一个
logger.ts <-- 第二个
... <-- 第三个
```
在中间件文件名前添加数字以控制它们的执行顺序。
```md
middleware/
1.logger.ts <-- 第一个
2.auth.ts <-- 第二个
3.... <-- 第三个
```
::note
文件名会按字符串排序,因此 `10.filename.ts` 会紧跟在 `1.filename.ts` 之后,并排在 `2.filename.ts` 之前。如果同一目录中有超过 10 个中间件,请在 `1-9` 前加上 `0`(例如 `01.filename.ts`)。
::
### 请求过滤
全局中间件会在每个请求上运行,但你可以添加自定义条件来限定其作用范围。例如,检查 URL,以便将中间件应用于特定路由:
```ts [middleware/auth.ts]
import { defineHandler } from "nitro";
export default defineHandler((event) => {
// 仅对 /auth 路由执行
if (event.url.pathname.startsWith('/auth')) {
event.context.user = { name: "Nitro" };
}
});
```
### 路由作用域中间件
你可以使用 [`handlers`](#handlers-config) 配置并设置 `middleware` 选项和特定的 `route` 来为特定路由模式注册中间件:
```ts [nitro.config.ts]
import { defineConfig } from "nitro";
export default defineConfig({
handlers: [
{
route: "/api/**",
handler: "./server/utils/api-auth.ts",
middleware: true,
},
],
});
```
与全局中间件(从 `middleware/` 目录自动注册,并匹配 `/**`)不同,路由作用域中间件只会对匹配指定模式的请求运行。
请将路由作用域处理器文件放在 `middleware/` 目录之外(如上例所示),这样目录扫描不会同时将它们注册为全局中间件。
## 错误处理
你可以使用 [H3 中提供的工具](https://h3.dev/guide/basics/error)来处理路由处理器和中间件中的错误。
错误发送回客户端的方式取决于环境。在开发环境中,`Accept` 标头为 `text/html` 的请求(例如浏览器请求)会收到 HTML 错误页面。在生产环境中,错误始终以 JSON 形式发送。此行为可能会受到请求属性的影响,例如 `Accept` 或 `User-Agent` 标头。
要使用 `error` hook 全局捕获错误,请参阅[生命周期](/docs/lifecycle#error-handling)。
## 代码分割
Nitro 为每个路由处理程序创建单独的代码块。代码块在首次请求时按需加载,因此 `/api/users` 不会加载 `/api/posts` 的代码。
查看 [`inlineDynamicImports`](/config#inlinedynamicimports) 以将所有内容打包到单个文件中。
## 路由规则
路由规则允许你直接从配置中将行为(重定向、代理、缓存、身份验证、自定义标头)附加到路由模式,而无需编写处理器代码。
每条规则都会将路由模式(使用 [rou3](https://github.com/h3js/rou3) 语法)映射到一组选项,这些选项基于 [h3 路由规则](https://h3.dev/guide/rules)。设置 `cache` 选项后,匹配的处理器会自动使用 `defineCachedHandler` 包装。请参阅[缓存指南](/docs/cache)了解更多信息。
使用 `routeRules` 配置选项设置路由规则:
```ts [nitro.config.ts]
import { defineConfig } from "nitro";
export default defineConfig({
routeRules: {
'/blog/**': { swr: true },
'/blog2/**': { swr: 600 },
'/blog3/**': { static: true },
'/blog4/**': { cache: { /* 缓存选项 */ } },
'/assets/**': { headers: { 'cache-control': 's-maxage=0' } },
'/api/v1/**': { cors: true, headers: { 'access-control-allow-methods': 'GET' } },
'/old-page': { redirect: '/new-page' },
'/old-page/**': { redirect: '/new-page/**' },
'/proxy/example': { proxy: 'https://example.com' },
'/proxy/**': { proxy: '/api/**' },
}
});
```
### 规则合并与覆盖
路由规则按照从最不具体到最具体的顺序匹配。当多个规则匹配一个请求时,它们的选项会合并,更具体的规则优先。
你可以使用 `false` 来禁用由更通用模式设置的规则:
```ts [nitro.config.ts]
import { defineConfig } from "nitro";
export default defineConfig({
routeRules: {
'/api/cached/**': { swr: true },
'/api/cached/no-cache': { cache: false, swr: false },
'/api/**': { cors: true },
'/api/internal/**': { cors: false },
}
});
```
### 按方法限定的规则
在规则键前加上大写的 HTTP 方法(后跟一个空格),即可将规则限定为使用该方法的请求。不带方法前缀的键适用于所有方法。按方法限定的规则会合并到匹配相同路径的、与方法无关的规则之上,因此你可以在共享默认配置的基础上叠加特定于方法的行为:
```ts [nitro.config.ts]
import { defineConfig } from "nitro";
export default defineConfig({
routeRules: {
// 应用于所有方法
'/api/**': { headers: { 'x-api': 'true' } },
// Only POST requests to /api/** get this header
'POST /api/**': { headers: { 'x-write': 'true' } },
// Cache GET requests to /feed only
'GET /feed': { swr: 600 },
}
});
```
::note
方法匹配由服务器运行时针对每个请求进行解析。平台原生的静态生成(例如 Netlify/Cloudflare 的 `_headers` 和 `_redirects`、Vercel 的 `config.json`)不会按方法进行拆分,因此对于希望平台写入其静态配置的 `headers`/`redirect`/`proxy` 规则,建议使用与方法无关的键。
::
### Headers
为匹配的路由设置自定义响应头:
```ts [nitro.config.ts]
import { defineConfig } from "nitro";
export default defineConfig({
routeRules: {
'/api/**': { headers: { 'cache-control': 's-maxage=60' } },
'**': { headers: { 'x-powered-by': 'Nitro' } },
}
});
```
### 跨域资源共享(CORS)
在运行时使用 `cors` 规则处理 CORS。`cors: true` 会应用宽松的默认设置(来源、方法和允许的请求头均为 `*`):简单请求会获得 `access-control-allow-origin: *` 和 `access-control-expose-headers: *`,而 `OPTIONS` 预检请求会被直接响应(状态码为 `204`),并返回匹配的 `access-control-allow-*` 请求头。
> [!NOTE]
> CORS 由正在运行的服务器(h3 的 [`handleCors`](https://h3.dev/utils/security#handlecorsevent-options))应用,而不是由静态/CDN 配置应用。
> 在可以直接从边缘节点提供预渲染/静态资源的平台上,只有当请求到达服务器处理程序时,才会添加 CORS 请求头。
传入对象可以进行更精细的控制:来源允许列表、`credentials`、`maxAge` 等(h3 `CorsOptions`)。将 `credentials: true` 与通配符来源结合使用是无效的,并会在构建时抛出错误:
```ts [nitro.config.ts]
import { defineConfig } from "nitro";
export default defineConfig({
routeRules: {
// 宽松的默认设置
'/api/public/**': { cors: true },
// 限制为特定来源并启用凭据
'/api/v1/**': {
cors: { origin: ['https://app.example.com'], credentials: true },
},
}
});
```
### 重定向
将匹配的路由重定向到另一个 URL。使用字符串进行简单重定向(默认为 `307` 状态码),或使用对象进行更精细的控制:
```ts [nitro.config.ts]
import { defineConfig } from "nitro";
export default defineConfig({
routeRules: {
// 简单重定向(307 状态码)
'/old-page': { redirect: '/new-page' },
// 自定义状态码的重定向
'/legacy': { redirect: { to: 'https://example.com/', status: 308 } },
// Wildcard redirect, preserves the path after the pattern
'/old-blog/**': { redirect: 'https://blog.example.com/**' },
}
});
```
### 代理
将请求代理到另一个 URL。支持内部和外部目标:
```ts [nitro.config.ts]
import { defineConfig } from "nitro";
export default defineConfig({
routeRules: {
// 代理到确切 URL
'/api/proxy/example': { proxy: 'https://example.com' },
// 代理到内部路由
'/api/proxy/**': { proxy: '/api/echo' },
// Wildcard proxy, preserves the path after the pattern
'/cdn/**': { proxy: 'https://cdn.jsdelivr.net/**' },
// 带选项的代理
'/external/**': {
proxy: {
to: 'https://api.example.com/**',
// 其他 H3 代理选项...
},
},
}
});
```
### 身份验证
没有 `auth` 路由规则:身份验证需要可执行逻辑,因此应放在[中间件](#middleware)中。对于单个路由,请使用 h3 的 [`basicAuth`](https://h3.dev/utils/security#basicauthopts) 中间件:
```ts [routes/admin/index.ts]
import { defineHandler } from "nitro";
import { basicAuth } from "nitro/h3";
export default defineHandler({
middleware: [basicAuth({ username: 'admin', password: 'supersecret', realm: 'Admin Area' })],
handler: (event) => `Hello, ${event.context.basicAuth?.username}!`,
});
```
对于整个子树,可以将其注册为[路由作用域中间件](#route-scoped-middleware),或注册为 `middleware/` 目录中的全局中间件。
### 缓存(SWR / 静态)
使用 `cache`、`swr` 或 `static` 选项控制缓存行为:
```ts [nitro.config.ts]
import { defineConfig } from "nitro";
export default defineConfig({
routeRules: {
// 启用 stale-while-revalidate 缓存
'/blog/**': { swr: true },
// 带 maxAge(秒)的 SWR
'/blog/posts/**': { swr: 600 },
// 完整缓存选项
'/api/data/**': {
cache: {
maxAge: 60,
swr: true,
// ...其他缓存选项
},
},
// 禁用缓存
'/api/realtime/**': { cache: false },
}
});
```
::tip
`swr: true` 是 `cache: { swr: true }` 的简写,`swr: ` 是 `cache: { swr: true, maxAge: }` 的简写。
::
### 预渲染
标记路由以在构建时进行预渲染:
```ts [nitro.config.ts]
import { defineConfig } from "nitro";
export default defineConfig({
routeRules: {
'/about': { prerender: true },
'/dynamic/**': { prerender: false },
}
});
```
预渲染路由会在构建时生成一次,并作为静态文件提供。它们不会在运行时重新验证。
::warning
不要在同一路由上同时使用 `prerender` 和 `isr`。预渲染具有更高优先级,因此 `isr` 会被忽略,页面也不会在部署后重新生成。在支持的平台上需要增量再生时,请单独使用 `isr`。
::
### ISR(Vercel)
为 Vercel 部署配置增量静态再生:
```ts [nitro.config.ts]
import { defineConfig } from "nitro";
export default defineConfig({
routeRules: {
'/isr/**': { isr: true },
'/isr-ttl/**': { isr: 60 },
'/isr-custom/**': {
isr: {
expiration: 60,
allowQuery: ['q'],
group: 1,
},
},
}
});
```
::note
ISR 会按需生成页面,并根据你设置的过期时间重新验证页面。它是构建时 `prerender` 的替代方案,而不是可以在同一路由上与其结合使用的功能。
::
### 路由规则参考
| 选项 | 类型 | 描述 |
|--------|------|-------------|
| `headers` | `Record` | 自定义响应标头 |
| `redirect` | `string \| { to: string, status?: number }` | 重定向到另一个 URL(默认状态码:`307`) |
| `proxy` | `string \| { to: string, ...proxyOptions }` | 将请求代理到另一个 URL |
| `cors` | `boolean \| CorsOptions` | 通过 h3 的 [`handleCors`](https://h3.dev/utils/security#handlecorsevent-options) 处理 CORS(`true` = 宽松设置) |
| `cache` | `object \| false` | 缓存选项(请参阅[缓存指南](/docs/cache)) |
| `swr` | `boolean \| number` | `cache: { swr: true, maxAge: number }` 的简写(`false` 会重置继承的缓存规则) |
| `static` | `boolean \| number` | 静态缓存的简写 |
| `prerender` | `boolean` | 启用/禁用预渲染 |
| `isr` | `boolean \| number \| object` | 增量静态再生(Vercel) |
### 运行时路由规则
路由规则可以通过 `runtimeConfig` 提供,允许通过环境变量覆盖而无需重新构建:
```ts [nitro.config.ts]
import { defineConfig } from "nitro";
export default defineConfig({
runtimeConfig: {
nitro: {
routeRules: {
'/api/**': { headers: { 'x-env': 'production' } },
},
},
},
});
```
## 配置参考
这些配置选项控制路由行为:
| 选项 | 类型 | 默认值 | 描述 |
|--------|------|---------|-------------|
| `baseURL` | `string` | `"/"` | 所有路由的基础 URL |
| `apiBaseURL` | `string` | `"/api"` | `api/` 目录中路由的基础 URL |
| `apiDir` | `string` | `"api"` | API 路由的目录名称 |
| `routesDir` | `string` | `"routes"` | 基于文件的路由的目录名称 |
| `serverDir` | `string \| false` | `false` | 用于扫描路由、中间件、插件等的服务器目录 |
| `scanDirs` | `string[]` | `[]` | 扫描路由的额外目录 |
| `routes` | `Record` | `{}` | 路由到处理程序的映射 |
| `handlers` | `NitroEventHandler[]` | `[]` | 程序化处理程序注册(主要用于中间件) |
| `routeRules` | `Record` | `{}` | 匹配模式的路由规则 |
| `ignore` | `string[]` | `[]` | 文件扫描期间忽略的 glob 模式 |
---
# Nitro 服务端入口
> 使用服务端入口处理没有匹配到任何路由的请求
服务端入口是 Nitro 注册为 catch-all(`/**`)路由的特殊处理器。特定路由始终具有优先权,因此服务端入口只会在请求没有匹配到任何路由时、[渲染器](/docs/renderer)运行之前执行。它通常用于在 Nitro 中挂载另一个框架,或为文件系统路由未处理的所有请求实现自定义路由。
::warning
服务端入口是回退机制,而不是全局中间件:对于已由路由处理的请求,它不会运行。对于必须应用于**每个**请求的横切关注点(身份验证、日志记录、请求预处理),请改用[中间件](/docs/routing#middleware)。
::
## 自动检测的 `server.ts`
默认情况下,Nitro 会在 `serverDir`(如果已设置)或项目根目录中自动查找 `server.ts`(或 `.js`、`.mjs`、`.mts`、`.tsx`、`.jsx`)文件。
如果找到,Nitro 将使用它作为服务端入口,并针对所有传入请求运行它。
::code-group
```ts [server.ts]
export default {
async fetch(req: Request) {
const url = new URL(req.url);
// 处理特定路由
if (url.pathname === "/health") {
return new Response("OK", {
status: 200,
headers: { "content-type": "text/plain" }
});
}
// 为所有请求添加自定义请求头
// 不返回内容以继续执行下一个处理器
}
}
```
```ts [routes/api/hello.ts]
import { defineHandler } from "nitro";
export default defineHandler((event) => {
return { hello: "API" };
});
```
::
::tip
当检测到 `server.ts` 时,Nitro 会在终端中输出:``已检测到 `server.ts` 作为服务端入口。``
::
使用此配置:
- `/health` → 由服务端入口处理(返回响应)
- `/api/hello` → 由 API 路由处理器直接处理
- `/about` 等 → 服务端入口先运行,如果没有返回响应,则继续执行到渲染器
## 框架兼容性
服务端入口是与其他框架集成的绝佳方式。任何公开标准 Web `fetch(request: Request): Response` 接口的框架都可以用作服务端入口。
### Web 兼容框架
实现 Web `fetch` API 的框架可以直接与 `server.ts` 配合使用:
::tabs
::tabs-item{label="H3" icon="i-undocs-h3"}
```ts [server.ts]
import { H3 } from "h3";
const app = new H3()
app.get("/", () => "⚡️ 来自 H3 的问候!");
export default app;
```
::
::tabs-item{label="Hono" icon="i-undocs-hono"}
```ts [server.ts]
import { Hono } from "hono";
const app = new Hono();
app.get("/", (c) => c.text("🔥 来自 Hono 的问候!"));
export default app;
```
::
::tabs-item{label="Elysia" icon="i-undocs-elysia"}
```ts [server.ts]
import { Elysia } from "elysia";
const app = new Elysia();
app.get("/", () => "🦊 来自 Elysia 的问候!");
export default app.compile();
```
::
::
### Node.js 框架
对于使用 `(req, res)` 风格处理器(如 [Express](https://expressjs.com/) 或 [Fastify](https://fastify.dev/))的 Node.js 框架,请将服务端入口文件命名为 `server.node.ts` 而不是 `server.ts`。Nitro 将自动检测 `.node.` 后缀,并使用 [`srvx`](https://srvx.h3.dev/) 将 Node.js 处理器转换为兼容 Web 的 fetch 处理器。
::tabs
::tabs-item{label="Express"}
```ts [server.node.ts]
import Express from "express";
const app = Express();
app.use("/", (_req, res) => {
res.send("Hello from Express with Nitro!");
});
export default app;
```
::
::tabs-item{label="Fastify"}
```ts [server.node.ts]
import Fastify from "fastify";
const app = Fastify();
app.get("/", () => "Hello, Fastify with Nitro!");
await app.ready();
export default app.routing;
```
::
::
## 服务端选项
当服务端入口的默认导出为普通对象时,以下服务端选项会传递给由 [Node.js](/deploy/runtimes/node)、[Bun](/deploy/runtimes/bun) 和 [Deno](/deploy/runtimes/deno) 预设启动的 [srvx](https://srvx.h3.dev/) 服务器:`port`、`hostname`、`reusePort`、`protocol`、`tls`、`silent`、`gracefulShutdown`、`maxRequestBodySize`、`trustProxy` 以及运行时特定设置(`node`、`bun`、`deno`)。
使用 `defineServerEntry` 辅助函数获取类型化选项:
```ts [server.ts]
import { defineServerEntry } from "nitro";
export default defineServerEntry({
fetch(req) {
return new Response("Hello from server entry!");
},
port: 8080,
maxRequestBodySize: 1024 * 1024,
});
```
::read-more{to="https://srvx.h3.dev/guide/options" title="srvx server options"}
::
::note
- `NITRO_PORT`/`PORT`、`NITRO_HOST`/`HOST` 和 `NITRO_SSL_CERT`/`NITRO_SSL_KEY` 环境变量的优先级高于 `port`、`hostname` 和 `tls` 选项,因此服务器仍可在运行时进行配置。
- 在开发过程中(`nitro dev`),选项会应用于开发工作进程服务器,但监听器选项(`port`、`hostname`、`protocol`、`tls`、`silent`、`gracefulShutdown`)除外,这些选项由开发服务器和 CLI(`--port`、`--host`)控制。Vite 开发服务器不会应用这些选项。
- 其他 srvx 选项(`middleware`、`plugins`、`error` 和 `manual`)会被忽略。这些选项会改变请求的处理方式,但只适用于这些预设,不适用于无服务器部署或 `nitroApp.fetch`。请改用 [Nitro 中间件](/docs/routing#middleware)和[插件](/docs/plugins)。
- 选项只会从普通对象导出中读取(不会从 `export default app` 这类框架实例中读取),也不会从 Node.js 格式入口(`server.node.ts`)中读取。
::
## 配置
### 自定义服务端入口文件
你可以使用 Nitro 配置中的 `serverEntry` 选项指定自定义服务端入口文件:
```ts [nitro.config.ts]
import { defineConfig } from "nitro";
export default defineConfig({
serverEntry: "./nitro.server.ts"
})
```
你还可以提供一个包含 `handler` 和 `format` 选项的对象:
```ts [nitro.config.ts]
import { defineConfig } from "nitro";
export default defineConfig({
serverEntry: {
handler: "./server.ts",
format: "node" // "web"(默认)或 "node"
}
})
```
### 处理器格式
`format` 选项控制 Nitro 如何处理服务端入口的默认导出:
- **`"web"`**(默认):要求使用具有 `fetch(request: Request): Response` 方法的 Web 兼容处理器
- **`"node"`**:要求使用 Node.js 风格的 `(req, res)` 处理器。Nitro 会自动将其转换为 Web 兼容处理器
自动检测时,格式由文件名决定:`server.node.ts` 使用 `"node"` 格式,而 `server.ts` 使用 `"web"` 格式。
### 禁用服务端入口
将 `serverEntry` 设置为 `false` 以禁用自动检测,并阻止 Nitro 使用任何服务端入口:
```ts [nitro.config.ts]
import { defineConfig } from "nitro";
export default defineConfig({
serverEntry: false
})
```
## 使用事件处理器
你可以导出使用 `defineHandler` 创建的事件处理器,而不是 Web `fetch` 处理器,从而获得更好的类型推断并访问 H3 事件对象:
```ts [server.ts]
import { defineHandler, HTTPError } from "nitro";
export default defineHandler((event) => {
// 仅在请求未匹配到任何路由时运行
if (event.url.pathname.startsWith("/api/")) {
throw new HTTPError("Unknown API endpoint", { status: 404 });
}
// 为渲染器添加上下文
event.context.requestId = crypto.randomUUID();
// 不返回内容以将请求交给渲染器
});
```
::important
返回 `undefined`(或不返回任何内容)会将请求交给[渲染器](/docs/renderer)。如果没有渲染器,Nitro 会返回一个空的 `200` 响应。返回一个值则会在此处结束请求。
::
## 启动逻辑
服务端入口会被预先导入,因此其中的顶层代码会在**服务器启动时运行一次**,而不是在第一个请求到达时运行。
请注意:
- 顶层代码会在[运行时插件](/docs/plugins)**之前**运行,因此插件注册的钩子尚不可用。不要在顶层调用 `useNitroApp()`;应在处理器内部调用它
- 顶层 `await` 会延迟启动:在服务端入口完成求值之前,服务器不会接受请求
- 在无服务器和边缘预设中,“启动”意味着新实例的每次冷启动。某些运行时(例如 Cloudflare Workers)不允许在全局作用域中执行 I/O(例如 `fetch`)
- 在开发过程中,每当服务端入口重新加载时,顶层代码都会再次运行
## 请求生命周期
服务端入口被注册为通配符(`/**`)路由处理器。当特定路由(如 `/api/hello`)匹配到请求时,该路由处理器具有优先权。对于不匹配任何特定路由的请求,服务端入口在渲染器之前运行:
```md
1. Server hook: `request`
2. Route rules (headers, redirects, etc.)
3. Global middleware (static assets first, then middleware/)
4. Route-scoped middleware (handlers config)
5. Route matching:
a. Specific routes (routes/) ← if matched, handles the request
b. Server entry ← runs for unmatched routes
c. Renderer (renderer.ts or index.html)
```
当服务端入口和[渲染器](/docs/renderer)同时存在时,它们会串联执行:服务端入口先运行,如果没有返回响应,则由渲染器处理请求。
:read-more{to="/docs/lifecycle" title="生命周期"}
## 开发模式
在开发过程中,Nitro 会监视服务端入口文件的变更。当文件被创建、修改或删除时,开发服务器会自动重新加载以获取最新更改。
## 最佳实践
- 将服务端入口用作没有匹配到任何路由的请求的回退机制,或用于挂载另一个框架
- 对于必须应用于每个请求(包括已匹配路由)的关注点,请使用[中间件](/docs/routing#middleware)
- 返回 `undefined` 以继续交给渲染器;返回一个值以结束请求
- 保持服务端入口轻量;它会针对每个未匹配的请求运行
- 使用[运行时插件](/docs/plugins)实现一次性初始化逻辑
- 不要将服务端入口用于特定路由逻辑;[路由处理器](/docs/routing)的性能更高。
---
# 缓存
> Nitro 提供了一个构建于[存储层](/docs/storage)之上的缓存系统,由 [ocache](https://ocache.unjs.io/) 提供支持。
缓存可以存储开销较大的操作结果(渲染后的响应、上游 API 调用、复杂计算),并在之后再次提供这些结果,而无需重新执行操作。Nitro 提供了三种使用方式:
- [缓存处理器](#缓存处理器):缓存事件处理器的完整响应。
- [缓存函数](#缓存函数):缓存任意函数的结果,并在多个处理器之间复用。
- [路由规则](#使用路由规则):通过配置为路由模式启用缓存,而无需修改处理器代码。
## 缓存处理器
要缓存事件处理器,请使用 `defineCachedHandler` 方法。
它的工作方式类似于 `defineHandler`,但多了一个用于传入[缓存选项](#选项)的第二个参数。
```ts [routes/cached.ts]
import { defineCachedHandler } from "nitro/cache";
export default defineCachedHandler((event) => {
return "我会被缓存一小时";
}, { maxAge: 60 * 60 });
```
在此示例中,响应会缓存 1 小时。缓存过期后,下一个请求会等待处理器解析出新的值,然后再返回响应。如果你希望在后台重新验证时立即提供过期响应,请设置 `swr: true`(请参阅 [SWR 行为](#swr-行为))。
有关可用选项的更多详情,请参阅[选项](#选项)部分。
::important
`maxAge` 默认为 **1 秒**。对于任何确实希望缓存的内容,请务必显式设置此选项。
::
### 处理器可以看到的内容
对于可缓存的请求,**处理器接收到的请求数据,恰好就是缓存键所涵盖的数据**。在处理器运行前,其他所有内容都会被移除,因此处理器无法根据缓存键无法区分的值来渲染输出,并将其存储在共享键下:
- **请求头**会被剥离,除非列在 [`varies`](#仅适用于处理器的选项) 中。这包括 `if-none-match` / `if-modified-since`(Nitro 会自行处理条件请求)以及 `traceparent` 或 `x-request-id` 等追踪头。
- **查询参数**会被剥离,除非列在 [`allowQuery`](#仅适用于处理器的选项) 中。默认情况下,`/page`、`/page?a=1` 和 `/page?utm=x` 都会共用一个条目,并且处理器内部的 `url.searchParams` 为空。
- **Cookie**会被剥离,除非列在 [`allowCookies`](#仅适用于处理器的选项) 中,并且每个缓存响应中的 `set-cookie` 都会被移除。
- **`authorization` / `proxy-authorization`** 会被剥离,除非设置了 [`allowAuthorization`](#仅适用于处理器的选项)。
- **`host`** 是唯一会被重写而非移除的请求头:它携带已解析请求源的主机名,该主机名属于缓存键的一部分。因此,为多个主机名提供服务的处理器会为每个主机名生成一个条目。
对于必须原样到达处理器的请求,请使用 [`shouldBypassCache`](#通用选项)。绕过缓存的请求会保留其请求头、Cookie、查询字符串和请求体,且其响应不会被存储。
::read-more{to="https://ocache.unjs.io/docs/handler#headers-the-handler-cant-see"}
ocache:处理器无法看到的请求头。
::
### 自动 HTTP 头
使用 `defineCachedHandler` 时,Nitro 会自动管理缓存响应上的 HTTP 缓存头:
- **`etag`**:如果处理器尚未设置,则根据响应体哈希生成弱 ETag(`W/"..."`)。
- **`cache-control`**:根据实际应用于条目的有效期生成,除非处理器已设置此项(请参阅下表)。
- **`vary`**:`varies` 中的每个名称都会合并到响应的 `Vary` 中;使用 `allowCookies` 时也会加入 `Cookie`。`allowQuery` 不会添加任何内容,因为查询参数已经包含在 URL 中。
- **`x-cache`**:类似 CDN 的缓存状态头(`HIT`、`STALE`、`REVALIDATED` 或 `MISS`)。可通过 [`cacheStatusHeader`](#仅适用于处理器的选项) 选项自定义。
| 选项 | 合成的 `cache-control` |
| ------- | --------------------------- |
| `{ maxAge: 60 }` | `max-age=60` |
| `{ maxAge: 60, swr: true }` | `max-age=60, s-maxage=60` |
| `{ maxAge: 60, swr: true, staleMaxAge: 600 }` | `max-age=60, s-maxage=60, stale-while-revalidate=600` |
只有设置了 `staleMaxAge` 时,才会声明 `stale-while-revalidate`,因为该指令需要一个以秒为单位的数值。设置 `swr: true` 而未设置 `staleMaxAge` 时,过期窗口没有限制,因此下游缓存会在 `max-age` 之后重新验证,而 Nitro 会使用其过期副本响应。
要仅在服务器端缓存,而不向客户端和 CDN 声明 `cache-control`,请设置 [`sendCacheControl: false`](#仅适用于处理器的选项)。
::note
**永远不会生成 `last-modified`。** 条目填充的时间并不等于内容更改的时间,因此当处理器知道实际修改时间时,应自行设置此请求头。同样,处理器设置的 `cache-control` 也永远不会被覆盖。
::
### 条件请求(304 Not Modified)
缓存处理器会自动支持条件请求。当客户端发送的 `if-none-match` 与缓存的 `etag` 匹配,或者 `if-modified-since` 的时间等于或晚于处理器设置的 `last-modified` 时,Nitro 会返回不含响应体的 `304 Not Modified` 响应。如果两者都存在,则以 `if-none-match` 为准。
请求验证器会在请求被缩减范围之前捕获,因此**处理器永远不会看到它们**,也无法自行处理条件请求。
### 可缓存的请求
只有 `GET` 和 `HEAD` 请求会被缓存。所有其他 HTTP 方法(`POST`、`PUT`、`DELETE` 等)都会自动绕过缓存并直接调用处理器;携带 `Range` 请求头的请求也会如此。
`GET` 和 `HEAD` 会使用**单独的条目**进行缓存,因为 `HEAD` 响应不包含响应体。即使使用自定义 `getKey`,方法仍然是缓存键的一部分。
### 请求去重
当缓存正在解析时,如果有多个并发请求命中相同的缓存键,只会运行一次处理器调用。所有并发请求都会等待并共享相同的结果。
共享解析受 [`maxResolveTime`](#通用选项) 限制(默认 30 秒)。超时后,所有等待者都会因 `TimeoutError` 而被拒绝,正在刷新的条目会被移除,并且该键可以再次使用。此外,处理器还会通过 `event.req.signal` 收到此截止时间,因此你可以将其传递给上游 `fetch` 调用。
## 缓存函数
你也可以使用 `defineCachedFunction` 缓存函数。这适用于缓存非事件处理器函数的结果,但该函数是事件处理器的一部分,并且需要在多个处理器中复用的情况。
例如,你可能想将 API 调用的结果缓存一小时:
```ts [routes/api/stars/[...repo\\].ts]
import { defineCachedFunction } from "nitro/cache";
import { defineHandler, type H3Event } from "nitro";
export default defineHandler(async (event) => {
const { repo } = event.context.params;
const stars = await cachedGHStars(repo).catch(() => 0)
return { repo, stars }
});
const cachedGHStars = defineCachedFunction(async (repo: string) => {
const data = await fetch(`https://api.github.com/repos/${repo}`).then(res => res.json());
return data.stargazers_count;
}, {
maxAge: 60 * 60,
name: "ghStars",
getKey: (repo: string) => repo
});
```
星标数会使用[缓存存储](#缓存存储)(默认为内存)进行缓存。缓存键由组、名称和 `getKey` 的结果生成,而 `value` 则是星标数。
```json
{"expires":1677851092249,"value":43991,"mtime":1677847492540,"integrity":"ZUHcsxCWEH"}
```
::important
由于缓存数据会被序列化为 JSON,因此缓存函数不能返回无法序列化的内容,例如 Symbols、Maps、Sets……
对于**本身就是**字节的值(`Uint8Array`、`ArrayBuffer` 或任何类型化数组),无需使用钩子即可处理,并且始终会作为 `Uint8Array` 返回。其*存储*方式取决于后端:声明了 `binary` 的后端会原样保存字节,而执行序列化的后端(包括 Nitro 的[默认缓存存储](#缓存存储))会将其存储为 base64。对象中嵌套的字节不会采用此处理方式;对于这类数据结构,请使用 [`serialize`/`transform`](#仅适用于函数的选项)。
::
::note
如果你使用 edge workers 托管应用,应遵循以下说明。
在 edge workers 中,实例会在每个请求完成后销毁。Nitro 会自动使用 `event.waitUntil`,以便在响应发送给客户端的同时,实例仍能保持运行并更新缓存。
为确保缓存函数在 edge workers 中按预期工作,**应始终将 `event` 作为使用 `defineCachedFunction` 定义的函数的第一个参数传入。**
```ts [routes/api/stars/[...repo\\].ts] {5,10,17}
import { defineCachedFunction } from "nitro/cache";
import { defineHandler, type H3Event } from "nitro";
export default defineHandler(async (event) => {
const { repo } = event.context.params;
const stars = await cachedGHStars(event, repo).catch(() => 0)
return { repo, stars }
});
const cachedGHStars = defineCachedFunction(async (event: H3Event, repo: string) => {
const data = await fetch(`https://api.github.com/repos/${repo}`).then(res => res.json());
return data.stargazers_count;
}, {
maxAge: 60 * 60,
name: "ghStars",
getKey: (event: H3Event, repo: string) => repo
});
```
这样,函数就能在更新缓存时保持实例运行,同时不会拖慢返回给客户端的响应。
对于请求不携带 `waitUntil` 的平台,请改用 [`waitUntil` 选项](#通用选项)传递平台钩子。
::
## 选项
`defineCachedHandler` 和 `defineCachedFunction` 函数接受以下选项:
### 通用选项
以下选项可用于 `defineCachedHandler` 和 `defineCachedFunction`:
::field-group
::field{name="base" type="string | string[]"}
用于缓存键的存储基路径(前缀),通常与存储挂载点相匹配。 :br
默认为 `/cache`(存储在 `cache:` 前缀下)。如果提供数组,读取时会按顺序尝试每个基路径,写入时则会写入所有基路径(多层缓存)。
::
::field{name="name" type="string"}
如果未提供,则根据函数名称猜测;否则回退为 `anon_`(根据函数代码计算)。
::
::field{name="group" type="string"}
处理器默认为 `'nitro/handlers'`,函数默认为 `'nitro/functions'`。
::
::field{name="getKey()" type="(...args) => string"}
接受与原始函数相同参数并返回缓存键(`String`)的函数。 :br
如果未提供,则会使用内置哈希函数,根据函数参数生成键。对于缓存处理器,键由请求源、路径和方法生成。 :br :br
自定义 `getKey` 会替代生成的键,但**不会**替代请求范围缩减:处理器可以看到哪些内容,仍由 `allowQuery`、`allowCookies` 和 `varies` 决定。
::
::field{name="integrity" type="string"}
更改时会使缓存失效的值。 :br
默认情况下,它根据**函数代码和选项**计算,因此更改其中任一项都会静默忽略旧条目,而不是提供旧条目。
::
::field{name="maxAge" type="number"}
缓存有效的最长时间,以秒为单位。 :br
默认为 `1`(秒)。显式设置为 `0` 会禁用该处理器或函数的缓存;省略或设为 `undefined` 时,则回退到默认值。
::
::field{name="staleMaxAge" type="number"}
在 `maxAge` 过期后,后台重新验证期间仍可提供过期值的最长时间,以秒为单位。仅在启用 `swr` 时适用。 :br
设置为 `0` 时,永远不会提供过期值(等同于禁用 `swr`)。未设置时,过期值可以无限期提供,并且**不会写入存储 TTL**,因此条目会一直保留,直到后端将其清除。
::
::field{name="swr" type="boolean"}
启用 `stale-while-revalidate` 行为,在异步重新验证的同时提供过期的缓存值。 :br
启用后,过期的缓存值会立即返回,同时在后台进行重新验证。禁用后,调用方会在前台等待值重新验证完成。 :br
默认为 `false`。请参阅 [SWR 行为](#swr-行为)。
::
::field{name="getMaxAge()" type="(entry: CacheEntry) => number | { maxAge?, staleMaxAge? }"}
根据解析后的值为每个条目推导缓存有效期,并覆盖该条目的静态 `maxAge` / `staleMaxAge` 选项。返回以秒为单位的数值(`maxAge` 的简写),或返回可同时覆盖 `staleMaxAge` 的对象。 :br
适用于携带自身过期时间的值,例如访问令牌:`getMaxAge: (entry) => entry.value.expires_in`。解析后的值 `<= 0` 时,会禁用该条目的缓存。对于处理器,`entry.value` 是 `Response`,合成的 `cache-control` 会遵循返回的有效期。
::
::field{name="maxResolveTime" type="number"}
一次共享解析的截止时间,以秒为单位。默认为 `30`。 :br
超时后,所有等待的调用方都会因 `TimeoutError` 而被拒绝,并且正在刷新的条目会被移除。设置为 `Infinity` 或 `0` 可禁用此限制;对于确实运行缓慢的解析器,请调高此值。
::
::field{name="storage" type="StorageInterface | () => StorageInterface"}
覆盖此处理器或函数所使用的后端。 :br
默认为 Nitro 的[缓存存储](#缓存存储),它是 Nitro 存储层中 `cache:` 前缀的适配器。
::
::field{name="waitUntil()" type="(promise: Promise) => void"}
将后台工作(缓存写入、SWR 刷新、清除操作)交给宿主运行时处理。 :br
优先级高于 `event.req.waitUntil`。仅在请求不携带此方法的无服务器平台上需要使用。
::
::field{name="shouldInvalidateCache()" type="(...args) => boolean | Promise"}
返回 `boolean` 的函数,用于使当前缓存失效并创建新缓存。
::
::field{name="shouldBypassCache()" type="(...args) => boolean | Promise"}
返回 `boolean` 的函数,用于绕过当前缓存但不使现有条目失效。 :br
对于处理器,绕过缓存的请求会以**未更改的状态**到达处理器(包括凭据、Cookie 和完整查询字符串),其响应既不会被存储,也不会带有缓存头。
::
::field{name="onError()" type="(error: unknown) => void"}
缓存函数抛出错误时调用的自定义错误处理器。 :br
默认情况下,错误会记录到控制台,并由 Nitro 错误处理器捕获。
::
::
### 仅适用于处理器的选项
以下选项仅适用于 `defineCachedHandler`:
::field-group
::field{name="headersOnly" type="boolean"}
为 `true` 时,跳过完整响应缓存,仅处理条件请求头(`if-none-match`、`if-modified-since`),用于返回 `304 Not Modified` 响应。每个请求都会调用处理器,并以处理器自身设置的 `etag` / `last-modified` 作为条件,因此处理器必须自行设置这些值。此模式下不会生成 `x-cache` 请求头。
::
::field{name="varies" type="string[]"}
作为缓存键变化依据的请求头名称数组。此处列出的请求头在缓存解析期间会保留在请求中,并纳入缓存键,使缓存按请求头值的不同组合进行区分。它们也会合并到响应的 `Vary` 请求头中。 :br :br
未列在 `varies` 中的请求头会在调用处理器之前从请求中剥离,以确保缓存命中一致。 :br :br
如果响应自身的 `Vary` 列出了键中未包含的请求头,则该响应会返回,但**不会被存储**,因此请确保两个列表保持同步。 :br :br
多租户配置现在无需在此处添加 `['host']`:请求源已经是生成的键的一部分。只有当处理器在代理后会读取 `x-forwarded-host` 时,才需将其添加进来。
::
::field{name="allowQuery" type="boolean | string[]"}
允许作为缓存键变化依据的查询参数名称白名单。**默认情况下,任何查询参数都不会影响缓存键,也不会传递给处理器**,因此 `/page?a=1` 和 `/page?utm=x` 会共用一个条目,并且处理器内部的 `url.searchParams` 为空。 :br :br
设置数组可指定允许的名称(区分大小写,不区分顺序);设置为 `true` 则会根据完整查询字符串生成键,适用于参数无法预先确定的代理和搜索端点。
::
::field{name="allowCookies" type="string[]"}
参与缓存的 Cookie 名称白名单。列出的 Cookie 会影响缓存键,并保留在处理器所见的 `cookie` 请求头中。 :br :br
**默认情况下不允许任何 Cookie**:处理器运行前会剥离 `cookie` 请求头,并且响应中的任何 `set-cookie` 请求头都会在缓存或返回前被丢弃,因此每个用户专属的 Cookie(例如会话 ID)永远不会通过缓存泄漏给其他用户。只将其值可安全地在所有命中同一缓存键的用户之间共享的 Cookie 加入白名单(例如 `theme` 或 `locale` 偏好),切勿加入用户专属的机密信息。 :br :br
设置此选项会生成 `Vary: Cookie`,这可能会降低 CDN 和共享代理的命中率。可以考虑改用 `allowQuery` 将选择放入 URL,或与 `sendCacheControl: false` 配合使用。
::
::field{name="allowAuthorization" type="boolean"}
允许 `authorization` 和 `proxy-authorization` 到达处理器,并使缓存键随其变化。默认为 `false`,此时可缓存请求中的这两个请求头都会被剥离。 :br :br
发送相同凭据值的所有客户端会共用一个条目。如果响应绝不能共享,请改用 `shouldBypassCache`。
::
::field{name="sendCacheControl" type="boolean"}
是否自动设置 `cache-control` 响应头。默认为 `true`。 :br
设置为 `false` 可仅在服务器端缓存:响应仍会存储并从缓存提供,但不会向客户端和 CDN 声明 `cache-control`。此设置不会生成 `no-store`,因此下游缓存仍可能根据自身启发式规则存储响应。
::
::field{name="cacheStatusHeader" type="boolean | string"}
添加缓存状态响应头(`X-Cache: HIT | STALE | REVALIDATED | MISS`)。默认为 `true`。传入字符串可使用自定义请求头名称,传入 `false` 可禁用。
::
::field{name="stream" type="boolean"}
直接流式传输用于填充缓存条目的响应,而不是先进行缓冲。默认为 `false`。 :br
这可以改善流式渲染的首字节时间,但会导致无法生成合成的 `etag`(摘要需要完整的响应体),并且会影响响应体中途出错时的恢复。任何不完整内容都不会被存储,后续请求仍会像往常一样使用已存储的条目。
::
::field{name="maxBodySize" type="number"}
可缓冲并用于存储的最大响应体大小,以字节为单位。更大的响应会像绕过缓存的请求一样直接流式传输,不会被缓存。 :br
Nitro 的默认存储未声明单个条目的大小上限,因此**默认没有限制**。对于代理大小不受你控制的上游响应的任何路由,请设置此选项。
::
::field{name="shouldCache()" type="(entry: ResponseCacheEntry) => boolean | Promise"}
在[内置检查](#哪些内容会被缓存)的基础上,额外判断响应是否可缓存(内置检查始终适用,且只能进一步收窄缓存范围)。返回 `false` 可跳过响应缓存;响应仍会返回给调用方,只是不予存储。 :br
此函数在读取和写入时都会运行,可以是异步函数,并且会以安全失败的方式处理:钩子抛出错误时会将响应视为“不可缓存”,并交由 `onError` 处理。
::
::
### 仅适用于函数的选项
以下选项仅适用于 `defineCachedFunction`:
::field-group
::field{name="transform()" type="(entry: CacheEntry, ...args) => any"}
在返回缓存条目之前对其进行转换。返回值会替代缓存值。 :br
条目还会携带每次调用对应的 `entry.status`(`"hit"`、`"stale"`、`"revalidated"` 或 `"miss"`),用于说明值的提供方式,适用于指标统计或条件逻辑。
::
::field{name="serialize()" type="(entry: CacheEntry, ctx: { args }) => any"}
在将条目写入存储之前,准备好已解析的值(对应于 `transform` 的写入侧操作)。适用于函数返回无法原样存储的内容(例如流或类实例)的情况:`serialize` 会在写入时转换数据,而 `transform` 会在读取时恢复数据。 :br
每次解析只会运行一次,且去重调用会共享该结果,因此可以安全地读取流等只能使用一次的数据源。
::
::field{name="validate()" type="(entry: CacheEntry, ctx: { args }) => boolean | Promise"}
验证缓存条目。返回 `false`(或解析为 `false` 的 Promise)会将条目视为无效并触发重新解析。第二个参数包含当前调用的 `args`,因此可以根据这些参数验证条目。
::
::
## SWR 行为
`stale-while-revalidate`(SWR)模式需要通过 `swr` 选项**显式启用**(默认禁用)。了解它与其他选项的交互方式:
| `swr` | `maxAge` | 行为 |
|-------|----------|----------|
| `false`(默认) | `3600` | 缓存 1 小时;过期时等待新值 |
| `true` | `3600` | 缓存 1 小时;重新验证时提供过期值 |
| `true` | `3600`,且设置 `staleMaxAge: 600` | 缓存 1 小时;重新验证时最多再提供 10 分钟的过期值 |
| `true` | `3600`,且设置 `staleMaxAge: 0` | 缓存 1 小时;永远不提供过期值(等同于 `swr: false`) |
启用 `swr` 且存在已过期的缓存值时:
1. 立即将过期的缓存值返回给客户端。
2. 在后台调用函数或处理器以刷新缓存。
3. 在 edge workers 上,使用 `event.waitUntil` 保持后台刷新继续运行。
禁用 `swr`(默认)且缓存值已过期时:
1. 客户端会等待函数或处理器解析出新值。
2. 调用返回前会替换该条目(状态为 `REVALIDATED`)。
::tip
当允许数据略微过期时(例如内容页面、列表、第三方 API 镜像),请启用 `swr`,这样即使缓存过期,响应时间也能保持稳定。对于调用方绝不能收到过期数据的情况(例如访问令牌或逐请求授权检查),请保持禁用。
::
::warning
设置 `swr: true` 且**未设置** `staleMaxAge` 时,条目不会携带存储 TTL:最后一个有效值会一直提供,直到后端将其清除。这正是 ISR 模式,但也意味着缓存增长受后端容量而非时间限制。请设置 `staleMaxAge`,以便最终清理条目。
::
## 缓存键
使用 `defineCachedFunction` 或 `defineCachedHandler` 函数时,缓存键会按照以下模式生成:
```ts
`${options.base}:${options.group}:${options.name}:${options.getKey(...args)}.json`
```
`group` 和 `name` 在进入键之前会经过**转义**:移除 `[A-Za-z0-9_]` 之外的字符;如果值因此发生变化,则会附加原始值的哈希。这就是为什么 Nitro 的 `nitro/functions` 组会显示为 `nitrofunctions.`,并且不会与 `:` 键结构混淆。`getKey` 的结果是最后一个片段,并且会按原样存储。
例如,以下函数:
```ts
import { defineCachedFunction } from "nitro/cache";
const getAccessToken = defineCachedFunction(() => {
return String(Date.now())
}, {
maxAge: 10,
name: "getAccessToken",
getKey: () => "default"
});
```
将存储在类似以下的键下:
```ts
cache:nitrofunctions.3aFYDY_G88ZLdElv-hEB0r8snSViMYJ1NbRtlNP9o_0:getAccessToken:default.json
```
默认的 `base` 为 `/cache`,存储层会将其规范化为 `cache:` 前缀(与处理键中其他 `/` 的方式相同)。
::note
对于缓存处理器,生成的键涵盖请求的**源**(协议、主机和端口)、URL 路径和方法,以及 [`varies`](#仅适用于处理器的选项)、[`allowQuery`](#仅适用于处理器的选项) 和 [`allowCookies`](#仅适用于处理器的选项) 对应值的哈希。`GET` 和 `HEAD` 会分别存储。
::
::important
如果未提供 `name`,则会回退为**函数源码**的哈希;这种方式无法区分由工厂或循环生成的处理器或函数:它们的源码相同,但闭包变量不同,因此会共享同一个键并相互覆盖条目。对于这类情况,请务必显式传入 `name`(或 `getKey`)。
::
::note
如果未提供 `getKey`,参数会被哈希。不透明值(`Blob`、`ReadableStream`、`Promise`、`Request`、`WeakMap`)只会贡献其类型,因此两个不同的值也会共用一个条目;嵌套深度超过 128 层的参数会抛出 `RangeError`。出现上述任一情况时,请传入一个只读取结果所依赖字段的 `getKey`。
::
### 哪些内容会被缓存
存储响应之前,会进行以下检查。未通过任一检查的响应仍会返回给调用方,只是永远不会被存储或从缓存提供:
| 检查 | 原因 |
| ----- | ------ |
| 状态不是 `200`、`203`、`301` 或 `308` | 只有这些状态对应完整、可复用的表示。错误、`204`/`304`、`206` 范围响应和临时重定向都会被排除。 |
| 处理器通过 `cache-control` 选择退出 | 包括 `no-store`、`private`、`no-cache`,或共享有效期为零(如果存在 `s-maxage` 则使用它,否则使用 `max-age`)。 |
| `Vary: *` | 除 `no-store` 之外最强的“禁止共享”信号。 |
| `Vary` 中指定了缓存键之外的请求头 | 存储此响应会将某个变体重放给所有客户端。请将该请求头添加到 [`varies`](#仅适用于处理器的选项) 中。 |
| 缺少响应体 | 没有可供重放的内容。空的(零字节)`200` 响应是允许的,并会正常缓存。 |
| `etag` 或 `last-modified` 等于字符串字面值 `"undefined"` | 这是将缺失值转换为字符串后通常产生的结果,会破坏条件请求。 |
这些检查始终适用。使用 [`shouldCache`](#仅适用于处理器的选项) 可以进一步收窄缓存范围。内置检查拒绝的响应也**不会**获得合成的 `cache-control`,因此前置 CDN 无法继续缓存错误;而被你自己的 `shouldCache` 拒绝的响应仍会获得该请求头。
::note
`cache-control: must-revalidate` 不会选择退出缓存。响应会被存储,并在新鲜时提供,但过期后永远不会提供,因此仅对该响应禁用 SWR。
::
::read-more{to="https://ocache.unjs.io/docs/cache-control"}
ocache:完整了解 `cache-control` 和缓存资格。
::
## 缓存失效
可以在运行时以编程方式使缓存条目失效(例如,在底层数据更改时通过 webhook 触发),而无需等待 `maxAge` 过期。
### `.invalidate()` 和 `.expire()` 方法
通过 `defineCachedFunction` 创建的每个函数都会公开按需重新验证方法:
- **`.invalidate(...args)`**:完全移除缓存条目。下次调用时会重新调用函数,并等待新值。
- **`.expire(...args)`**:将缓存条目标记为过期,但不移除它。启用 `swr` 时,仍会提供过期值,而下次调用会触发后台刷新;未启用 SWR 时,下次调用会在返回前重新解析。
- **`.resolveKeys(...args)`**:解析条目所缓存到的存储键(每个 `base` 前缀对应一个)。
参数会传递给 `getKey`,以生成缓存键。
```ts
import { defineCachedFunction } from "nitro/cache";
const cachedGHStars = defineCachedFunction(async (repo: string) => {
const data = await fetch(`https://api.github.com/repos/${repo}`).then(res => res.json());
return data.stargazers_count;
}, {
maxAge: 60 * 60,
name: "ghStars",
getKey: (repo: string) => repo,
});
await cachedGHStars("unjs/nitro"); // populates the cache
await cachedGHStars.expire("unjs/nitro"); // marks the entry as stale
await cachedGHStars.invalidate("unjs/nitro"); // removes the entry
await cachedGHStars("unjs/nitro"); // re-invokes the function
```
如果没有缓存条目与给定参数匹配,`.invalidate()` 和 `.expire()` 会正常完成且不报错,并且不会更改存储。两者也会取消该键上正在进行的解析,因此清除前的值不会在清除后写入。
::note
Nitro 的 `defineCachedHandler` 目前会返回普通事件处理器,并且不会公开这些方法,因此无法按需使缓存路由失效。如果需要此功能,请将开销较大的操作移入由普通 `defineHandler` 调用的 `defineCachedFunction`,然后使该函数失效。
::
## 缓存存储
缓存条目使用[存储层](/docs/storage)存储在 `cache:` 前缀下(该前缀源自默认的 `base: "/cache"` 选项)。
默认情况下,没有为其配置专用挂载点:条目存储在根存储中,该存储使用内存驱动程序,并且在开发和生产环境中**不会跨重启持久化**。
要使用持久化后端,请通过 `kv` 选项设置 `cache` 挂载点:
```ts [nitro.config.ts]
import { defineConfig } from "nitro";
export default defineConfig({
kv: {
cache: {
driver: 'redis',
/* redis 连接器选项 */
}
}
})
```
在开发环境中,你也可以使用 `$development` 配置键覆盖缓存挂载点:
```ts [nitro.config.ts]
import { defineConfig } from "nitro";
export default defineConfig({
kv: {
cache: {
// 生产环境缓存存储
},
},
$development: {
kv: {
cache: {
// development cache storage
}
}
}
})
```
若要在预渲染时也覆盖它,请同时在 `$prerender` 中设置。
::note
条目会通过 unstorage 序列化为 JSON,因此 Nitro 的默认缓存存储不会声明 `binary`。可解码为 UTF-8 的响应体(HTML、JSON、纯文本)无论如何都会以文本形式存储;只有**不是**有效 UTF-8 的响应体(图片、字体、PDF)才会进行 base64 编码,字节值的缓存函数也会如此。传入你自己的、声明了 `binary` 的 [`storage`](#通用选项)(例如基于驱动程序的 `getItemRaw` / `setItemRaw` 创建 ocache 的 `createBlobStorage`),即可将这些内容保留为字节,并跳过编码、解码和 4/3 倍膨胀。 :br :br
默认后端也不会声明单个条目的大小上限,因此缓存处理器会缓冲任意大小的响应体,除非你设置了 [`maxBodySize`](#仅适用于处理器的选项)。
::
::read-more{to="https://ocache.unjs.io/docs/storage"}
ocache:了解存储接口、二进制数据和分层后端。
::
::read-more{to="/docs/storage"}
进一步了解 Nitro 存储层和可用驱动程序。
::
## 使用路由规则
路由规则允许你直接通过配置,为匹配 glob 模式的所有路由启用缓存。对于应用中某个部分实施全局缓存策略,而无需修改处理器代码时,这尤其有用。
将所有博客路由缓存 1 小时:
```ts [nitro.config.ts]
import { defineConfig } from "nitro";
export default defineConfig({
routeRules: {
"/blog/**": { cache: { maxAge: 60 * 60 } },
},
});
```
`cache` 规则接受上文记录的相同[处理器选项](#选项)(`swr`、`staleMaxAge`、`varies`、`allowQuery`、`allowCookies`、`allowAuthorization`、`sendCacheControl`、`cacheStatusHeader`、`stream`、`maxBodySize`、`headersOnly`、`base`、`group`、`name`、`integrity`、`maxResolveTime`)。`shouldCache` 等钩子选项仅适用于 `defineCachedHandler`。
如果要使用[自定义缓存存储](#缓存存储)挂载点,可以使用 `base` 选项。
```ts [nitro.config.ts]
import { defineConfig } from "nitro";
export default defineConfig({
kv: {
redis: {
driver: "redis",
url: "redis://localhost:6379",
},
},
routeRules: {
"/blog/**": { cache: { maxAge: 60 * 60, base: "redis" } },
},
});
```
### 路由规则快捷方式
你可以使用 `swr` 快捷方式为路由规则启用 `stale-while-revalidate` 缓存。`swr: 3600` 是 `cache: { swr: true, maxAge: 3600 }` 的简写。设置为 `true` 时,SWR 会使用默认的 `maxAge` **1 秒**,因此建议传入明确的数值。
```ts [nitro.config.ts]
import { defineConfig } from "nitro";
export default defineConfig({
routeRules: {
"/blog/**": { swr: 3600 },
"/api/**": { swr: 60 },
},
});
```
要显式禁用某个路由的缓存,请在更具体的模式上设置 `cache: false`(或 `swr: false`)。这也会移除从范围更广的模式继承的 `cache` 规则:
```ts [nitro.config.ts]
import { defineConfig } from "nitro";
export default defineConfig({
routeRules: {
"/api/**": { swr: 60 },
"/api/realtime/**": { cache: false },
},
});
```
::note
使用路由规则时,缓存处理器会使用组 `'nitro/route-rules'`,而不是默认的 `'nitro/handlers'`;生成的 `name` 会将条目限定到匹配的路由处理器、HTTP 方法、规则模式和匹配的路由。
::
::warning
生成的范围在每个进程中都是唯一的。对于**持久化**缓存存储,重启或启动第二个工作进程都会从全新的条目开始,而不会复用已存储的条目。如果路由规则缓存条目需要跨进程共享,请在规则上设置显式的 `name`。
::
::read-more{to="/docs/routing#route-rules"}
路由规则的用途远不止缓存:还包括请求头、重定向、代理等。
::
---
# KV 存储
> Nitro 提供了一个内置存储层,可以抽象文件系统、数据库或任何其他数据源。
Nitro 内置了与 [unstorage](https://unstorage.unjs.io) 的集成,以提供与运行时无关的持久化层:你的代码始终调用相同的键值 API,而底层驱动(内存、文件系统、Redis 等)则通过配置进行切换。
## 用法
要使用存储层,可以使用 `useKV()` 工具函数访问存储实例。
```ts
import { useKV } from "nitro/kv";
// Default storage (in-memory)
await useKV().setItem("test:foo", { hello: "world" });
const value = await useKV().getItem("test:foo");
// You can specify a base prefix with useKV(base)
const testStorage = useKV("test");
await testStorage.setItem("foo", { hello: "world" });
await testStorage.getItem("foo"); // { hello: "world" }
// You can use generics to type the return value
await useKV<{ hello: string }>("test").getItem("foo");
await useKV("test").getItem<{ hello: string }>("foo");
```
:read-more{to="https://unstorage.unjs.io"}
::note
`useKV` 之前名为 `useStorage`,并从 `nitro/storage` 导出;`kv` 配置选项之前名为 `storage`。旧名称仍可作为已弃用的别名使用。已弃用的 `devStorage` 选项已被 `$development`(以及 `$prerender`)中的 `kv` 替代。
::
### 可用方法
`useKV()` 返回的存储实例提供以下方法:
| 方法 | 描述 |
|---|---|
| `getItem(key)` | 获取键的值。如果键不存在则返回 `null`。 |
| `getItems(items)` | 一次获取多个项目。接受键数组或 `{ key, options }` 对象。 |
| `getItemRaw(key)` | 获取键的原始值,不进行解析。适用于二进制数据。 |
| `setItem(key, value)` | 设置键的值。 |
| `setItems(items)` | 一次设置多个项目。接受 `{ key, value }` 对象数组。 |
| `setItemRaw(key, value)` | 设置键的原始值,不进行序列化。 |
| `hasItem(key)` | 检查键是否存在。返回布尔值。 |
| `removeItem(key)` | 从存储中移除键。 |
| `getKeys(base?)` | 获取所有键,可选按基础前缀过滤。 |
| `clear(base?)` | 清除所有键,可选按基础前缀过滤。 |
| `getMeta(key)` | 获取键的元数据(例如 `mtime`、`atime`、`ttl`)。 |
| `setMeta(key, meta)` | 设置键的元数据。 |
| `removeMeta(key)` | 移除键的元数据。 |
| `mount(base, driver)` | 在基础路径动态挂载存储驱动。 |
| `unmount(base)` | 从基础路径卸载存储驱动。 |
| `watch(callback)` | 监视变更。回调接收 `(event, key)`,其中 event 为 `"update"` 或 `"remove"`。 |
| `unwatch()` | 停止监视变更。 |
还提供了简写别名:`get`、`set`、`has`、`del`、`remove`、`keys`。
```ts
import { useKV } from "nitro/kv";
// Get all keys under a prefix
const keys = await useKV("test").getKeys();
// Check if a key exists
const exists = await useKV().hasItem("test:foo");
// Remove a key
await useKV().removeItem("test:foo");
// Get raw binary data
const raw = await useKV().getItemRaw("assets:server:image.png");
// Get metadata (type, etag, mtime, etc.)
const meta = await useKV("assets:server").getMeta("file.txt");
```
## 配置
你可以使用 `kv` 选项挂载一个或多个自定义存储驱动。
键是挂载点名称,值是驱动名称和配置。
```ts [nitro.config.ts]
import { defineConfig } from "nitro";
export default defineConfig({
kv: {
redis: {
driver: "redis",
/* redis 连接器选项 */
}
}
})
```
然后,你可以使用 `useKV("redis")` 函数来使用 Redis 存储。
::read-more{to="https://unstorage.unjs.io/"}
你可以在 [unstorage 文档](https://unstorage.unjs.io/) 上找到驱动列表及其配置。
::
::important
挂载选项会被序列化到构建输出中,因此必须是可进行 JSON 序列化的。如果挂载需要不可序列化的选项,请使用[运行时配置](#runtime-configuration)。
::
### 驱动依赖
某些驱动依赖第三方库(例如,`redis` 需要 [`ioredis`](https://www.npmjs.com/package/ioredis))。
Nitro 会检测挂载驱动所需的库,并提示安装缺失的库(在 CI 中会自动安装)。安装的库随后会通过其 `lib` 选项显式传递给驱动,以便打包器能够静态解析它们。因此,`lib` 选项无法在 `kv` 挂载中配置(将其设置为 `null` 可选择不注入导入)。
### 开发存储
你可以使用 `$development` 配置键在开发期间覆盖 `kv` 挂载。
当你的生产驱动在开发环境中不可用时(例如托管的 Redis 实例),这很有用。
```ts [nitro.config.ts]
import { defineConfig } from "nitro";
export default defineConfig({
kv: {
db: {
driver: "redis",
host: "prod.example.com",
}
},
$development: {
kv: {
db: {
driver: "fs",
base: "./.data/db"
}
}
}
})
```
在开发模式下运行时,`$development.kv` 挂载会合并到 `kv` 挂载之上,让你在开发时可以使用本地文件系统驱动或内存驱动。使用不同 `driver` 的挂载会替换整个挂载(上例中的 `host` 不会传递给 `fs` 驱动)。使用相同 `driver` 的挂载则会深度合并其选项。
`$development` 仅适用于开发服务器。在[预渲染](/docs/prerendering)生产构建时,则会依次应用 `$production` 和 `$prerender` 覆盖项。使用 `$prerender` 仅在预渲染期间覆盖挂载:
```ts [nitro.config.ts]
import { defineConfig } from "nitro";
export default defineConfig({
kv: {
db: { driver: "redis", host: "prod.example.com" }
},
$development: {
kv: { db: { driver: "fs", base: "./.data/db" } }
},
$prerender: {
kv: { db: { driver: "fs", base: "./.data/db" } }
}
})
```
::tip
一种常见配置是在生产环境的 `kv` 中使用托管驱动(例如 `redis`),并在 `$development.kv` 中使用 `fs` 驱动,这样开发数据便于在本地 `.data/` 中检查,也无需依赖外部服务。
::
## 内置挂载点
Nitro 自动挂载以下存储路径:
### `/assets`
服务器资源挂载在 `/assets` 基础路径下(参见[服务器资源](#server-assets))。在生产环境中,此挂载为只读,并且仅支持 `getKeys()`、`hasItem()`、`getItem()`、`getItemRaw()` 和 `getMeta()`。
```ts
import { useKV } from "nitro/kv";
// Access server assets via the /assets mount
const content = await useKV("assets:server").getItem("my-file.txt");
```
### 默认(内存中)
根存储(无基础路径)默认使用内存驱动。此处存储的数据在重启后不会持久化。
```ts
import { useKV } from "nitro/kv";
// In-memory by default, not persisted
await useKV().setItem("counter", 1);
```
要持久化数据,请使用 `kv` 配置选项挂载具有持久化后端的驱动(例如 `fs`、`redis` 等)。
::note
[缓存层](/docs/cache)也会将其条目存储在此根存储中(位于 `cache:` 前缀下),除非你配置了专用的 `cache` 挂载点,因此默认情况下,缓存数据在重启后也不会持久化。
::
## 服务器资源
Nitro 允许你从 `assets/` 目录中捆绑文件。这些文件可以在运行时通过 `assets:server` 存储挂载访问。
当设置了 [`serverDir`](/config#serverdir) 时,目录相对于该目录解析;否则相对于项目根目录解析:
```
my-project/
server/
assets/
data.json
templates/
welcome.html
routes/
index.ts
```
```ts [server/routes/index.ts]
import { defineHandler } from "nitro";
import { useKV } from "nitro/kv";
export default defineHandler(async () => {
const serverAssets = useKV("assets:server");
const keys = await serverAssets.getKeys();
const data = await serverAssets.getItem("data.json");
const template = await serverAssets.getItem("templates/welcome.html");
return { keys, data, template };
});
```
### 自定义资源目录
你可以使用 `serverAssets` 配置选项注册其他资源目录。每个 `dir` 都相对于 `rootDir`(而不是 `serverDir`)解析:
```ts [nitro.config.ts]
import { defineConfig } from "nitro";
export default defineConfig({
serverAssets: [
{
baseName: "templates",
dir: "./templates",
}
]
})
```
自定义资源目录可通过 `assets:` 访问:
```ts
import { useKV } from "nitro/kv";
const templates = useKV("assets:templates");
const keys = await templates.getKeys();
const html = await templates.getItem("email.html");
```
### 资源元数据
服务器资源包含元数据,例如内容类型、ETag 和修改时间:
```ts
import { useKV } from "nitro/kv";
const serverAssets = useKV("assets:server");
const meta = await serverAssets.getMeta("image.png");
// { type: "image/png", etag: "\"...\"", mtime: "2024-01-01T00:00:00.000Z" }
// 适用于设置响应头
const raw = await serverAssets.getItemRaw("image.png");
```
::note
在开发环境中(以及预渲染期间),服务器资源会直接使用 `fs` 驱动从文件系统读取,因此 `getMeta()` 返回的是文件系统统计信息(`mtime`、`size` 等),而不是 `type` 和 `etag`。在生产环境中,资源会被捆绑到构建输出中,其元数据会在构建时预先计算。
::
::read-more{to="/docs/assets"}
详细了解公共资源和服务器资源。
::
## 运行时配置
在挂载点配置直到运行时才知道的场景中,Nitro 可以使用[插件](/docs/plugins)在启动期间动态添加挂载点。
```ts [plugins/storage.ts]
import { useKV } from "nitro/kv";
import { definePlugin } from "nitro";
import redisDriver from "unstorage/drivers/redis";
export default definePlugin(() => {
const storage = useKV()
// Dynamically pass credentials from runtime config or elsewhere
const driver = redisDriver({
base: "redis",
host: process.env.REDIS_HOST,
port: Number(process.env.REDIS_PORT),
// Pass the driver library explicitly so that it is bundled
lib: () => import("ioredis"),
/* other redis connector options */
})
// Mount the driver
storage.mount("redis", driver)
})
```
::important
以这种方式挂载的驱动不在 Nitro 的[驱动依赖检测](#driver-dependencies)范围内:请自行安装第三方库(上例中的 `ioredis`),并通过驱动的 `lib` 选项传入。如果不传入,驱动会回退到动态 `import("ioredis")`,而打包器无法静态解析该导入,导致挂载在运行时失败,并出现 `Cannot import ioredis` 错误。
::
---
# 资源
Nitro 支持三种类型的资源:直接提供给客户端的**公共资源**、内联到服务器包中的**导入文件**,以及打包到服务器中以供编程访问的**服务器资源**。
## 公共资源
Nitro 通过 `public/` 目录处理资源。
`public/` 目录中的所有资源将被自动提供。这意味着您可以直接从浏览器访问它们,无需任何特殊配置。
```md
public/
image.png <-- /image.png
video.mp4 <-- /video.mp4
robots.txt <-- /robots.txt
```
### 缓存和请求头
公共资源会自动携带 `ETag` 和 `Last-Modified` 响应头,以支持条件请求。当客户端发送 `If-None-Match` 或 `If-Modified-Since` 请求头时,Nitro 会返回 `304 Not Modified` 响应。
对于从非根目录 `baseURL`(如 `/build/`)提供的资源,Nitro 会阻止请求穿透到应用处理器。如果请求匹配了公共资源基础路径但文件未找到,将立即返回 `404`。
### 生产环境公共资源
构建 Nitro 应用时,`public/` 目录将被复制到 `.output/public/`,同时会创建一个包含元数据的清单文件并嵌入到服务器包中。
```json
{
"/image.png": {
"type": "image/png",
"etag": "\"4a0c-6utWq0Kbk5OqDmksYCa9XV8irnM\"",
"mtime": "2023-03-04T21:39:45.086Z",
"size": 18956
},
"/robots.txt": {
"type": "text/plain; charset=utf-8",
"etag": "\"8-hMqyDrA8fJ0R904zgEPs3L55Jls\"",
"mtime": "2023-03-04T21:39:45.086Z",
"size": 8
},
"/video.mp4": {
"type": "video/mp4",
"etag": "\"9b943-4UwfQXKUjPCesGPr6J5j7GzNYGU\"",
"mtime": "2023-03-04T21:39:45.085Z",
"size": 637251
}
}
```
这使得 Nitro 无需扫描目录即可了解公共资源,配合缓存响应头提供高性能。
### 自定义公共资源目录
您可以使用 `publicAssets` 配置选项配置额外的公共资源目录。每个条目支持以下属性:
- `dir` -- 目录路径(相对于 `rootDir` 解析)
- `baseURL` -- 用于提供资源的 URL 前缀(默认值:`"/"`)
- `maxAge` -- 以秒为单位的缓存 `max-age`。设置后,将通过路由规则应用 `Cache-Control: public, max-age=, immutable` 响应头。仅当 `fallthrough` 为 `false` 时生效(非根目录的默认值)。
- `fallthrough` -- 当资源未找到时,请求是否应继续交由应用处理器处理。顶层(`baseURL: "/"`)目录默认为 `true`;非根目录默认为 `false`
- `ignore` -- 传入 `false` 以禁用忽略模式,或传入 glob 模式数组以覆盖全局 `ignore` 选项
```js [nitro.config.ts]
import { defineConfig } from "nitro";
export default defineConfig({
publicAssets: [
{
baseURL: "build",
dir: "public/build",
maxAge: 3600,
},
],
});
```
在此示例中,`public/build/` 目录下的文件在 `/build/` 路径下提供,具有一小时缓存且不会穿透到应用处理器。
### 压缩公共资源
Nitro 可以在构建期间生成公共资源的预压缩版本。当客户端发送 `Accept-Encoding` 请求头时,服务器将提供压缩版本(如果可用)。支持的压缩编码包括 gzip(`.gz`)、brotli(`.br`)和 zstd(`.zst`)。
设置 `compressPublicAssets: true` 以启用所有编码:
```js [nitro.config.ts]
import { defineConfig } from "nitro";
export default defineConfig({
compressPublicAssets: true,
});
```
或选择特定编码:
```js [nitro.config.ts]
import { defineConfig } from "nitro";
export default defineConfig({
compressPublicAssets: {
gzip: true,
brotli: true,
zstd: false,
},
});
```
::note
仅压缩可压缩的 MIME 类型(文本、JavaScript、JSON、XML、WASM、字体、SVG 等)且文件大小至少为 1 KB 的文件。源映射文件(`.map`)会被排除。zstd 压缩要求在构建时使用 Node.js 22+(`zlib.zstdCompress`),在较旧版本上会被静默跳过
::
## 导入文件
任何文件都可以通过 `type` import 属性导入。其内容会以内联形式添加到服务器包中,可以作为带有 [`type: "bytes"`](https://github.com/tc39/proposal-import-bytes) 的 `Uint8Array`,也可以作为带有 [`type: "text"`](https://github.com/tc39/proposal-import-text) 的 UTF-8 `string`:
```ts [routes/logo.ts]
import { defineHandler } from "nitro";
import logo from "./logo.png" with { type: "bytes" }; // Uint8Array
import readme from "./README.md" with { type: "text" }; // string
export default defineHandler(() => {
return new Response(logo, { headers: { "content-type": "image/png" } });
});
```
文件类型会被忽略:任何文件都可以作为字节或文本导入。
动态导入同样受支持:
```ts
const { default: logo } = await import("./logo.png", { with: { type: "bytes" } });
```
或者,`raw:` 前缀会将文件作为 `string`(对于文本文件,根据文件类型确定)或 `Uint8Array`(对于二进制文件)导入:
```ts
import readme from "raw:./README.md"; // string
import logo from "raw:./logo.png"; // Uint8Array
```
::note
导入的文件会以内联形式添加到服务器包中(二进制文件会进行 base64 编码)。对于较大的文件,建议使用[服务器资源](#server-assets)
::
::note
TypeScript 尚不支持 `bytes` 和 `text` 属性。在此之前,导入未知文件类型时需要添加 `// @ts-ignore` 注释
::
## 服务器资源
`assets/` 目录中的所有资源(如果设置了 [`serverDir`](/config#serverdir),则相对于该目录,否则相对于项目根目录)都会添加到服务器包中。构建应用后,可以在 `.output/server/chunks/raw/` 目录中找到这些资源。请注意资源大小,因为它们会与服务器包一起打包。
::tip
如果您的代码从未使用 `useKV()`,服务器资源将不会包含在服务器包中。
::
它们可以通过 `assets:server` 挂载点使用[存储层](/docs/storage)来访问。
例如,您可以将 json 文件存储在 `assets/data.json` 中,并在处理器中检索它:
```js
import { defineHandler } from "nitro";
import { useKV } from "nitro/kv";
export default defineHandler(async () => {
const data = await useKV("assets:server").get("data.json");
return data;
});
```
### 自定义服务器资源
为了从自定义目录添加资源,您需要在 nitro 配置中定义路径。这允许您从 `assets/` 目录之外的目录添加资源。
`serverAssets` 中的每个条目支持以下属性:
- `baseName` -- 用作存储挂载点的名称(通过 `assets:` 访问)
- `dir` -- 目录路径(相对于 `rootDir` 解析)
- `pattern` -- 文件包含的 glob 模式(默认:`"**/*"`)
- `ignore` -- 用于排除文件的 glob 模式数组
```js [nitro.config.ts]
import { defineConfig } from "nitro";
export default defineConfig({
serverAssets: [
{
baseName: "templates",
dir: "./templates",
},
],
});
```
然后您可以使用 `assets:templates` 基础路径来检索您的资源。
```ts [handlers/success.ts]
import { defineHandler } from "nitro";
import { useKV } from "nitro/kv";
export default defineHandler(async (event) => {
const html = await useKV("assets:templates").get("success.html");
return html;
});
```
::note
在开发期间,服务器资源会使用 `fs` unstorage 驱动程序直接从文件系统读取。在生产环境中,它们会作为带有预计算元数据(MIME 类型、ETag、修改时间)的延迟导入打包到服务器中
::
::read-more{to="/docs/storage"}
服务器资源通过存储层访问。有关所有可用方法的完整列表,请参阅存储文档
::
---
# 配置
> 自定义并扩展 Nitro 默认配置。
::read-more{to="/config"}
查看 [配置参考](/config) 了解可用选项。
::
## 配置文件
你可以使用配置文件自定义 Nitro。根据你的设置,在 `nitro.config.ts` 中定义选项,或者使用 Vite 插件时在 `vite.config.ts` 的 `nitro` 键下定义选项。
::code-group
```ts [nitro.config.ts]
import { defineConfig } from "nitro";
export default defineConfig({
// Nitro options
})
```
```ts [vite.config.ts]
import { defineConfig } from 'vite'
import { nitro } from 'nitro/vite'
export default defineConfig({
plugins: [
nitro()
],
nitro: {
// Nitro 选项
}
})
```
::
### 支持的配置文件
Nitro 使用项目根目录中的 [c12](https://github.com/unjs/c12) 加载配置(该目录由传递给 `nitro` CLI 的目录决定,默认为当前工作目录)。系统会按以下顺序检查位置,并使用第一个匹配的文件:
- `nitro.config.{js,ts,mjs,cjs,mts,cts,json,jsonc,json5,yaml,yml,toml}`
- `.config/nitro.{js,ts,mjs,cjs,mts,cts,json,jsonc,json5,yaml,yml,toml}`
- `.config/nitro.config.{js,ts,mjs,cjs,mts,cts,json,jsonc,json5,yaml,yml,toml}`
推荐使用 `nitro.config.ts` 这一约定。
同一目录下没有扩展名的 `.nitrorc` 文件也会被加载(使用 `key=value` 语法),并以低于主配置文件的优先级进行合并。
使用 Vite 插件时,也可以在 `vite.config.ts` 的 `nitro` 键中直接传入选项(如上所示)。
### 环境特定配置
使用 [c12](https://github.com/unjs/c12) 约定,你可以使用 `$development` 和 `$production` 键提供特定于环境的覆盖:
```ts [nitro.config.ts]
import { defineConfig } from "nitro";
export default defineConfig({
logLevel: 3,
$development: {
// 仅在开发模式下应用的选项
debug: true,
},
$production: {
// 仅在生产构建中应用的选项
minify: true,
},
})
```
环境名称在 `nitro dev` 期间为 `"development"`,在 `nitro build` 期间为 `"production"`。
### 扩展配置
你可以使用 `extends` 键从其他配置或预设进行扩展:
```ts [nitro.config.ts]
import { defineConfig } from "nitro";
export default defineConfig({
extends: "./base.config",
})
```
## 目录选项
Nitro 提供了多个选项来控制目录结构:
| 选项 | 默认值 | 描述 |
| --- | --- | --- |
| `rootDir` | `.`(当前目录) | 项目的根目录。 |
| `serverDir` | `false` | 服务器源代码目录(设置为 `"server"` 或 `"./"` 以启用)。 |
| `buildDir` | `node_modules/.nitro` | 构建产物的目录。 |
| `output.dir` | `.output` | 生产输出目录。 |
| `output.serverDir` | `.output/server` | 服务器输出目录。 |
| `output.publicDir` | `.output/public` | 公共资源输出目录。 |
```ts [nitro.config.ts]
import { defineConfig } from "nitro";
export default defineConfig({
serverDir: "server",
buildDir: "node_modules/.nitro",
output: {
dir: ".output",
},
})
```
::note
`srcDir` 选项已弃用。请改用 `serverDir`。
::
:read-more{to="/config#directories"}
## 环境变量
某些 Nitro 行为可以使用环境变量进行配置。这些变量会在构建和启动时配置 Nitro 本身。若要向应用公开你自己的值,请改用[运行时配置](#runtime-configuration)。
| 变量 | 描述 |
| --- | --- |
| `NITRO_PRESET` | 覆盖部署预设。 |
| `NITRO_COMPATIBILITY_DATE` | 设置兼容性日期。 |
| `NITRO_APP_BASE_URL` | 覆盖基础 URL(默认值:`/`)。 |
| `NITRO_BUILDER` | 选择用于构建的打包器(`rollup`、`rolldown` 或 `vite`)。 |
| `NITRO_ENV_PREFIX` | 设置运行时配置环境覆盖的自定义次要前缀(默认值:`_`)。 |
| `NITRO_ENV_EXPANSION` | 启用运行时配置值中的环境变量展开。 |
## 运行时配置
运行时配置允许你向应用公开配置值,并使用环境变量在运行时覆盖这些值。这对于在不同环境(开发、预发布、生产)之间有所不同的值非常有用,例如 API 端点或功能标志。
首先,在配置文件中定义运行时配置。
```ts [nitro.config.ts]
import { defineConfig } from "nitro";
export default defineConfig({
runtimeConfig: {
apiToken: "dev_token", // `dev_token` 是默认值
}
});
```
你现在可以使用 `useRuntimeConfig()` 访问运行时配置。
```ts [api/example.get.ts]
import { defineHandler } from "nitro";
import { useRuntimeConfig } from "nitro/runtime-config";
export default defineHandler((event) => {
return useRuntimeConfig().apiToken; // 返回 `dev_token`
});
```
### 嵌套对象
运行时配置支持嵌套对象。任何深度的键都使用 `NITRO_` 前缀和 `UPPER_SNAKE_CASE` 转换映射到环境变量:
```ts [nitro.config.ts]
import { defineConfig } from "nitro";
export default defineConfig({
runtimeConfig: {
database: {
host: "localhost",
port: 5432,
},
},
});
```
```bash [.env]
NITRO_DATABASE_HOST="db.example.com"
NITRO_DATABASE_PORT="5433"
```
::note
只有在配置文件的 `runtimeConfig` 中定义的键才会被考虑。你无法仅通过环境变量引入新键。
::
### 序列化
运行时配置值必须是可序列化的(字符串、数字、布尔值、普通对象和数组)。不可序列化的值(类实例、函数等)将在构建时触发警告。
配置中值为 `undefined` 或 `null` 的项将作为后备替换为空字符串(`""`)。
### 本地开发
你可以使用环境变量更新运行时配置。在本地可以使用 `.env` 或 `.env.local` 文件,在生产环境中可以使用平台变量(见下文)。
在你的项目根目录创建一个 `.env` 文件:
```bash [.env]
NITRO_API_TOKEN="123"
```
重启开发服务器,访问 `/api/example` 端点,你应该会看到响应为 `123`,而不是 `dev_token`。
::note
`.env` 和 `.env.local` 文件会在 Nitro 解析配置时加载,在 `nitro dev` 和 `nitro build` 中都是如此,因此 `nitro.config.ts` 可以通过 `process.env` 读取它们。环境中已经存在的变量优先级高于 `.env` 中的值。它们**不会**在运行时由构建后的服务器加载:在生产环境中,请使用平台原生的环境变量机制。
::
你仍然可以使用 `import.meta.env` 或 `process.env` 直接访问环境变量,但应避免在环境全局上下文中(模块顶层)读取它们,以防止出现意外行为。
### 生产环境
你可以在生产环境中定义变量以更新运行时配置。
::warning
所有变量必须以 `NITRO_` 为前缀才能应用到运行时配置。它们将覆盖你在 `nitro.config.ts` 文件中定义的运行时配置变量。
::
```bash
NITRO_API_TOKEN="123"
```
运行时配置中的键采用 camelCase,环境变量中采用 UPPER_SNAKE_CASE。
```ts
{
helloWorld: "foo"
}
```
```bash
NITRO_HELLO_WORLD="foo"
```
### 自定义环境前缀
你可以使用 `nitro.envPrefix` 运行时配置键配置次要的环境变量前缀。除了默认的 `NITRO_` 前缀外,还会检查此前缀:
```ts [nitro.config.ts]
import { defineConfig } from "nitro";
export default defineConfig({
runtimeConfig: {
nitro: {
envPrefix: "APP_",
},
apiToken: "",
},
});
```
使用此配置,`NITRO_API_TOKEN` 和 `APP_API_TOKEN` 都将被检查作为覆盖值。
::note
未配置自定义前缀时,默认的次要前缀为 `_`。例如,`_API_TOKEN` 也会覆盖 `apiToken`。
::
### 环境变量展开
启用后,运行时配置字符串值中使用 `{{VAR_NAME}}` 语法的环境变量引用将在运行时展开:
```ts [nitro.config.ts]
import { defineConfig } from "nitro";
export default defineConfig({
experimental: {
envExpansion: true,
},
runtimeConfig: {
url: "https://{{APP_DOMAIN}}/api",
},
});
```
```bash
APP_DOMAIN="example.com"
```
在运行时,`useRuntimeConfig().url` 将解析为 `"https://example.com/api"`。
---
# 数据库
> Nitro 提供了一个内置的轻量级 SQL 数据库层。
> Nitro 提供了一个内置的轻量级 SQL 数据库层。
默认数据库连接已通过 [SQLite](https://db0.unjs.io/connectors/sqlite) **预配置**,可在开发模式和任何兼容 Node.js 的生产部署中开箱即用。默认情况下,数据将存储在 `.data/db.sqlite` 中。
:read-more{to="https://db0.unjs.io" title="DB0 文档"}
::important
数据库支持目前处于实验阶段。
请参阅 [db0 issues](https://github.com/unjs/db0/issues) 了解状态并报告错误。
::
数据库层默认不启用。启用实验性功能标志:
```ts [nitro.config.ts]
import { defineConfig } from "nitro";
export default defineConfig({
experimental: {
database: true
}
})
```
## 使用
```ts [server.ts]
import { defineHandler } from "nitro";
import { useDatabase } from "nitro/database";
export default defineHandler(async () => {
const db = useDatabase();
// 创建用户表
await db.sql`DROP TABLE IF EXISTS users`;
await db.sql`CREATE TABLE IF NOT EXISTS users ("id" TEXT PRIMARY KEY, "firstName" TEXT, "lastName" TEXT, "email" TEXT)`;
// 添加新用户
const userId = String(Math.round(Math.random() * 10_000));
await db.sql`INSERT INTO users VALUES (${userId}, 'John', 'Doe', '')`;
// 查询用户
const { rows } = await db.sql`SELECT * FROM users WHERE id = ${userId}`;
return {
rows,
};
});
```
### `useDatabase`
使用 `useDatabase` 获取数据库实例。它接受一个可选的连接名称(默认为 `"default"`)。
```ts
import { useDatabase } from "nitro/database";
// 使用默认连接
const db = useDatabase();
// 使用命名连接
const usersDb = useDatabase("users");
```
数据库实例会在首次使用时延迟创建,并在后续使用相同连接名称调用时进行缓存。如果连接名称未配置,将抛出错误。
### `db.sql`
使用带标签的模板字面量和自动参数绑定执行 SQL 查询:
```ts
const db = useDatabase();
// 使用参数化值插入(防止 SQL 注入)
const id = "1001";
await db.sql`INSERT INTO users VALUES (${id}, 'John', 'Doe', 'john@example.com')`;
// 使用参数查询
const { rows } = await db.sql`SELECT * FROM users WHERE id = ${id}`;
// 结果包含行、变更计数和最后插入的 ID
const result = await db.sql`INSERT INTO posts (title) VALUES (${"Hello"})`;
// result.rows, result.changes, result.lastInsertRowid
```
### `db.exec`
直接执行原始 SQL 字符串:
```ts
const db = useDatabase();
await db.exec("CREATE TABLE IF NOT EXISTS users (id TEXT PRIMARY KEY, name TEXT)");
```
### `db.prepare`
准备 SQL 语句以供重复执行:
```ts
const db = useDatabase();
const stmt = db.prepare("SELECT * FROM users WHERE id = ?");
const result = await stmt.bind("1001").all();
```
## 配置
你可以使用 `database` 配置更改默认连接,或为任意[受支持的数据库](https://db0.unjs.io/connectors/sqlite)定义其他命名连接,并将数据库实例与任意[受支持的 ORM](https://db0.unjs.io/integrations)集成。
每个连接都是一个包含 `connector` 名称和可选 `options` 对象的 `DatabaseConnectionConfig`。特定连接器的设置(例如 `url`、`host` 或 `name`)应放在 `options` 下,而不是连接配置的顶层。
```ts [nitro.config.ts]
import { defineConfig } from "nitro";
export default defineConfig({
database: {
default: {
connector: "sqlite",
options: { name: "db" },
},
users: {
connector: "postgresql",
options: {
url: "postgresql://username:password@hostname:port/database_name",
},
},
analytics: {
connector: "mysql2",
options: {
host: "localhost",
port: 3306,
user: "root",
password: "password",
database: "analytics",
},
},
},
});
```
请参阅 [db0 连接器文档](https://db0.unjs.io/connectors),了解每个连接器接受的 `options`。
### 开发数据库
使用 `devDatabase` 配置来**仅在开发模式下**覆盖数据库配置。这对于在开发期间使用本地 SQLite 数据库,同时在生产环境中使用不同的数据库非常有用。
```ts [nitro.config.ts]
import { defineConfig } from "nitro";
export default defineConfig({
database: {
default: {
connector: "postgresql",
options: {
url: "postgresql://username:password@hostname:port/database_name"
}
}
},
devDatabase: {
default: {
connector: "sqlite",
options: { name: "dev-db" }
}
}
});
```
::note
在开发环境中,提供的 `devDatabase` 会完全**替换** `database` 配置;连接不会按名称合并。请在 `devDatabase` 中包含开发期间所需的每个连接。
::
::tip
启用 `experimental.database` 且未提供 `database` 或 `devDatabase` 配置时,Nitro 会自动配置一个默认的 SQLite 连接。在开发模式下,数据会存储在相对于项目根目录的位置。在 Node.js 生产环境中,则使用默认的 SQLite 路径。
::
## 连接器
Nitro 支持所有 [db0 连接器](https://db0.unjs.io/connectors)。数据库配置中的 `connector` 字段接受以下任意值:
| 连接器 | 描述 |
|---|---|
| `sqlite` | Node.js 内置 SQLite(`node-sqlite` 的别名)|
| `node-sqlite` | Node.js 内置 SQLite |
| `better-sqlite3` | [better-sqlite3](https://github.com/WiseLibs/better-sqlite3) |
| `sqlite3` | [sqlite3](https://github.com/TryGhost/node-sqlite3) |
| `bun` / `bun-sqlite` | Bun 内置 SQLite |
| `libsql` / `libsql-node` | [libSQL](https://github.com/tursodatabase/libsql)(Node.js) |
| `libsql-http` | 基于 HTTP 的 libSQL |
| `libsql-web` | 用于 Web 环境的 libSQL |
| `libsql-core` | 使用用户提供客户端的 libSQL |
| `postgresql` | [PostgreSQL](https://github.com/brianc/node-postgres) |
| `mysql2` | [MySQL](https://github.com/sidorares/node-mysql2) |
| `pglite` | [PGlite](https://github.com/electric-sql/pglite)(嵌入式 PostgreSQL) |
| `planetscale` | [PlanetScale](https://github.com/planetscale/database-js) 无服务器数据库 |
| `neon` | [Neon](https://github.com/neondatabase/serverless) 无服务器数据库 |
| `cloudflare-d1` | [Cloudflare D1](https://developers.cloudflare.com/d1/) |
| `cloudflare-hyperdrive-mysql` | 使用 MySQL 的 Cloudflare Hyperdrive |
| `cloudflare-hyperdrive-postgresql` | 使用 PostgreSQL 的 Cloudflare Hyperdrive |
### 连接器依赖
某些连接器依赖第三方库(例如,`postgresql` 需要 [`pg`](https://www.npmjs.com/package/pg))。
Nitro 会检测已配置连接所需的库,并提示安装缺失的库(在 CI 中会自动安装)。随后,已安装的库会通过其 `lib` 选项显式传递给连接器,以便打包器能够静态解析它们。
---
# 生命周期
> 了解 Nitro 如何运行并为你的应用提供传入请求服务。
## 服务器启动
当服务器启动时,在接受任何请求之前,Nitro 会:
1. 执行[服务器入口](/docs/server-entry#startup-logic)中的顶层代码。
2. 创建 Nitro 应用,并按顺序运行[运行时插件](/docs/plugins)。
在无服务器和边缘预设中,这会在每次冷启动时发生。
## 请求生命周期
请求可以在以下任何层中被拦截和终止(无论是否有响应),按以下顺序:
::steps
### `request` 钩子
`request` 钩子是每个传入请求运行的第一段代码。它通过[运行时插件](/docs/plugins)进行注册:
```ts [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` 钩子](#error-handling)捕获,不会终止请求管道。
::
### 路由规则
接下来执行 Nitro 配置中的匹配[路由规则](/docs/routing#route-rules)。路由规则作为中间件运行,在所有全局中间件之前执行,并且其中大多数会修改响应而不会终止请求(例如添加标头或设置缓存策略)。匹配的规则会在任何中间件运行之前解析,并且在每个中间件中都可通过 `event.context.routeRules` 访问。
```ts [nitro.config.ts]
import { defineConfig } from "nitro";
export default defineConfig({
routeRules: {
'/**': { headers: { 'x-nitro': 'first' } }
}
})
```
:read-more{to="/docs/routing#route-rules" title="路由 > 路由规则"}
### 静态资源
启用静态资源服务时(大多数预设的默认设置),Nitro 会将静态资源处理程序注册为**第一个全局中间件**。它会在其他全局中间件和路由处理程序运行之前,检查请求是否匹配 `public/` 目录中的文件。
如果找到匹配项,静态文件会立即使用适当的 `Content-Type`、`ETag`、`Last-Modified` 和 `Cache-Control` 标头进行响应。请求会被终止,不会再执行其他中间件或路由。
静态资源还通过 `Accept-Encoding` 标头支持对预压缩文件(gzip、brotli、zstd)的内容协商。
### 全局中间件
接下来会运行 `middleware/` 目录中定义的所有全局中间件:
```ts [middleware/info.ts]
import { defineHandler } from "nitro";
export default defineHandler((event) => {
event.context.info = { name: "Nitro" };
});
```
::warning
从中间件返回将关闭请求,应尽可能避免。
::
::read-more{to="/docs/routing#middleware"}
了解有关 Nitro 中间件的更多信息。
::
### 路由中间件
针对特定路由模式注册的中间件(通过 [`handlers` 配置](/docs/routing#route-scoped-middleware))会在全局中间件之后运行。附加到匹配路由本身的任何中间件会最后运行,就在路由处理程序之前。
### 路由
Nitro 会将传入请求与 `routes/` 文件夹中定义的路由进行匹配。
```ts [routes/api/hello.ts]
export default (event) => ({ world: true })
```
::read-more{to="/docs/routing#filesystem-routing"}
详细了解 Nitro 文件系统路由。
::
### 服务器入口
如果定义了服务器入口,它会捕获所有未匹配任何路由的请求,充当 `/**` 路由处理程序。
```ts [server.ts]
import { defineHandler } from "nitro";
export default defineHandler((event) => {
if (event.url.pathname === "/") {
return "Home page";
}
});
```
::read-more{to="/docs/server-entry"}
详细了解 Nitro 服务器入口。
::
### 渲染器
如果没有匹配的路由,Nitro 将查找渲染器处理程序(已定义或自动检测)来处理请求。
::read-more{to="/docs/renderer"}
了解有关 Nitro 渲染器的更多信息。
::
### `response` 钩子
在创建响应后(来自上述任何层),`response` 钩子运行。此钩子接收最终的 `Response` 对象和事件,可用于检查或修改响应头:
```ts [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:
1. 调用 `error` 钩子,传入错误和上下文(包括事件和源标签)。
2. 将错误传递给**错误处理程序**,将其转换为 HTTP 响应。
### 抛出错误
从任何处理程序或中间件中抛出 `HTTPError`,以使用特定的状态码、消息和可选数据结束请求:
```ts [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:
```json
{
"error": true,
"status": 400,
"message": "Invalid user input",
"data": { "field": "email" }
}
```
在开发环境中,程序化请求会收到相同的 JSON 格式;但当请求的 `Accept` 标头包含 `text/html` 时(浏览器请求),Nitro 会渲染一个带有源映射堆栈跟踪的交互式 HTML 错误页面。
### 自定义错误处理程序
要控制错误响应格式,请在 Nitro 配置中将 `errorHandler` 设置为一个文件,该文件使用 `defineErrorHandler` 导出处理程序:
```ts [nitro.config.ts]
import { defineConfig } from "nitro";
export default defineConfig({
errorHandler: "~/error",
});
```
```ts [error.ts]
import { defineErrorHandler } from "nitro";
export default defineErrorHandler((error, event) => {
return new Response(`[custom error] ${error.message}`, {
status: error.status || 500,
headers: { "Content-Type": "text/plain" },
});
});
```
返回 `Response` 以将其发送给客户端,或不返回任何内容以继续交由下一个处理程序处理。`errorHandler` 还接受按顺序运行的处理程序数组:第一个返回响应的处理程序获胜,并且内置默认处理程序始终作为最后一项追加,因此始终存在备用响应:
```ts [nitro.config.ts]
import { defineConfig } from "nitro";
export default defineConfig({
errorHandler: ["~/error/known-errors", "~/error/fallback"],
});
```
如果处理程序自身抛出错误,则会记录该失败,并继续使用下一个处理程序。每个处理程序还会在其第三个参数中以 `{ defaultHandler }` 的形式接收内置的 `defaultHandler`,因此你可以在自己的逻辑中复用默认渲染。
::read-more{to="/examples/custom-error-handler"}
查看完整的自定义错误处理程序示例。
::
要仅自定义开发服务器的错误渲染(不影响生产环境构建),请将处理程序函数作为 `devErrorHandler` 传入:
```ts [nitro.config.ts]
import { defineConfig } from "nitro";
import errorHandler from "./error.ts";
export default defineConfig({
devErrorHandler: errorHandler,
});
```
### 观察错误
要在不更改响应的情况下观察错误,请使用来自[插件](/docs/plugins)的 `error` 运行时钩子:
```ts [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`](/docs/plugins#programmatic-error-capture) 以编程方式将错误送入同一管道。错误也会按请求记录在 `event.req.context.nitro.errors` 中,以便在后续钩子中检查。
此外,进程级别的未处理 Promise 拒绝和未捕获异常会自动捕获到 `error` 钩子中,并带有 `"unhandledRejection"` 和 `"uncaughtException"` 标签。
## 服务器关闭
当 Nitro 服务器关闭时,会调用 `close` 钩子。使用它来清理资源,如数据库连接、定时器或外部服务句柄:
```ts [plugins/cleanup.ts]
import { definePlugin } from "nitro";
export default definePlugin((nitroApp) => {
nitroApp.hooks.hook("close", async () => {
// 清理资源
});
});
```
::note
HTTP 服务器停止接受连接后会等待该钩子完成。长时间运行的服务器预设(Node.js、Bun 和 Deno)会在 `SIGINT` 和 `SIGTERM` 上调用它。无服务器和边缘预设没有关闭信号,因此只有在平台关闭运行时的情况下才会运行该钩子。
::
## 钩子参考
所有运行时钩子都通过[运行时插件](/docs/plugins)使用 `nitroApp.hooks.hook()` 注册。
| 钩子 | 签名 | 运行时机 |
| --- | --- | --- |
| `request` | `(event: HTTPEvent) => void \| Promise` | 每个请求开始时,路由之前。 |
| `response` | `(res: Response, event: HTTPEvent) => void \| Promise` | 响应创建后、发送前。 |
| `error` | `(error: Error, context: { event?, tags? }) => void` | 在生命周期期间捕获到任何错误时。 |
| `close` | `() => void` | 当 Nitro 服务器关闭时。 |
::note
`NitroRuntimeHooks` 接口是可扩展的。部署预设(如 Cloudflare)可以用平台特定的钩子来扩展它。
::
::read-more{to="/docs/plugins"}
了解有关 Nitro 插件和钩子使用示例的更多信息。
::
---
# OpenAPI
> Nitro 可以根据你的路由处理器自动生成 [OpenAPI](https://www.openapis.org/) 规范,并提供交互式 API 文档。
Nitro 会扫描所有路由处理器,提取通过 `defineRouteMeta` 定义的元数据,并生成一份 [OpenAPI 3.1.0](https://spec.openapis.org/oas/v3.1.0) 规范。内置的 [Scalar](https://scalar.com/) 和 [Swagger UI](https://swagger.io/tools/swagger-ui/) 界面让你可以直接在浏览器中浏览并测试 API。
::important
OpenAPI 支持目前处于实验阶段。
::
## 启用 OpenAPI
在 Nitro 配置中启用 OpenAPI:
::code-group
```ts [nitro.config.ts]
import { defineConfig } from "nitro";
export default defineConfig({
experimental: {
openAPI: true,
},
});
```
::
启用后,开发环境中会提供以下端点:
| 端点 | 说明 |
|---|---|
| `/_openapi.json` | OpenAPI 3.1.0 JSON 规范 |
| `/_scalar` | Scalar API 参考界面 |
| `/_swagger` | Swagger UI |
## 路由元数据
在路由处理器文件中使用 `defineRouteMeta` 宏,为每个路由提供 OpenAPI 元数据。`openAPI` 属性接收标准的 OpenAPI [Operation Object](https://spec.openapis.org/oas/v3.1.0#operation-object)。
```ts [routes/api/hello.ts]
import { defineRouteMeta, defineHandler } from "nitro";
defineRouteMeta({
openAPI: {
tags: ["greeting"],
description: "返回问候消息",
responses: {
200: { description: "问候成功" },
},
},
});
export default defineHandler(() => {
return { message: "你好,世界!" };
});
```
::note
`defineRouteMeta` 是一个构建期宏。元数据会在构建过程中被静态提取,不会为处理器增加任何运行时开销。
::
### 参数
路由参数(如 `:id`、`[id]`)会自动转换为 OpenAPI 路径参数。你也可以在 `parameters` 数组中定义额外的查询参数或请求头参数:
```ts [routes/api/users/[id].get.ts]
import { defineRouteMeta, defineHandler } from "nitro";
defineRouteMeta({
openAPI: {
tags: ["users"],
description: "根据 ID 获取用户",
parameters: [
{
in: "query",
name: "include",
description: "要包含的关联资源列表,使用逗号分隔",
schema: { type: "string" },
},
],
responses: {
200: { description: "已找到用户" },
404: { description: "未找到用户" },
},
},
});
export default defineHandler((event) => {
const { id } = event.context.params;
return { id, name: "小明" };
});
```
在这个示例中,`id` 路径参数会根据路由模式自动推断,因此只需要声明额外的 `include` 查询参数。
### 响应 Schema
使用标准的 OpenAPI `responses` 对象来定义响应的内容类型和 Schema:
```ts [routes/api/status.ts]
import { defineRouteMeta, defineHandler } from "nitro";
defineRouteMeta({
openAPI: {
description: "返回当前服务器状态",
responses: {
200: {
description: "服务器状态",
content: {
"application/json": {
schema: {
type: "object",
properties: {
status: { type: "string", enum: ["healthy", "degraded"] },
uptime: { type: "number" },
},
},
},
},
},
},
},
});
export default defineHandler(() => {
return { status: "healthy", uptime: process.uptime() };
});
```
### 全局组件
使用 `$global` 属性定义可复用的 Schema,它们会被提升到 OpenAPI 规范顶层的 `components` 区域。这样你就可以在多个路由之间通过 `$ref` 复用这些共享 Schema。
```ts [routes/api/users.get.ts]
import { defineRouteMeta, defineHandler } from "nitro";
defineRouteMeta({
openAPI: {
tags: ["users"],
description: "列出所有用户",
responses: {
200: {
description: "用户列表",
content: {
"application/json": {
schema: {
type: "array",
items: { $ref: "#/components/schemas/User" },
},
},
},
},
},
$global: {
components: {
schemas: {
User: {
type: "object",
properties: {
id: { type: "string" },
name: { type: "string" },
email: { type: "string", format: "email" },
},
},
},
},
},
},
});
export default defineHandler(() => {
return [{ id: "1", name: "小明", email: "xiaoming@example.com" }];
});
```
定义之后,`User` Schema 就可以在任意其他路由中通过 `{ $ref: "#/components/schemas/User" }` 引用,而无需重复声明。
### 自动标签
路由会根据路径前缀自动分配标签:
| 路径前缀 | 标签 |
|---|---|
| `/api/` | API 路由 |
| `/_` | 内部 |
| 其他 | 应用路由 |
你也可以在 `openAPI` 元数据中显式指定 `tags` 来覆盖自动标签。
## 配置
通过顶层 `openAPI` 选项配置 OpenAPI 的行为:
```ts [nitro.config.ts]
import { defineConfig } from "nitro";
export default defineConfig({
experimental: {
openAPI: true,
},
openAPI: {
meta: {
title: "My API",
description: "我的超棒 API",
version: "2.0.0",
},
},
});
```
### `meta`
设置 API 元数据,这些内容会出现在规范的 `info` 对象中:
| 属性 | 默认值 | 说明 |
|---|---|---|
| `title` | `"Nitro Server Routes"` | API 标题 |
| `description` | — | API 描述 |
| `version` | `"1.0.0"` | API 版本 |
### `route`
- 默认值:`"/_openapi.json"`
覆盖 OpenAPI JSON 规范的服务路径:
```ts [nitro.config.ts]
export default defineConfig({
openAPI: {
route: "/_docs/openapi.json",
},
});
```
### `ui`
配置或禁用内置的 API 文档界面:
```ts [nitro.config.ts]
export default defineConfig({
openAPI: {
ui: {
scalar: {
route: "/_docs/scalar",
theme: "purple",
},
swagger: {
route: "/_docs/swagger",
},
},
},
});
```
将任一 UI 设置为 `false`{lang=ts} 即可禁用:
```ts [nitro.config.ts]
export default defineConfig({
openAPI: {
ui: {
swagger: false,
},
},
});
```
:read-more{to="https://github.com/scalar/scalar" title="Scalar 配置"}
## 生产环境
默认情况下,OpenAPI 端点只在开发环境中可用。若要在生产环境中启用,请设置 `production` 选项:
```ts [nitro.config.ts]
export default defineConfig({
openAPI: {
production: "runtime",
},
});
```
| 值 | 行为 |
|---|---|
| `false`{lang=ts} | 在生产环境中禁用(默认) |
| `"runtime"`{lang=ts} | 在每次请求时运行时生成规范 |
| `"prerender"`{lang=ts} | 在构建时生成规范,并作为静态文件提供 |
如果规范在不同部署之间不会变化,建议使用 `"prerender"`{lang=ts} 以获得最佳性能;如果你需要动态服务器信息或中间件访问能力,则使用 `"runtime"`{lang=ts}。
::warning
如果你在生产环境中启用了 OpenAPI,请务必使用合适的鉴权或访问控制来保护这些端点。
::
---
# 插件
> 使用运行时插件扩展 Nitro 的运行时行为。
运行时插件会在服务器启动期间**执行一次**,因此它们适合用于一次性初始化以及注册[生命周期钩子](/docs/lifecycle)。每个插件都会接收 `nitroApp` 上下文。
插件会从 `plugins/` 目录自动注册并同步运行。插件函数本身必须是同步的(返回 `void`),但它们注册的钩子可以是异步的。
```ts [plugins/test.ts]
import { definePlugin } from "nitro";
export default definePlugin((nitroApp) => {
console.log('Nitro plugin', nitroApp)
})
```
如果你有其他目录中的插件,可以使用 `plugins` 选项:
```ts [nitro.config.ts]
import { defineConfig } from "nitro";
export default defineConfig({
plugins: ['my-plugins/hello.ts']
})
```
插件按以下顺序运行:
1. 按 `plugins` 选项中列出的顺序运行其中列出的插件。
2. 运行从 `plugins/` 目录自动注册的插件。目录按照 [`scanDirs`](/config#scandirs) 的顺序处理,每个目录中的文件则按路径排序。
## `nitroApp` 上下文
插件函数接收一个具有以下属性的 `nitroApp` 对象:
| 属性 | 类型 | 描述 |
| --- | --- | --- |
| `hooks` | [`HookableCore`](https://github.com/unjs/hookable) | 用于注册生命周期回调的钩子系统。 |
| `h3` | `H3Core` | 底层的 [H3](https://github.com/h3js/h3) 应用实例。 |
| `fetch` | `(req: Request) => Response \| Promise` | 应用的内部 fetch 处理器。 |
| `captureError` | `(error: Error, context) => void` | 以编程方式将错误捕获到错误钩子管道中。 |
::note
H3 会在第一个请求时组合一次中间件链。插件在启动期间添加到 `nitroApp.h3["~middleware"]` 的中间件会被包含在内,但如果它们位于数组前部,则会在 Nitro 的路由规则之前运行。如果插件在处理第一个请求后修改了该数组,则还必须将 `nitroApp.h3["~dispatch"]` 和 `nitroApp.h3["~composed"]` 重置为 `undefined`,以便重新组合该链。
::
## Nitro 运行时钩子
使用 Nitro [钩子](https://github.com/unjs/hookable) 在[请求生命周期](/docs/lifecycle)中的特定时间点运行自定义函数。在插件内部使用 `nitroApp.hooks.hook()` 注册它们:
```ts [plugins/close.ts]
import { definePlugin } from "nitro";
export default definePlugin((nitroApp) => {
nitroApp.hooks.hook("close", async () => {
// 当 nitro 关闭时运行
});
})
```
### 可用的钩子
| 钩子 | 签名 | 描述 |
| --- | --- | --- |
| `request` | `(event: HTTPEvent) => void \| Promise` | 在每个请求开始时调用。 |
| `response` | `(res: Response, event: HTTPEvent) => void \| Promise` | 在响应创建后调用。 |
| `error` | `(error: Error, context: { event?: HTTPEvent, tags?: string[] }) => void` | 当错误被捕获时调用。 |
| `close` | `() => void` | 当 Nitro 服务器关闭时调用。 |
> [!NOTE]
> `NitroRuntimeHooks` 接口是可扩展的。部署预设(如 Cloudflare)可以使用平台特定的钩子(如 `cloudflare:scheduled` 和 `cloudflare:email`)来扩展它。
### 注销钩子
`hook()` 方法返回一个注销函数,可以调用该函数来移除钩子:
```ts [plugins/example.ts]
import { definePlugin } from "nitro";
export default definePlugin((nitroApp) => {
const unregister = nitroApp.hooks.hook("request", (event) => {
// ......
});
// 稍后,移除该钩子
unregister();
});
```
## 示例
### 捕获错误
你可以使用插件来捕获所有应用错误。
```ts [plugins/errors.ts]
import { definePlugin } from "nitro";
export default definePlugin((nitroApp) => {
nitroApp.hooks.hook("error", async (error, { event }) => {
console.error(`${event?.req.url} Application error:`, error)
});
})
```
`context` 对象包含一个可选的 `tags` 数组,用于标识错误来源(例如 `"request"`、`"response"`、`"cache"`、`"plugin"`、`"unhandledRejection"`、`"uncaughtException"`)。
### 以编程方式捕获错误
你可以使用 `captureError` 手动将错误送入错误钩子管道:
```ts [plugins/capture.ts]
import { definePlugin } from "nitro";
export default definePlugin((nitroApp) => {
nitroApp.captureError(new Error("something went wrong"), {
tags: ["startup"],
});
});
```
### 优雅关闭
服务器会优雅地关闭,等待所有使用 `event.waitUntil` 启动的待处理后台任务。使用 `close` 钩子清理你自己的资源:
```ts [plugins/shutdown.ts]
import { definePlugin } from "nitro";
export default definePlugin((nitroApp) => {
nitroApp.hooks.hook("close", async () => {
// 清理资源、关闭连接等。
});
});
```
### 请求和响应生命周期
你可以使用插件注册在[请求生命周期](/docs/lifecycle)中运行的钩子:
```ts [plugins/lifecycle.ts]
import { definePlugin } from "nitro";
export default definePlugin((nitroApp) => {
nitroApp.hooks.hook("request", (event) => {
console.log("on request", event.req.url);
});
nitroApp.hooks.hook("response", (res, event) => {
// 修改或检查响应
console.log("on response", res.status);
});
});
```
### 修改响应头
```ts [plugins/headers.ts]
import { definePlugin } from "nitro";
export default definePlugin((nitroApp) => {
nitroApp.hooks.hook("response", (res, event) => {
const { pathname } = new URL(event.req.url);
if (pathname.endsWith(".css") || pathname.endsWith(".js")) {
res.headers.append("Vary", "Origin");
}
});
});
```
---
# 预渲染
> 在构建时渲染路由,并将其作为静态资源提供
启用预渲染后,Nitro 会在构建期间获取指定路由,并将响应写入 `.output/public` 目录。预渲染的路由随后会作为静态文件提供。请求时不会进行服务器渲染,因此这些路由速度快、可缓存,并且可以部署到任意静态托管服务或 CDN。
预渲染适用于内容不会随请求变化的路由:落地页、文档、博客文章,或输出内容固定的 JSON 端点。动态路由仍然可以继续由服务器提供,并且你可以在同一个应用中自由混合预渲染路由和服务器渲染路由。
## 预渲染路由
### `prerender.routes` 配置
使用 `prerender.routes` 选项列出要预渲染的路由:
```ts [nitro.config.ts]
import { defineConfig } from "nitro";
export default defineConfig({
prerender: {
routes: ["/", "/about", "/api/content.json"],
},
});
```
HTML 响应会作为 `index.html` 文件写入子文件夹中(`/about` 会生成 `about/index.html`),而其他响应会保留其路径(`/api/content.json` 会生成 `api/content.json`)。请参阅 [`autoSubfolderIndex`](#options) 以更改此行为。
### `prerender` 路由规则
或者,使用 `prerender` [路由规则](/docs/routing#route-rules)将路由标记为预渲染:
```ts [nitro.config.ts]
import { defineConfig } from "nitro";
export default defineConfig({
routeRules: {
"/about": { prerender: true },
},
});
```
::warning
路由规则只会将**完全匹配的路径**添加到预渲染列表中。像 `"/blog/**": { prerender: true }` 这样的通配符模式**不会**自动展开,因为 Nitro 无法知道哪些路由与该模式匹配。要预渲染一组动态路由,请使用[链接爬取](#crawling-links)或 [`prerender:routes` hook](#hooks)。
::
## 爬取链接
除了(或作为补充)列出每个路由之外,还可以启用 `crawlLinks`,让 Nitro 通过跟随预渲染 HTML 页面中的链接来发现路由:
```ts [nitro.config.ts]
import { defineConfig } from "nitro";
export default defineConfig({
prerender: {
crawlLinks: true,
routes: ["/"],
},
});
```
爬虫会从每个预渲染 HTML 页面中提取 `href` 属性,并将发现的路由加入预渲染队列。外部链接、哈希链接,以及指向带扩展名文件的链接(`.json` 除外)都会被跳过。相对链接会根据其所在页面进行解析。
如果启用了 `crawlLinks` 且未指定路由,爬取会从 `/` 开始。
::tip
处理程序可以通过将 `x-nitro-prerender` 响应标头设置为以逗号分隔的路径列表,来为预渲染添加其他路由。即使禁用了 `crawlLinks`,此功能仍然有效。
::
## 忽略路由
使用 `prerender.ignore` 跳过路由。每个条目可以是字符串前缀、正则表达式,或接收路径的函数:
```ts [nitro.config.ts]
import { defineConfig } from "nitro";
export default defineConfig({
prerender: {
crawlLinks: true,
ignore: [
"/admin", // Ignore routes starting with /admin
/\/preview$/, // Ignore routes ending with /preview
(path) => path.includes("draft"), // Ignore routes containing "draft"
],
},
});
```
你也可以使用 `prerender: false` 路由规则排除路由。与启用预渲染不同,通配符模式**可以**用于排除,因为它们会针对每个候选路由进行匹配:
```ts [nitro.config.ts]
import { defineConfig } from "nitro";
export default defineConfig({
routeRules: {
"/dynamic/**": { prerender: false },
},
});
```
### 公共资源
由带有基础 URL 前缀的[公共资源](/docs/assets)提供的路由始终会被跳过。此外,你可以设置 `prerender.ignoreUnprefixedPublicAssets: true`,跳过已经由不带前缀的公共资源提供的路由,从而避免输出中出现重复文件:
```ts [nitro.config.ts]
import { defineConfig } from "nitro";
export default defineConfig({
prerender: {
crawlLinks: true,
ignoreUnprefixedPublicAssets: true,
},
});
```
## 选项
| 选项 | 默认值 | 描述 |
|--------|---------|-------------|
| `failOnError` | `false` | 当路由无法预渲染时使构建失败。适用于 CI。 |
| `retry` | `3` | 失败路由的重试次数。传入 `Infinity` 可无限重试。 |
| `retryDelay` | `500` | 每次重试之间的延迟时间,单位为毫秒。 |
| `concurrency` | `1` | 并行预渲染的最大路由数。 |
| `interval` | `0` | 预渲染请求之间的延迟时间,单位为毫秒。 |
| `autoSubfolderIndex` | `true` | 将 HTML 路由写入子文件夹索引文件。 |
::note
`retry` 和 `retryDelay` 选项可以在配置中接受,但当前的 v3 预渲染器尚未应用它们。
::
`concurrency` 和 `interval` 选项控制预渲染速度。提高 `concurrency` 可以加快大型网站的速度,或者添加 `interval`,以避免路由调用外部 API 时触发速率限制。
`autoSubfolderIndex` 控制 HTML 文件写入 `.output/public` 的方式:
```bash
# autoSubfolderIndex: true (default)
/about -> .output/public/about/index.html
# autoSubfolderIndex: false
/about -> .output/public/about.html
```
当你的托管服务提供商不允许配置尾部斜杠行为时,将其设置为 `false`。
```ts [nitro.config.ts]
import { defineConfig } from "nitro";
export default defineConfig({
prerender: {
crawlLinks: true,
failOnError: true,
concurrency: 8,
autoSubfolderIndex: false,
},
});
```
## Hooks
对于高级用例,你可以在构建时接入预渲染生命周期。这些 hooks 可供[模块](/docs/modules)和 `hooks` 配置选项使用:
| Hook | 描述 |
|------|-------------|
| `prerender:routes` | 使用初始的路由 `Set` 调用,在预渲染开始前添加或移除条目。 |
| `prerender:config` | 使用用于预渲染的内部 Nitro 实例配置调用。 |
| `prerender:generate` | 在写入每个路由的文件之前调用,用于检查或修改路由对象(`route.contents`、`route.fileName`……),或设置 `route.skip = true`。 |
| `prerender:route` | 每个路由生成后调用。 |
| `prerender:done` | 预渲染完成后调用一次,并传入已预渲染和失败路由的列表。 |
一个常见用例是在运行时动态注册路由,例如从 CMS 或数据库中注册:
```ts [nitro.config.ts]
import { defineConfig } from "nitro";
export default defineConfig({
modules: [
(nitro) => {
nitro.hooks.hook("prerender:routes", async (routes) => {
const posts = await fetch("https://api.example.com/posts").then((res) => res.json());
for (const post of posts) {
routes.add(`/blog/${post.slug}`);
}
});
},
],
});
```
## 静态托管
要发布一个不包含服务器包的完全预渲染网站,请使用 `static`、`github_pages` 或 `gitlab_pages` 等静态预设。这些预设会跳过服务器构建、启用 `crawlLinks`,并且只输出 `public` 目录,可直接上传到任意静态托管服务。详情请参阅[部署文档](/deploy)以及 [GitHub Pages](/deploy/providers/github-pages) 等提供商的文档。
::note
`static: true` 配置选项只会禁用服务器构建;它本身不会启用预渲染。直接使用该选项而不是静态预设时,请配置 `prerender` 选项(通常为 `crawlLinks: true`)来生成页面。
::
---
# 任务
> Nitro tasks 允许在运行时执行一次性操作:例如数据库迁移、缓存清理或内容同步等命名的服务器端操作,你可以按需、按计划或从 CLI 运行这些操作
## 选择加入实验性功能
::important
Tasks support is currently experimental.
See [nitrojs/nitro#1974](https://github.com/nitrojs/nitro/issues/1974) for the relevant discussion.
::
为了使用 tasks API,你需要启用实验性功能标志。Tasks 会从你的 [`serverDir`](/docs/configuration) 中扫描,同时也必须设置该配置:
```ts [nitro.config.ts]
import { defineConfig } from "nitro";
export default defineConfig({
serverDir: "./server",
experimental: {
tasks: true
}
})
```
本页面上的所有示例都假设使用此配置。
## 定义任务
可以在 [`serverDir`](/docs/configuration) 内的 `tasks/[name].ts` 文件中定义任务。
::note
与路由和插件一样,只有在设置了 [`serverDir`](/docs/configuration)(或 `scanDirs`)时才会扫描任务;默认不会扫描任何目录
::
支持嵌套目录。任务名称将使用 `:` 连接。
```txt
server/
tasks/
cleanup.ts <-- cleanup
db/
migrate.ts <-- db:migrate
nitro.config.ts
```
**示例:**
```ts [server/tasks/db/migrate.ts]
import { defineTask } from "nitro/task";
export default defineTask({
meta: {
name: "db:migrate",
description: "Run database migrations",
},
run({ payload, context }) {
console.log("Running DB migration task...");
return { result: "Success" };
},
});
```
### 任务接口
`defineTask` 辅助函数接受一个具有以下属性的对象:
- **`meta`**(可选):一个包含可选的 `name` 和 `description` 字符串字段的对象,用于在开发服务器和 CLI 中显示。
- **`run`**(必需):一个接收 [`TaskEvent`](#taskevent) 并返回(或解析为)一个包含可选 `result` 属性的对象的函数。
```ts
interface Task