H3 嵌套应用

2026-10-30 15:38 更新

一个后端长到一定程度,总会出现「这块归另一个团队」「这段已经用别的框架写好了」的情况。把所有东西塞进一个 H3 实例既难维护,也不现实。

H3 的答案很直接:mount() 把另一个应用挂到某个路径前缀下,路由和中间件自动合并。挂进来的可以是一个 H3 实例,也可以是任何 Request => Response 的 Web 应用。

挂载一个 H3 子应用

import { H3, serve } from "h3";

// 子应用:自带一条中间件和一条通配路由
const nestedApp = new H3()
  .use((event) => {
    event.res.headers.set("x-api", "1"); // 只对 /api 下的请求生效
  })
  .get("/**:slug", (event) => ({
    pathname: event.url.pathname, // 完整路径,不剥前缀
    slug: event.context.params?.slug, // 通配捕获到的是剩余部分
  }));

// 主应用把子应用挂到 /api
const app = new H3().mount("/api", nestedApp);

请求 /api/test 时,返回值得注意两处:

字段 值 说明
pathname /api/test event.url.pathname 始终是真实完整路径,不会因为挂载而剥掉前缀
slug /test 通配段捕获到的是去掉前缀后的剩余部分

这个区别很重要:路径是全局的,参数是相对的。所以子应用里写重定向、拼内部链接时可以直接用 pathname,而做路由分发时用 params。

📌 v1 里用 useBase() 给应用加前缀,v2 改成了 withBase();同时 event.path 已弃用,要拿完整路径用 event.url.pathname + event.url.search。这也是 v2「路径永远不省略前缀」这条设计的一部分。

全局配置与钩子不会被继承

子应用的 H3Config 与全局钩子(onRequest、onResponse、onError)不会随挂载带入主应用:

// 子应用的配置只在子应用自己 serve 时才生效
const nestedApp = new H3({
  onError: (error) => console.error("子应用的钩子(挂载后不会被调用)", error),
});

const app = new H3().mount("/api", nestedApp);

所以官方建议:钩子和全局配置一律设在主应用上。如果确实需要子应用自己的错误处理,用 onError 中间件而不是全局钩子(见 2.6)。

挂载任意 Web 标准应用

两者差异先用一张图固定下来:

挂 H3 子应用保留前缀,挂 Web 应用移除前缀

只要对方符合 Request => Response,就能挂进来——Hono、Elysia 这类框架都符合:

import { H3 } from "h3";
import { Hono } from "hono";
import { Elysia } from "elysia";

// 两个第三方应用分别挂到不同前缀,互不干扰
const app = new H3()
  .mount(
    "/elysia",
    new Elysia().get("/test", () => "Hello Elysia!"),
  )
  .mount(
    "/hono",
    new Hono().get("/test", (c) => c.text("Hello Hono!")),
  );

和挂 H3 子应用有一处不同,必须记住:

挂载 Web 标准应用时,前缀会从 request.url 里移除;挂 H3 子应用时不移除。

原因是 Web 应用期望自己收到「以自己为根」的请求。所以 Hono 里写 /test 路由,对应外部路径是 /hono/test,而它看到的 request.url 已经变成 /test。

反过来:把 H3 挂到别的框架上

这个能力是对称的。H3 应用本身就是一个 .fetch 处理器,可以直接挂到 Hono 或 Elysia 的路由下(各家都提供 mount)。迁移期特别有用:新老框架并存,按路径逐步切换。

该不该拆

情况 建议
单团队、路由量中等 不拆,一个实例 + 路由规则足够
按业务域边界清晰(如 /admin、/openapi) 拆成子应用,各自维护中间件与路由
已有其他团队的 Web 应用 直接 mount,不重写
需要统一鉴权、日志、错误处理 拆,但钩子与全局中间件集中在主应用

拆分的代价要认清:中间件的作用范围变复杂了。主应用注册的 app.use("/api/**") 能覆盖挂载进去的子应用路径,但子应用内部的中间件只在子应用内生效,排查顺序问题时需要同时看两层。

排查表

现象 原因
子应用里的重定向跳到了错误地址 用了相对路径拼接,未考虑 pathname 是全路径
挂载的 Hono 应用所有路由 404 对方按「已剥前缀」的 URL 匹配,你按带前缀的路径注册了
子应用抛错没有日志 用了子应用的全局钩子,挂载后不生效,改用 onError 中间件
子应用的中间件没拦住主应用的路由 正常现象:子应用中间件只作用于子应用内部

延伸阅读

  • 挂载在生命周期里的位置:见 2.1 请求生命周期
  • 路径归一化与通配捕获:见 2.2 路由
  • 错误钩子与中间件的取舍:见 2.6 错误处理
  • 单个 handler 而非整棵应用如何转换:见 2.4 事件处理器
以上内容是否对您有帮助:
在线笔记
App下载
App下载

扫描二维码

下载编程狮App

公众号
微信公众号

编程狮公众号