
本文介绍一种轻量、可复用的 Express 中间件方案,通过匹配 Host 请求头实现多域名共享同一端口、按需加载对应路由或静态资源,无需重构架构即可灵活托管多个独立站点。
本文介绍一种轻量、可复用的 express 中间件方案,通过匹配 `host` 请求头实现多域名共享同一端口、按需加载对应路由或静态资源,无需重构架构即可灵活托管多个独立站点。
在 Express 应用中,有时需要在同一服务器实例上为不同域名(如 site-a.com、site-b.local)提供完全独立的路由逻辑或静态资源服务——这并非反模式,而是常见于多租户管理后台、本地开发环境模拟多站部署、或微前端聚合场景。核心挑战在于:不能在 app.get('/') 等路由处理器中调用 app.use()(因中间件注册必须在请求处理前完成),但又需根据 req.hostname 或 req.headers.host 动态启用特定路由模块。
解决方案是封装一个条件路由中间件(Conditional Router Middleware):它不直接响应请求,而是检查当前请求的 Host 头是否匹配预设域名列表;若匹配,则将控制权交由指定的子路由(Router 实例)或静态服务处理;否则调用 next() 继续后续中间件链。
以下是推荐实现:
✅ 自定义域名路由中间件(domainRouting.js)
// domainRouting.js
module.exports = (hosts, routerOrMiddleware) => {
return (req, res, next) => {
const host = req.headers.host?.split(':')[0] || ''; // 忽略端口部分,兼容 localhost:3000
if (hosts.includes(host)) {
// 若传入的是 Express Router 实例(如 require('./routes')),直接 use
if (typeof routerOrMiddleware === 'function' && routerOrMiddleware.constructor.name === 'Router') {
return routerOrMiddleware(req, res, next);
}
// 若传入的是中间件函数(如 express.static),直接执行
return routerOrMiddleware(req, res, next);
}
next();
};
};
? 提示:使用 req.headers.host 而非 req.hostname 更可靠,因后者可能被代理修改;split(':')[0] 可安全剥离端口号(如 localhost:3000 → localhost)。
✅ 在主应用中按需注册多域名路由
const express = require('express');
const app = express();
const path = require('path');
const forwardForDomain = require('./domainRouting');
// ✅ 站点 A:使用自定义 Router(如 pages/site-A/routes.js 导出的 Router 实例)
const siteARouter = require('./pages/site-A/routes');
app.use(forwardForDomain(['localhost', 'site-a.local', 'site-a.com'], siteARouter));
// ✅ 站点 B:直接托管静态文件(注意:此处需用函数包装,避免提前执行)
app.use(
forwardForDomain(['site-b.local', 'site-b.com'],
express.static(path.join(__dirname, 'pages/site-b-html'))
)
);
// ✅ 站点 C:混合使用(如 React SSR 路由)
const siteCRoutes = require('./pages/site-C/routes');
app.use(forwardForDomain(['site-c.local', 'site-c.com'], siteCRoutes));
// ? 兜底:未匹配域名时返回默认站点
app.use(express.static(path.join(__dirname, 'pages/default')));
app.get('*', (req, res) => {
res.sendFile(path.join(__dirname, 'pages/default/index.html'));
});
⚠️ 关键注意事项
- 中间件顺序至关重要:forwardForDomain 必须在 app.use(express.static(...)) 等通用中间件之前注册,否则匹配失败后请求已被静态服务捕获。
- Router 实例不可复用多次:每个 require('./routes') 应返回全新 Router 实例(确保路由隔离),避免状态污染。
- 开发环境调试:配合 /etc/hosts 添加映射(如 127.0.0.1 site-a.local),即可在浏览器中直接访问 http://site-a.local:3000 触发对应路由。
- 生产环境扩展:可将域名配置从硬编码改为读取环境变量或 JSON 配置文件,便于 CI/CD 管理。
该方案简洁、无侵入性、符合 Express 设计哲学——它不改变原有路由结构,仅通过高阶中间件实现“路由分发器”,让单一 Express 实例真正成为多站点统一入口。










