> For AI agents: the complete documentation index is available at /zh/llms.txt, the full documentation bundle is available at /zh/llms-full.txt.

# 自定义 Web Server

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

要在 Modern.js 项目中使用自定义 Web Server，请按照以下步骤操作：

1. 安装 `@modern-js/server-runtime` 依赖

如果项目尚未安装 `@modern-js/server-runtime` 依赖，请先安装：

```bash
pnpm add @modern-js/server-runtime
```

:::tip 版本一致性
请确保 `@modern-js/server-runtime` 的版本与项目中 `@modern-js/app-tools` 的版本保持一致。Modern.js 的所有官方包使用统一版本号发布，版本不一致可能导致兼容性问题。

请先查看 `@modern-js/app-tools` 的版本，然后安装相同版本的 `@modern-js/server-runtime`：

```bash
# 查看当前 @modern-js/app-tools 的版本
pnpm list @modern-js/app-tools

# 安装相同版本的 @modern-js/server-runtime
pnpm add @modern-js/server-runtime@<版本号>
```

:::

2. 创建 `server` 目录和配置文件

在项目根目录下创建 `server/modern.server.ts` 文件：

```ts title="server/modern.server.ts"
import { defineServerConfig } from '@modern-js/server-runtime';

export default defineServerConfig({
  middlewares: [], // 中间件
  renderMiddlewares: [], // 渲染中间件
  plugins: [], // 插件
  onError: () => {}, // 错误处理
});
```

创建文件后，可以在这个文件中编写自定义逻辑。

3. 你可以将 `server` 目录包含在 `tsconfig.json` 中

```jsonc
{
  // ...
  "include": [
    // ...
    "server",
  ],
}
```

## 自定义 Web Server 能力

Modern.js 的服务器基于 Hono 实现，在最新版本的自定义 Web Server 中，我们向用户暴露了 Hono 的中间件能力，你可以参考 [Hono 文档](https://hono.dev/docs/api/context) 了解更多用法。

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

- **中间件（Middleware）**
- **渲染中间件（RenderMiddleware）**
- **服务端插件（Plugin）**

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

![](https://lf3-static.bytednsdoc.com/obj/eden-cn/10eh7nuhpenuhog/server-md-wf.png)
### 基本配置

```ts title="server/modern.server.ts"
import { defineServerConfig } from '@modern-js/server-runtime';

export default defineServerConfig({
  middlewares: [], // 中间件
  renderMiddlewares: [], // 渲染中间件
  plugins: [], // 插件
  onError: () => {}, // 错误处理
});
```

### 类型定义

`defineServerConfig` 类型定义如下:

```ts
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 接口请求。
:::

使用姿势如下：

```ts title="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 也提供了渲染中间件，使用姿势如下：

```ts title="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，使用姿势如下：

```ts title="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();
        },
      });
    });
  },
});
```

```ts title="server/modern.server.ts"
import { defineServerConfig } from '@modern-js/server-runtime';
import serverPlugin from './plugins/serverPlugin';

export default defineServerConfig({
  plugins: [serverPlugin()],
});
```

```ts title="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` 配置示例：

```ts title="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);
  },
});
```
