You need to enable JavaScript to run this app.
优惠活动
大模型
产品
解决方案
定价
更多

Express调用res.render返回JSON响应 未正常渲染EJS页面问题

问题排查思路与解决方案

按优先级排查核心问题

1. 重定向逻辑未终止执行,触发响应头冲突

当前token校验逻辑存在明显流程漏洞:

if (amountOfTokens == undefined || amountOfTokens == null) {
  res.redirect('/dashboard')
}
let numOfRecords = body.length
res.render('dashboard.ejs', {data: req.data, body: body, amountOfTokens: amountOfTokens, numOfRecords: numOfRecords})

当amountOfTokens为空触发重定向时,代码没有中断执行,会继续往下调用res.render,导致同一个请求连续触发两次响应发送:

  • 第一次发送302重定向响应,响应头包含跳转路径
  • 第二次尝试发送渲染后的HTML内容
    此时Node.js会在控制台抛出ERR_HTTP_HEADERS_SENT警告,部分浏览器、反向代理遇到这种响应头错乱的情况,会自动将响应内容按JSON格式解析,就会出现「展示JSON响应头+页面路径」的现象。
    修复方式:重定向后加return终止后续逻辑执行
if (amountOfTokens == undefined || amountOfTokens == null) {
  return res.redirect('/dashboard')
}

2. 响应头Content-Type被前置逻辑意外覆盖

res.render默认会自动将响应头设置为text/html; charset=utf-8,如果最终返回的Content-Type是application/json,说明执行res.render之前已经有逻辑修改了响应头:

  • 检查全局中间件:是否在路由注册前写了统一设置响应类型为json的逻辑,比如全局拦截器里固定写了res.setHeader('Content-Type', 'application/json')的配置
  • 检查authCheck鉴权中间件:是否在鉴权逻辑里,针对未登录/异常场景提前设置了JSON响应头,甚至提前调用res.json()返回数据,但没有加return终止后续路由逻辑执行
  • 检查自定义响应封装方法:如果给res对象挂载了统一返回JSON的工具方法,确认是否在该路由逻辑里被意外触发
    快速排查方式:在res.render调用前打印当前已设置的响应头
console.log('当前响应类型', res.get('Content-Type'))
let numOfRecords = body.length
res.render('dashboard.ejs', {data: req.data, body: body, amountOfTokens: amountOfTokens, numOfRecords: numOfRecords})

如果打印结果已经是application/json,顺着调用栈往上找到修改响应头的逻辑调整即可,也可以在render前手动强制设置响应类型覆盖:

res.type('html')
res.render('dashboard.ejs', /** 传参 */)

3. 请求方式与渲染场景不匹配

如果/findRecord接口是被前端通过axios/fetch等AJAX工具异步调用的,而不是浏览器地址栏直接访问、普通a标签跳转、表单提交触发的:

  • 哪怕后端正常返回HTML内容,浏览器也不会自动跳转渲染页面,只会把HTML内容作为响应结果交给JS逻辑处理,部分AJAX库默认会把响应当做JSON格式解析,就会出现解析失败、直接展示响应原始内容的情况
    修复方式:如果是需要浏览器渲染整页的场景,用普通页面跳转方式访问该路由,不要用AJAX异步请求。

4. 下游接口异常未做拦截

当前调用https://myurl.com/user_records接口时,没有判断HTTP状态码,只要request库没有抛出系统级错误(比如网络不通)就会往下走render逻辑。如果下游接口返回4xx/5xx的JSON格式错误(比如鉴权失败、参数错误),直接把错误JSON传给模板,若模板里有直接输出原始数据的逻辑,也会出现类似返回JSON的现象。
修复方式:增加下游接口状态校验,异常场景单独处理

request.post({
  url: 'https://myurl.com/user_records',
  headers: {
    "Cookie": req.headers?.cookie,
  },
  json: {
    max_date: new Date().valueOf()
  }
}, function(err, httpResponse, body) {
  if (err) {
    console.log(err)
    return res.status(500).send('服务异常')
  }
  // 新增下游接口状态校验
  if (httpResponse.statusCode !== 200) {
    console.log('下游接口返回异常', httpResponse.statusCode, body)
    return res.redirect('/dashboard')
  }
  if (amountOfTokens == undefined || amountOfTokens == null) {
    return res.redirect('/dashboard')
  }
  let numOfRecords = body.length
  res.render('dashboard.ejs', {data: req.data, body: body, amountOfTokens: amountOfTokens, numOfRecords: numOfRecords})
})

额外优化点

给request.post回调加的async关键字没有实际作用——回调内部没有任何await异步逻辑,反而可能因为request库对async回调的兼容问题导致意外流程错误,可以直接删掉。


内容的提问来源于stack exchange,提问作者devdude19289

相关产品推荐
方舟 Agent Plan

超全模态模型 × Harness 升级,最新支持 Deepseek-V4.1-Flash、GLM-5.3 系列、Doubao-Seedream-5.0-pro、Kimi-K3 (部分), 限时 9.9 元起

最近更新时间:2026.08.30 05:45:54