Modern.js 将大部分项目需要的服务端能力都进行了封装,通常项目无需进行服务端开发。但在有些开发场景下,例如用户鉴权、请求预处理、添加页面渲染骨架等,项目仍需要对服务端进行定制。
必须确保 Modern.js 版本是 x.67.5 及以上。
开发者可以在项目根目录执行 pnpm run new 命令,开启「自定义 Web Server」功能:
? 请选择你想要的操作 创建工程元素
? 请选择创建元素类型 新建「自定义 Web Server」源码目录执行命令后,项目目录下会自动创建 server/modern.server.ts 文件,可以在这个文件中编写自定义逻辑。
Modern.js 的服务器基于 Hono 实现,在最新版本的自定义 Web Server 中,我们向用户暴露了 Hono 的中间件能力,你可以参考 Hono 文档 了解更多用法。
server/modern.server.ts 文件中添加如下配置来扩展 Server:
其中 Plugin 中可以定义 Middleware 与 RenderMiddleware。 中间件加载流程如下图所示:
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 支持在 Modern.js 服务的请求处理与页面路由的流程前后,执行自定义逻辑。 即自定义逻辑既要处理接口路由,也要作用于页面路由,那么 Middleware 是不二选择。
如果仅需要处理 BFF 接口路由,可以通过检查 req.path 是否以 BFF prefix 开头,来判断是否为 BFF 接口请求。
使用姿势如下:
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,
},
],
});
必须执行 next 函数才会执行后续的 Middleware。
如果只需要处理页面渲染的前后执行逻辑,modern.js 也提供了渲染中间件,使用姿势如下:
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,
},
],
});Modern.js 支持在自定义插件中为 Server 添加上述 Middleware 及 RenderMiddleware,使用姿势如下:
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();
},
});
});
},
});import { defineServerConfig } from '@modern-js/server-runtime';
import serverPlugin from './plugins/serverPlugin';
export default defineServerConfig({
plugins: [serverPlugin()],
});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 是一个全局错误处理函数,用于捕获和处理 Modern.js server 中的所有未捕获错误。通过自定义 onError 函数,开发者可以统一处理不同类型的错误,返回自定义的错误响应,实现错误日志记录、错误分类处理等功能。
以下是一个基本的 onError 配置示例:
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);
},
});