Nitro 服务引擎深度解析
Nuxt4 的服务端能力全部来自 Nitro——一个通用的 TypeScript 服务引擎。它不是 Express、不是 Fastify,而是一个可以编译到 Node.js、Cloudflare Workers、Vercel Edge、Deno、Bun 等 20+ 运行时的跨平台引擎。理解 Nitro 的架构设计,是掌握 Nuxt4 全栈能力的关键。
1. Nitro 架构与跨平台能力
1.1 Nitro 是什么
Nitro 是 Nuxt 团队开发的独立服务引擎(可脱离 Nuxt 单独使用),核心特性:
- 跨平台编译:同一份代码 → 编译为不同平台的部署产物
- 基于 H3:轻量级 HTTP 框架,比 Express 快 3-5 倍
- 文件路由:
server/api/和server/routes/自动映射为接口 - 自动导入:
server/utils/中的工具函数全局可用 - 内置 KV 存储:
useStorage()统一的缓存/持久化 API
1.2 支持的部署平台
| 平台 | Preset | 运行时 | 适用场景 |
|---|---|---|---|
| Node.js | node-server | Node.js | 传统服务器、Docker |
| Vercel | vercel | Edge/Node | Serverless |
| Cloudflare Workers | cloudflare-pages | Workers | 边缘计算 |
| Netlify | netlify | Edge/Node | 静态 + Functions |
| Deno Deploy | deno-deploy | Deno | Deno 生态 |
| Bun | bun | Bun | 高性能 Node 替代 |
| AWS Lambda | aws-lambda | Node.js | AWS 生态 |
| 静态托管 | static | 无 | SSG 纯静态 |
// nuxt.config.ts
export default defineNuxtConfig({
nitro: {
preset: 'node-server', // 部署目标
},
})切换部署平台只需改一行配置,业务代码无需任何修改——这是 Nitro 最大的价值。
1.3 编译产物
nuxi build
# 输出到 .output/ 目录:
# .output/
# ├── server/ ← 服务端代码(编译后)
# │ ├── index.mjs ← 入口文件
# │ └── chunks/ ← 代码分片
# ├── public/ ← 静态资源
# └── nitro.json ← 运行时配置2. H3 框架事件处理模型
2.1 H3 vs Express
Nitro 底层使用 H3(HTTP 框架),而非 Express。核心差异:
| 特性 | Express | H3 |
|---|---|---|
| 设计理念 | 中间件链 | 事件处理器 |
| 请求/响应 | req / res 对象 | 统一的 H3Event |
| 性能 | 基准 | 快 3-5 倍 |
| 包体积 | ~500KB | ~50KB |
| 平台兼容 | 仅 Node.js | 跨平台(Workers/Deno/Bun) |
| TypeScript | 需配置 | 原生支持 |
2.2 H3Event 统一事件对象
H3 的核心概念是 H3Event——每个请求都是一个事件,所有操作都围绕它:
// server/api/example.get.ts
export default defineEventHandler((event) => {
// event 是 H3Event 实例,包含请求的所有信息
// 通过工具函数读取:
const query = getQuery(event) // 查询参数
const body = await readBody(event) // 请求体
const headers = getRequestHeaders(event) // 请求头
const cookies = parseCookies(event) // Cookie
// 设置响应:
setResponseStatus(event, 201)
setResponseHeader(event, 'X-Custom', 'value')
setCookie(event, 'token', 'xxx', { httpOnly: true })
return { message: 'ok' } // 自动序列化为 JSON
})设计哲学:不往 event 上挂属性(如 Express 的 req.user),而是用独立的工具函数操作。这使得 H3 的 API 是无状态、可 tree-shake 的。
2.3 H3 工具函数全景
H3 提供了 60+ 个工具函数,按类别梳理:
请求读取:
| 函数 | 用途 | 示例 |
|---|---|---|
getQuery(event) | URL 查询参数 | ?page=1&limit=10 |
getRouterParam(event, name) | 路径参数 | /api/videos/:id |
readBody(event) | JSON 请求体 | POST/PUT body |
readFormData(event) | FormData | 文件上传 |
readRawBody(event) | 原始请求体 | Webhook 签名验证 |
getRequestHeaders(event) | 所有请求头 | — |
getRequestHeader(event, name) | 单个请求头 | Authorization |
parseCookies(event) | 解析 Cookie | — |
getRequestIP(event) | 客户端 IP | 限流、日志 |
getRequestURL(event) | 完整 URL | 回调地址构造 |
isMethod(event, method) | 判断请求方法 | — |
响应控制:
| 函数 | 用途 |
|---|---|
setResponseStatus(event, code) | 设置状态码 |
setResponseHeader(event, name, value) | 设置响应头 |
setResponseHeaders(event, headers) | 批量设置响应头 |
setCookie(event, name, value, opts) | 设置 Cookie |
deleteCookie(event, name) | 删除 Cookie |
sendRedirect(event, url, code?) | 重定向 |
sendStream(event, stream) | 发送流 |
sendNoContent(event) | 返回 204 |
2.4 兼容 Express 中间件
如果有已有的 Express 中间件需要复用,H3 提供了适配器:
import { fromNodeMiddleware } from 'h3'
import helmet from 'helmet'
// 将 Express 中间件转为 H3 中间件
export default fromNodeMiddleware(helmet())但不推荐大量使用——Express 中间件依赖 Node.js API,无法在 Edge Runtime 运行。新项目应直接用 H3 原生 API。
3. server/ 各目录职责详解
server/
├── api/ ← API 接口(自动加 /api 前缀)
│ ├── videos/
│ │ ├── index.get.ts → GET /api/videos
│ │ ├── index.post.ts → POST /api/videos
│ │ └── [id].get.ts → GET /api/videos/:id
│ └── auth/
│ └── login.post.ts → POST /api/auth/login
├── routes/ ← 自定义路由(无前缀)
│ └── sitemap.xml.ts → GET /sitemap.xml
├── middleware/ ← 服务端中间件(每个请求都经过)
│ ├── 01.logger.ts
│ └── 02.auth.ts
├── plugins/ ← Nitro 插件(服务启动时执行)
│ └── database.ts
└── utils/ ← 工具函数(自动导入)
├── db.ts
└── auth.ts
3.1 api/ vs routes/ 的区别
api/:自动加/api前缀,专门用于 JSON APIroutes/:无前缀,用于非 API 路由(如 sitemap、RSS feed、健康检查)
3.2 HTTP 方法映射
文件名后缀决定 HTTP 方法:
| 文件名 | 匹配方法 |
|---|---|
index.ts | 所有方法 |
index.get.ts | GET |
index.post.ts | POST |
index.put.ts | PUT |
index.delete.ts | DELETE |
index.patch.ts | PATCH |
3.3 动态参数
// server/api/videos/[id].get.ts → GET /api/videos/:id
export default defineEventHandler((event) => {
const id = getRouterParam(event, 'id')
return { id }
})
// server/api/videos/[id]/comments/[commentId].delete.ts
// → DELETE /api/videos/:id/comments/:commentId4. useStorage 内置 KV 存储
4.1 统一存储抽象
Nitro 内置了 unstorage 库,提供统一的 KV 存储 API,可对接多种后端:
// server/api/cache.get.ts
export default defineEventHandler(async () => {
const storage = useStorage()
// 基本操作
await storage.setItem('views:video-123', 42)
const views = await storage.getItem('views:video-123') // 42
await storage.removeItem('views:video-123')
// 检查存在
const exists = await storage.hasItem('views:video-123')
// 列出所有 key
const keys = await storage.getKeys('views') // ['views:video-123', ...]
return { views }
})4.2 配置存储驱动
// nuxt.config.ts
export default defineNuxtConfig({
nitro: {
storage: {
// 默认:内存存储(开发环境)
cache: { driver: 'memory' },
// Redis
redis: {
driver: 'redis',
host: '127.0.0.1',
port: 6379,
},
// 文件系统
data: {
driver: 'fs',
base: './data/storage',
},
// Cloudflare KV
kv: {
driver: 'cloudflare-kv-binding',
binding: 'MY_KV',
},
},
},
})使用时通过前缀区分存储:
// 使用 redis 存储
const redis = useStorage('redis')
await redis.setItem('session:abc', { userId: 1 })
// 使用文件存储
const data = useStorage('data')
await data.setItem('config.json', { theme: 'dark' })4.3 缓存场景
// server/api/videos/trending.get.ts
export default defineCachedEventHandler(async () => {
// 这个 handler 的响应会被自动缓存
const videos = await db.query.videos.findMany({
orderBy: desc(videos.views),
limit: 20,
})
return videos
}, {
maxAge: 60 * 5, // 缓存 5 分钟
staleMaxAge: 60 * 60, // 过期后仍可用 1 小时(SWR 模式)
name: 'trending-videos',
})defineCachedEventHandler 是 Nitro 内置的缓存装饰器——底层使用 useStorage('cache'),自动处理缓存读写和过期策略。
5. Nitro 插件与生命周期钩子
5.1 Nitro 插件
server/plugins/ 中的文件在服务启动时执行一次,用于初始化全局资源:
// server/plugins/01.database.ts
export default defineNitroPlugin(async (nitroApp) => {
// 1. 初始化数据库连接
await db.execute(sql`SELECT 1`)
console.log('✅ Database connected')
// 2. 注册全局钩子
nitroApp.hooks.hook('request', (event) => {
event.context.requestStart = Date.now()
})
nitroApp.hooks.hook('afterResponse', (event) => {
const duration = Date.now() - (event.context.requestStart || 0)
console.log(`${event.path} completed in ${duration}ms`)
})
})插件按文件名排序执行,可以用数字前缀控制顺序。
5.2 Nitro 钩子一览
| 钩子 | 触发时机 | 典型用途 |
|---|---|---|
request | 请求进入 | 注入请求上下文 |
beforeResponse | 响应发送前 | 修改响应头、记录日志 |
afterResponse | 响应发送后 | 性能监控、清理资源 |
error | 未捕获错误 | 错误上报(Sentry) |
close | 服务关闭 | 关闭数据库连接 |
// 优雅关闭:释放数据库连接
export default defineNitroPlugin((nitroApp) => {
nitroApp.hooks.hook('close', async () => {
await db.$client.end()
console.log('Database connection closed')
})
})5.3 请求级钩子 vs 中间件
两者都能拦截请求,区别在于:
- 中间件(
server/middleware/):可以返回值拦截请求、路径匹配更灵活 - 钩子(
nitroApp.hooks):更底层,适合基础设施级逻辑(监控、日志),不能直接拦截请求
原则:业务逻辑用中间件,基础设施用钩子。
6. 运行时配置(runtimeConfig)
6.1 公开配置 vs 私有配置
// nuxt.config.ts
export default defineNuxtConfig({
runtimeConfig: {
// 私有:仅服务端可访问(不会暴露给客户端)
jwtSecret: process.env.JWT_SECRET || '',
databaseUrl: process.env.DATABASE_URL || '',
openaiApiKey: process.env.OPENAI_API_KEY || '',
// 公开:客户端也能访问
public: {
apiBase: process.env.API_BASE || 'http://localhost:3000',
appName: 'AI Video Platform',
},
},
})6.2 服务端读取
// server/api/config.get.ts
export default defineEventHandler(() => {
const config = useRuntimeConfig()
// ✅ 服务端可读全部配置
console.log(config.jwtSecret) // 私有配置
console.log(config.public.apiBase) // 公开配置
// ❌ 绝对不要返回私有配置给客户端
return { appName: config.public.appName }
})6.3 环境变量覆盖规则
Nitro 支持通过环境变量在运行时覆盖配置,无需重新构建:
| 配置项 | 环境变量名 | 说明 |
|---|---|---|
runtimeConfig.jwtSecret | NUXT_JWT_SECRET | 自动映射 |
runtimeConfig.databaseUrl | NUXT_DATABASE_URL | 自动映射 |
runtimeConfig.public.apiBase | NUXT_PUBLIC_API_BASE | 公开配置 |
规则:NUXT_ + 大写蛇形命名。这意味着 Docker 部署时只需设置环境变量,同一份构建产物可以在不同环境运行。
# Docker 部署示例
docker run -e NUXT_DATABASE_URL=postgres://prod:5432/app \
-e NUXT_JWT_SECRET=xxx \
-e NUXT_PUBLIC_API_BASE=https://api.example.com \
my-nuxt-app7. 定时任务与后台处理
7.1 Nitro 定时任务
Nitro 支持通过配置定义定时任务:
// nuxt.config.ts
export default defineNuxtConfig({
nitro: {
experimental: {
tasks: true, // 启用任务调度
},
scheduledTasks: {
// 每 5 分钟清理过期缓存
'*/5 * * * *': ['cache:cleanup'],
// 每天凌晨统计视频数据
'0 2 * * *': ['analytics:daily'],
},
},
})7.2 定义任务
// server/tasks/cache/cleanup.ts
export default defineTask({
meta: {
name: 'cache:cleanup',
description: '清理过期的缓存数据',
},
async run() {
const storage = useStorage('cache')
const keys = await storage.getKeys()
let cleaned = 0
for (const key of keys) {
const meta = await storage.getMeta(key)
if (meta?.mtime && Date.now() - meta.mtime > 3600 * 1000) {
await storage.removeItem(key)
cleaned++
}
}
return { result: `Cleaned ${cleaned} expired cache entries` }
},
})// server/tasks/analytics/daily.ts
export default defineTask({
meta: {
name: 'analytics:daily',
description: '每日视频数据统计',
},
async run() {
const yesterday = new Date(Date.now() - 86400 * 1000)
const stats = await db.select({
totalViews: sum(videos.views),
newVideos: count(),
}).from(videos).where(gte(videos.createdAt, yesterday))
await db.insert(dailyStats).values({
date: yesterday.toISOString().split('T')[0],
...stats[0],
})
return { result: stats[0] }
},
})7.3 手动触发任务
开发时可以通过 API 手动触发任务(生产环境应保护此接口):
// 通过 Nitro DevTools 或 API 触发
await $fetch('/_nitro/tasks/cache:cleanup', { method: 'POST' })本章小结
- Nitro 定位:Nuxt4 的服务引擎,独立于 Nuxt,可编译到 20+ 平台
- 跨平台部署:改一行
preset配置即可切换部署目标,业务代码零修改 - H3 事件模型:统一的
H3Event+ 60+ 工具函数,比 Express 更轻更快,可 tree-shake - server/ 目录:
api/(JSON API)、routes/(自定义路由)、middleware/(拦截器)、utils/(自动导入)、plugins/(启动时初始化)、tasks/(定时任务) - useStorage:统一 KV 存储 API,可对接内存、Redis、文件系统、Cloudflare KV 等
- 生命周期钩子:
request/beforeResponse/afterResponse/error/close,基础设施级逻辑 - runtimeConfig:私有配置仅服务端可见,公开配置客户端可见;
NUXT_环境变量运行时覆盖 - 定时任务:
defineTask+ cron 表达式,内置任务调度无需外部 cron