Node+Express全页缓存打孔:如何将另一路由渲染结果注入当前响应
实现方案
优先选择:异步流程改造(完全可行,无性能问题)
Express原生支持异步处理逻辑,不需要强行将异步请求转同步,你原有的代码仅需补充async/await语法适配即可正常运行,不需要调整整体架构。
你原有代码的核心问题是调用getRemoteInclude时没有等待异步请求执行完成,直接把Promise对象当成了字符串使用,修改后的可运行代码如下:
async function getRemoteInclude(req, content) { // remote include format: {{ includeRemote('path') }} const handlebarRegex = '(?<={{)(.*?)(?=}})'; const includeRegex = '(?<=includeRemote[(])(.*?)(?=[)])'; const replaceHandlebarRegex = /\{\{[^\}]+\}\}/g; let parsedContent = content; let foundHandlebars = content.match(handlebarRegex); if (foundHandlebars) { let foundInclude = foundHandlebars[0].match(includeRegex); if (foundInclude) { const axios = require('axios'); let path = 'http://expresscache.com:3000/test'; let response = await axios.get(path); // 修复axios返回值字段错误:响应内容存放在data字段而非body return parsedContent.replace(replaceHandlebarRegex, response.data); } } return content; } function interceptRender() { return function (req, res, next) { // 给重写的render方法加async标识支持异步逻辑 res.render = async function (view, options, callback) { var self = this; options = options || res.locals; // 将回调形式的app.render包装为Promise调用 const renderedContent = await new Promise((resolve, reject) => { self.app.render(view, options, (err, content) => { if (err) return reject(err); resolve(content); }) }); if (!callback) { // 等待远程内容注入完成再返回响应 const finalContent = await getRemoteInclude(req, renderedContent); self.locals.body = finalContent; self.send(finalContent); } else { callback(null, renderedContent); } } next(); } } module.exports = { interceptRender: interceptRender };
逻辑优化建议
同一服务内部不需要通过HTTP请求调用路由,可以把对应路由的渲染逻辑抽成公共函数直接调用,避免不必要的网络开销,性能比发HTTP请求高10倍以上。
备选方案:同步HTTP请求(不推荐,仅作调试使用)
如果强行需要同步执行请求,可以使用sync-request库,但该方案会阻塞Node事件循环,高并发场景下会导致所有请求卡顿,完全不适合生产环境使用。
其他替代实现思路
- 缓存分块:将页面拆分为独立缓存块,每个块单独配置缓存TTL,渲染时直接拼装各块内容,比正则匹配替换的方案更易维护
- 服务端包含(SSI):如果用Nginx做反向代理,可以直接使用SSI指令实现动态内容注入,不需要在Node层处理,性能更高
- 边缘端包含(ESI):如果有CDN层,支持ESI的CDN可以直接识别ESI标签自动拼装内容,不需要修改服务端逻辑
内容的提问来源于stack exchange,提问作者poor_documentation
相关产品推荐
相关产品推荐

