logo
  • 指南
  • 配置
  • 插件
  • API
  • 示例
  • 社区
  • Modern.js 2.x 文档
  • 简体中文
    • 简体中文
    • English
    • 开始
      介绍
      快速上手
      版本升级
      名词解释
      技术栈
      核心概念
      页面入口
      构建工具
      Web 服务器
      基础功能
      路由
      路由基础
      配置式路由
      数据管理
      数据获取
      数据写入
      数据缓存
      渲染
      服务端渲染(SSR)
      服务端流式渲染(Streaming SSR)
      渲染缓存
      静态站点生成(SSG)
      渲染预处理 (Render Preprocessing)
      样式开发
      引入 CSS
      使用 CSS Modules
      使用 CSS-in-JS
      使用 Tailwind CSS
      HTML 模板
      引用静态资源
      引用 JSON 文件
      引用 SVG 资源
      引用 Wasm 资源
      调试
      数据模拟(Mock)
      网络代理
      使用 Rsdoctor
      使用 Storybook
      测试
      Playwright
      Vitest
      Jest
      Cypress
      路径别名
      环境变量
      构建产物目录
      部署应用
      进阶功能
      使用 Rspack
      使用 BFF
      基础用法
      运行时框架
      扩展 BFF Server
      扩展一体化调用 SDK
      文件上传
      跨项目调用
      优化页面性能
      代码分割
      静态资源内联
      产物体积优化
      React Compiler
      提升构建性能
      浏览器兼容性
      配置底层工具
      源码构建模式
      服务端监控
      Monitors
      日志事件
      指标事件
      国际化
      基础概念
      快速开始
      配置说明
      语言检测
      资源加载
      路由集成
      API 参考
      高级用法
      最佳实践
      自定义 Web Server
      专题详解
      模块联邦
      简介
      开始使用
      应用级别模块
      服务端渲染
      部署
      集成国际化能力
      常见问题
      依赖安装问题
      命令行问题
      构建相关问题
      热更新问题
      已下线功能
      📝 编辑此页面
      上一页最佳实践下一页简介

      #自定义 Web Server

      Modern.js 将大部分项目需要的服务端能力都进行了封装,通常项目无需进行服务端开发。但在有些开发场景下,例如用户鉴权、请求预处理、添加页面渲染骨架等,项目仍需要对服务端进行定制。

      #开启自定义 Web Server

      Tip

      必须确保 Modern.js 版本是 x.67.5 及以上。

      开发者可以在项目根目录执行 pnpm run new 命令,开启「自定义 Web Server」功能:

      ? 请选择你想要的操作 创建工程元素
      ? 请选择创建元素类型 新建「自定义 Web Server」源码目录

      执行命令后,项目目录下会自动创建 server/modern.server.ts 文件,可以在这个文件中编写自定义逻辑。

      #自定义 Web Server 能力

      Modern.js 的服务器基于 Hono 实现,在最新版本的自定义 Web Server 中,我们向用户暴露了 Hono 的中间件能力,你可以参考 Hono 文档 了解更多用法。

      server/modern.server.ts 文件中添加如下配置来扩展 Server:

      • 中间件(Middleware)
      • 渲染中间件(RenderMiddleware)
      • 服务端插件(Plugin)

      其中 Plugin 中可以定义 Middleware 与 RenderMiddleware。 中间件加载流程如下图所示:

      #基本配置

      server/modern.server.ts
      import { defineServerConfig } from '@modern-js/server-runtime';
      
      export default defineServerConfig({
        middlewares: [], // 中间件
        renderMiddlewares: [], // 渲染中间件
        plugins: [], // 插件
        onError: () => {}, // 错误处理
      });

      #类型定义

      defineServerConfig 类型定义如下:

      import type { MiddlewareHandler } from 'hono';
      
      type MiddlewareObj = {
        name: string;
        path?: string;
        method?: 'options' | 'get' | 'post' | 'put' | 'delete' | 'patch' | 'all';
        handler: MiddlewareHandler | MiddlewareHandler[];
      };
      type ServerConfig = {
        middlewares?: MiddlewareObj[];
        renderMiddlewares?: MiddlewareObj[];
        plugins?: ServerPlugin[];
        onError?: (err: Error, c: Context) => Promise<any> | any;
      };

      #Middleware

      Middleware 支持在 Modern.js 服务的请求处理与页面路由的流程前后,执行自定义逻辑。 即自定义逻辑既要处理接口路由,也要作用于页面路由,那么 Middleware 是不二选择。

      Note

      如果仅需要处理 BFF 接口路由,可以通过检查 req.path 是否以 BFF prefix 开头,来判断是否为 BFF 接口请求。

      使用姿势如下:

      server/modern.server.ts
      import {
        defineServerConfig,
        type MiddlewareHandler,
      } from '@modern-js/server-runtime';
      
      export const handler: MiddlewareHandler = async (c, next) => {
        const monitors = c.get('monitors');
        const start = Date.now();
      
        await next();
      
        const end = Date.now();
        // 上报耗时
        monitors.timing('request_timing', end - start);
      };
      
      export default defineServerConfig({
        middlewares: [
          {
            name: 'request-timing',
            handler,
          },
        ],
      });
      Warning

      必须执行 next 函数才会执行后续的 Middleware。

      #RenderMiddleware

      如果只需要处理页面渲染的前后执行逻辑,modern.js 也提供了渲染中间件,使用姿势如下:

      server/modern.server.ts
      import {
        defineServerConfig,
        type MiddlewareHandler,
      } from '@modern-js/server-runtime';
      
      // 注入 render 性能指标
      const renderTiming: MiddlewareHandler = async (c, next) => {
        const start = Date.now();
      
        await next();
      
        const end = Date.now();
        c.res.headers.set('server-timing', `render; dur=${end - start}`);
      };
      
      // 修改响应体
      const modifyResBody: MiddlewareHandler = async (c, next) => {
        await next();
      
        const { res } = c;
        const text = await res.text();
        const newText = text.replace('<body>', '<body> <h3>bytedance</h3>');
      
        c.res = c.body(newText, {
          status: res.status,
          headers: res.headers,
        });
      };
      
      export default defineServerConfig({
        renderMiddlewares: [
          {
            name: 'render-timing',
            handler: renderTiming,
          },
          {
            name: 'modify-res-body',
            handler: modifyResBody,
          },
        ],
      });

      #Plugin

      Modern.js 支持在自定义插件中为 Server 添加上述 Middleware 及 RenderMiddleware,使用姿势如下:

      server/plugins/server.ts
      import type { ServerPlugin } from '@modern-js/server-runtime';
      
      export default (): ServerPlugin => ({
        name: 'serverPlugin',
        setup(api) {
          api.onPrepare(() => {
            const { middlewares, renderMiddlewares } = api.getServerContext();
      
            // 注入服务端数据,供页面 dataLoader 消费
            middlewares?.push({
              name: 'server-plugin-middleware',
              handler: async (c, next) => {
                c.set('message', 'hi modern.js');
                await next();
                // ...
              },
            });
      
            // 重定向
            renderMiddlewares?.push({
              name: 'server-plugin-render-middleware',
              handler: async (c, next) => {
                const user = getUser(c.req);
                if (!user) {
                  return c.redirect('/login');
                }
      
                await next();
              },
            });
          });
        },
      });
      server/modern.server.ts
      import { defineServerConfig } from '@modern-js/server-runtime';
      import serverPlugin from './plugins/serverPlugin';
      
      export default defineServerConfig({
        plugins: [serverPlugin()],
      });
      src/routes/page.data.ts
      import { useHonoContext } from '@modern-js/server-runtime';
      import { defer } from '@modern-js/runtime/router';
      
      export default () => {
        const ctx = useHonoContext();
        // SSR 场景消费服务端注入的数据
        const message = ctx.get('message');
      
        // ...
      };

      #onError

      onError 是一个全局错误处理函数,用于捕获和处理 Modern.js server 中的所有未捕获错误。通过自定义 onError 函数,开发者可以统一处理不同类型的错误,返回自定义的错误响应,实现错误日志记录、错误分类处理等功能。

      以下是一个基本的 onError 配置示例:

      server/modern.server.ts
      import { defineServerConfig } from '@modern-js/server-runtime';
      
      export default defineServerConfig({
        onError: (err, c) => {
          // 记录错误日志
          console.error('Server error:', err);
      
          // 根据不同的错误类型返回不同的响应
          if (err instanceof SyntaxError) {
            return c.json({ error: 'Invalid JSON' }, 400);
          }
      
          // 根据请求路径定制 bff 异常响应
          if (c.req.path.includes('/api')) {
            return c.json({ message: 'API error occurred' }, 500);
          }
      
          return c.text('Internal Server Error', 500);
        },
      });

      #旧版 API(废弃)

      Warning

      旧版 API 已不再兼容,扩展 Server 能力请移步 自定义 Web Server,迁移指南参考 迁移至新版自定义 Web Server。

      #Unstable Middleware

      Modern.js 支持为 Web Server 添加渲染中间件,支持在处理页面路由的前后执行自定义逻辑。

      server/index.ts
      import {
        UnstableMiddleware,
        UnstableMiddlewareContext,
      } from '@modern-js/server-runtime';
      
      const time: UnstableMiddleware = async (c: UnstableMiddlewareContext, next) => {
        const start = Date.now();
      
        await next();
      
        const end = Date.now();
      
        console.log(`dur=${end - start}`);
      };
      
      export const unstableMiddleware: UnstableMiddleware[] = [time];

      #Hook

      Modern.js 提供的 Hook 用于控制 Web Server 中的特定逻辑,所有的页面请求都会经过 Hook。

      目前提供了两种 Hook,分别是 AfterMatch 和 AfterRender,开发者可以在 server/index.ts 中这样写:

      import type {
        AfterMatchHook,
        AfterRenderHook,
      } from '@modern-js/server-runtime';
      
      export const afterMatch: AfterMatchHook = (ctx, next) => {
        next();
      };
      
      export const afterRender: AfterRenderHook = (ctx, next) => {
        next();
      };

      项目在使用 Hook 时,应该有以下最佳实践:

      1. 在 afterMatch 中做权限校验。
      2. 在 afterMatch 做 Rewrite 和 Redirect。
      3. 在 afterRender 中做 HTML 内容注入。

      #迁移至新版自定义 Web Server

      #迁移背景

      Modern.js Server 在不断演进,为了提供更强大的功能,我们对中间件和 Server 插件的定义和使用方式进行了优化。 虽然旧版 API 仍被兼容,但我们强烈建议您按照本指南进行迁移,以充分利用新版的优势。

      #迁移步骤

      1. 升级 Modern.js 版本至 x.67.5 及以上。
      2. 按照新版定义方式,在 server/modern.server.ts 中配置中间件或插件。
      3. 将 server/index.ts 自定义逻辑迁移到中间件或插件中,并参考 Context 和 Next 差异,更新您的代码。

      #Context 差异

      新版中间件 handler 类型为 Hono 的 MiddlewareHandler,即 Context 类型为 Hono Context。对比旧版自定义 Web Server 中 Context 差异如下:

      #UnstableMiddleware

      type Body = ReadableStream | ArrayBuffer | string | null;
      
      type UnstableMiddlewareContext<
        V extends Record<string, unknown> = Record<string, unknown>,
      > = {
        request: Request;
        response: Response;
        get: Get<V>;
        set: Set<V>;
        // 当前匹配到的路由信息
        route: string;
        header: (name: string, value: string, options?: { append?: boolean }) => void;
        status: (code: number) => void;
        redirect: (location: string, status?: number) => Response;
        body: (data: Body, init?: ResponseInit) => Response;
        html: (
          data: string | Promise<string>,
          init?: ResponseInit,
        ) => Response | Promise<Response>;
      };

      UnstableMiddleware Context 和 Hono Context 的具体差异:

      UnstableMiddlewareHono说明
      c.requestc.req.raw参考 HonoRequest raw 文档
      c.responsec.res参考 Hono Context res 文档
      c.routec.get('route')获取应用上下文信息
      loaderContext.gethonoContext.get通过 c.set 注入数据后 dataLoader 中消费:旧版通过 loaderContext.get 获取,新版参考 Plugin 示例

      #Middleware

      type MiddlewareContext = {
        response: {
          set: (key: string, value: string) => void;
          status: (code: number) => void;
          getStatus: () => number;
          cookies: {
            set: (key: string, value: string, options?: any) => void;
            clear: () => void;
          };
          raw: (
            body: string,
            { status, headers }: { status: number; headers: Record<string, any> },
          ) => void;
          locals: Record<string, any>;
        };
        request: {
          url: string;
          host: string;
          pathname: string;
          query: Record<string, any>;
          cookie: string;
          cookies: {
            get: (key: string) => string;
          };
          headers: IncomingHttpHeaders;
        };
        source: {
          req: IncomingMessage;
          res: ServerResponse;
        };
      };

      Middleware Context 和 Hono Context 的具体差异:

      UnstableMiddlewareHono说明
      c.request.cookiegetCookie()参考 Hono Cookie Helper 文档
      c.request.pathnamec.req.path参考 HonoRequest path 文档
      c.request.url-Hono c.req.url 为完整请求路径,自行通过 url 计算
      c.request.hostc.req.header('Host')通过 header 获取 host
      c.request.queryc.req.query()参考 HonoRequest query 文档
      c.request.headersc.req.header()参考 HonoRequest header 文档
      c.response.setc.res.headers.set例:c.res.headers.set('custom-header', '1')
      c.response.statusc.status例:c.status(201)
      c.response.cookiesc.header例:c.header('Set-Cookie', 'user_id=123')
      c.response.rawc.res参考 Hono Context res 文档

      #Hook

      type HookContext = {
        response: {
          set: (key: string, value: string) => void;
          status: (code: number) => void;
          getStatus: () => number;
          cookies: {
            set: (key: string, value: string, options?: any) => void;
            clear: () => void;
          };
          raw: (
            body: string,
            { status, headers }: { status: number; headers: Record<string, any> },
          ) => void;
        };
        request: {
          url: string;
          host: string;
          pathname: string;
          query: Record<string, any>;
          cookie: string;
          cookies: {
            get: (key: string) => string;
          };
          headers: IncomingHttpHeaders;
        };
      };
      
      type AfterMatchContext = HookContext & {
        router: {
          redirect: (url: string, status: number) => void;
          rewrite: (entry: string) => void;
        };
      };
      
      type AfterRenderContext = {
        template: {
          get: () => string;
          set: (html: string) => void;
          prependHead: (fragment: string) => void;
          appendHead: (fragment: string) => void;
          prependBody: (fragment: string) => void;
          appendBody: (fragment: string) => void;
        };
      };

      Hook Context 大部分和 Middleware Context 一致,因此我们要额外关注不同 Hook 多余的部分。

      UnstableMiddlewareHono说明
      router.redirectc.redirect参考 Hono Context redirect 文档
      router.rewrite-暂时没有提供对应的能力
      template APIc.res参考 Hono Context res 文档

      #Next API 差异

      在 Middleware 和 Hook 中,即使不执行 next,渲染函数也会执行。 在新的设计中,必须执行 next 函数才会执行后续的 Middleware。