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.jsnode-serverNode.js传统服务器、Docker
VercelvercelEdge/NodeServerless
Cloudflare Workerscloudflare-pagesWorkers边缘计算
NetlifynetlifyEdge/Node静态 + Functions
Deno Deploydeno-deployDenoDeno 生态
BunbunBun高性能 Node 替代
AWS Lambdaaws-lambdaNode.jsAWS 生态
静态托管staticSSG 纯静态
// 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。核心差异:

特性ExpressH3
设计理念中间件链事件处理器
请求/响应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 API
  • routes/:无前缀,用于非 API 路由(如 sitemap、RSS feed、健康检查)

3.2 HTTP 方法映射

文件名后缀决定 HTTP 方法:

文件名匹配方法
index.ts所有方法
index.get.tsGET
index.post.tsPOST
index.put.tsPUT
index.delete.tsDELETE
index.patch.tsPATCH

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/:commentId

4. 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.jwtSecretNUXT_JWT_SECRET自动映射
runtimeConfig.databaseUrlNUXT_DATABASE_URL自动映射
runtimeConfig.public.apiBaseNUXT_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-app

7. 定时任务与后台处理

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