首页 / Nuxt 4 入门教程 / 服务端中间件

Nuxt 4 入门教程

服务端中间件

本教程共 50 篇 · 第 34 篇 · 更新于 2026-08-08 · 约 7 分钟阅读

NuxtNuxt4server中间件middlewareNitro

本节目标:能在 server/middleware/ 下写出服务端中间件,用来记录日志、统一加响应头或往请求上下文注入数据,并知道它不该做什么。

上两章我们写了 API 接口(处理具体请求)和 Nitro 引擎(支撑运行)。但有些逻辑不想绑在某个接口上,而是想「每个请求来了都先过一遍」——比如记一条访问日志、检查有没有登录态、给所有响应统一加个头。这种「请求守门员」就是服务端中间件

34-1

Nuxt 会自动读取 server/middleware/ 目录下的每个文件,把它们注册成服务端中间件。每当有请求进来(无论最后是落到哪个 API 还是页面),中间件都会先于具体路由处理器执行。

它最适合做三件事:

  • 记录日志:把每个请求的 URL、时间打出来,方便排查。
  • 检查或补充:校验请求头、读取 cookie,把用户信息挂到上下文上供后续使用。
  • 改写请求/响应:统一加响应头(如 CORS、安全头)。

34-2

中间件文件和接口一样,也是导出一个 defineEventHandler。最简单的日志中间件:

export default defineEventHandler((event) => {
  console.log('新请求来了:' + getRequestURL(event))
})

每次收到请求,服务端控制台就会打印一行 URL。热更新照常生效,不用重启。

34-3

中间件最常干的活,是「提前准备好东西,让后面的接口直接用」。h3 的 event 上有一个 context 对象,专门干这个。比如我们在这里做个假的登录校验,把用户 id 挂上去:

export default defineEventHandler((event) => {
  // 真实场景里会解析 cookie / token,这里简化演示
  event.context.auth = { userId: 123 }
})

之后在任意接口里,都能直接读这个 auth

export default defineEventHandler((event) => {
  const auth = event.context.auth

  return { userId: auth?.userId }
})

这样认证逻辑只写一遍,全站接口共享,不用每个文件重复解析。

34-4

中间件也能给响应加头,比如开放跨域(CORS)或一个安全相关的头:

export default defineEventHandler((event) => {
  setResponseHeader(event, 'X-Powered-By', 'Nuxt')
})
Tip

setResponseHeader 也是 h3 的助手。想批量加安全头(CSP、X-Frame-Options 等),中间件是统一入口,比在每个接口里写干净得多。

34-5

这是服务端中间件最容易写错的地方。中间件的设计目的是「看一眼、补一点、然后放行」,它不应该自己返回内容、结束响应或做重定向。换句话说:

  • return 一个值(那会被当成响应体,导致真正的路由永远到不了)。
  • 别调用 event.node.res.end() 去结束请求。
  • 别用 sendRedirect 把请求拐走(除非你非常确定要拦截所有请求)。

正确姿势是:要么什么都不返回,只改 event;要么在条件不满足时 throw createError(...) 中断请求(比如鉴权失败返回 401)。

export default defineEventHandler((event) => {
  const token = getHeader(event, 'authorization')

  if (!token) {
    // 鉴权失败:抛错误中断,而不是返回内容
    throw createError({
      statusCode: 401,
      statusMessage: '未登录',
    })
  }

  // 通过:把用户信息挂上上下文,放行
  event.context.user = { token }
})
Warning

中间件一旦 return 了数据,那个请求就被它「截胡」了,后面的 API/页面处理器不会再执行。如果你的接口突然收不到请求,先检查中间件是不是不小心返回了东西。

34-6

新手常把两个名字相近的东西搞混,这里划清界限:

  • 服务端中间件server/middleware/,本章):跑在服务器上,拦截「每个 HTTP 请求」,用来做日志、鉴权、加头。它操作的是 h3 的 event
  • 路由中间件app/middleware/,第 19 章讲过):跑在「页面导航」层面,拦截「前端路由跳转」,用来做页面级权限控制(比如未登录跳登录页)。它操作的是 Nuxt 的路由上下文。

两者职责不同、所在目录不同、运行环境也不同。需要「每个网络请求都过一遍」用服务端中间件;需要「用户在前端切页面时拦一下」用路由中间件。

34-7

server/middleware/ 下可以有多个文件,它们会按文件名顺序依次执行。如果一个中间件往 event.context 注入了数据,后面的中间件和最终路由都能读到。利用这个顺序,你可以把「通用准备」放在靠前文件,「业务相关检查」放在靠后文件,逻辑更清晰。

Note

中间件对所有请求生效,包括静态资源和页面请求。所以里面的逻辑要尽量轻量,别做太重的事,否则会拖慢每一个请求。

34-8

把前面几节压成一张清单,写中间件前扫一眼:

能做的:

  • 读请求信息:URL、请求头、cookie(getRequestURLgetHeadergetCookie)。
  • event.context 挂数据,供后续接口读取。
  • setResponseHeader 统一加响应头。
  • 条件不满足时 throw createError 中断请求(如 401)。

不能做的:

  • 返回响应体:return 一个值会把请求「截胡」,真正的路由收不到。
  • 调用 event.node.res.end() 提前结束响应。
  • 随意 sendRedirect 把请求拐走,除非你确实想拦下所有请求。
  • 做太重的事(查库、调外部接口):中间件对每个请求生效,会拖慢全站。

34-9

除了前面举例的,服务端中间件还适合这几类活。

记访问日志,把方法、URL、耗时打出来,是排查问题的第一步:

export default defineEventHandler((event) => {
  const start = Date.now()
  event.node.res.on('finish', () => {
    console.log(getMethod(event), getRequestURL(event), Date.now() - start + 'ms')
  })
})

改写请求上下文,比如根据子域名决定租户,挂到 event.context.tenant,后面所有接口直接读:

export default defineEventHandler((event) => {
  const host = getRequestHost(event)
  event.context.tenant = host.split('.')[0]
})

鉴权「预告」。中间件只做轻量校验和身份识别,把用户信息挂上 event.context,真正的权限判断留给各接口——这叫鉴权预告。重活(比如查库验证 token 合法性)如果很重,更适合放在具体接口或专门的工具函数里,别全压在中间件。

Note

中间件对静态资源、页面 HTML 请求同样生效。给响应加头、记日志这类轻操作没问题;但别在中间件里调 readBody,它不仅对所有请求生效,还会和消费 body 的接口冲突。

34-10

服务端中间件是放在 server/middleware/ 下的「请求守门员」:每个请求都会先经过它,适合记日志、统一加响应头、往 event.context 注入共享数据。记住铁律——中间件只检查或补充,不要返回内容、不要结束响应(鉴权失败用 throw createError)。它和前端的「路由中间件」是两码事,别混。下一篇我们看怎么把密钥、接口地址这类配置安全地交给应用——runtimeConfig 与环境变量。

34-11 中间件的执行顺序与调试

当项目里有多个服务端中间件时,它们的执行顺序按文件名的字母排列。如果你需要控制顺序,可以在文件名前加数字前缀,如 01.cors.ts02.auth.ts。数字小的先执行。这种机制在需要按特定顺序处理请求时非常重要,比如先处理跨域再验证身份。

调试中间件的一个实用技巧是在关键位置加 console.log,打印请求的方法、路径和当前中间件的名称。这能帮你确认中间件是否被触发、执行顺序是否正确。在服务端日志里,这些打印内容会出现在终端的输出中。

要注意的一个陷阱是:中间件的修改对同一个请求的后续中间件和路由处理函数立即可见。比如你在第一个中间件里往 event.context 挂了用户信息,后面的中间件和路由都能直接读到。这种共享机制很方便,但也意味着如果某个中间件忘了做它该做的事,后面的代码可能会因为缺少预期数据而出错。排查这类问题时,逐个检查中间件的执行链路是最有效的方法。