Next.js自定义服务器Express中间件Sentry错误日志配置问题
好的,我来帮你一步步解决这个问题——先从错误中间件的核心原理说起,再拆解你每个尝试的问题,最后给你适配Next.js+Express+Sentry的正确配置方案。
一、Express/Next.js 错误中间件的工作原理
Express 错误中间件的核心规则
Express 对错误处理中间件有个强制要求:必须包含4个参数:(err, req, res, next),少一个参数都不会被识别为错误中间件,而是会被当作普通的请求处理中间件。
当你在路由或其他中间件中调用next(err)时,Express会跳过所有普通中间件,直接找到第一个符合4参数格式的错误中间件执行。这是你所有尝试出问题的核心根源之一。
Next.js + Express 集成的特殊点
用Express作为Next.js的自定义服务器时,错误流转分为两种场景:
- Express自身的API路由/中间件抛出的错误:会正常流转到Express的错误中间件。
- Next.js页面渲染(比如
getInitialProps、组件渲染)抛出的错误:需要通过Next.js的_error.js页面捕获,或者手动把错误传递给Express的错误中间件处理。
二、你的四个尝试为啥出问题?
我们逐个拆解:
尝试1:4参数格式正确,但位置错误
function logError(err, req, res, next) { Sentry.captureMessage(`${err.stack} - ${err.message}`) Sentry.captureException(err) console.log('Error reported to sentry.') next(err) }
问题原因:这个中间件的格式是对的,但你大概率把它放在了Next.js路由中间件(app.get('*', handle))之前。Express的路由匹配是顺序执行的,当Next.js处理请求时抛出的错误,不会流转到它之前的错误中间件——错误中间件必须放在所有路由和普通中间件之后。
尝试2:3参数被当作普通中间件,无响应挂起
function logError(err, req, res) { Sentry.captureMessage(`${err.stack} - ${err.message}`) Sentry.captureException(err) console.log('Error reported to sentry.') }
问题原因:只有3个参数,Express把它当成了普通请求中间件,只有当请求刚好匹配到这个中间件的路由时才会执行。而且你没有调用next()或返回响应,导致请求一直处于挂起状态。
尝试3:3参数+响应对象不兼容
function logError(err, req, res) { Sentry.captureMessage(`${err.stack} - ${err.message}`) Sentry.captureException(err) console.log('Error reported to sentry.') res.statusCode = 500 res.send('Internal server error') }
问题原因:同样是3参数的普通中间件,另外res.send is not a function是因为Next.js封装了响应对象,和Express原生响应对象的API不一致,直接调用Express的响应方法会出错。
尝试4:3参数+错误封装错误
function logError(err, req, res) { Sentry.captureMessage(`${err.stack} - ${err.message}`) Sentry.captureException(err) console.log('Error reported to sentry.') throw new Error(err) }
问题原因:还是3参数的普通中间件,throw new Error(err)时,因为err本身是Error对象,直接传入会调用它的toString()方法,变成[object Object],所以错误栈异常。能执行只是因为请求匹配到了它的路由,不是错误正常流转过来的。
三、正确的配置方案
1. Express服务器端完整配置
在你的服务器入口文件(比如server.js)中,严格按照顺序配置:
const Sentry = require('@sentry/node'); const express = require('express'); const next = require('next'); const dev = process.env.NODE_ENV !== 'production'; const app = next({ dev }); const handle = app.getRequestHandler(); // 1. 初始化Sentry Sentry.init({ dsn: 'YOUR_SENTRY_DSN', // 替换成你的Sentry DSN environment: dev ? 'development' : 'production', }); const server = express(); // 2. 先添加Sentry的请求处理中间件(必须在所有其他中间件之前) server.use(Sentry.Handlers.requestHandler()); // 3. 你的其他Express中间件(比如解析JSON、静态文件) server.use(express.json()); server.use(express.static('public')); // 4. 处理Next.js的所有路由 server.get('*', (req, res) => { return handle(req, res); }); // 5. 自定义错误处理中间件(必须是4参数,放在所有路由之后) server.use((err, req, res, next) => { // 上报错误到Sentry Sentry.captureException(err); console.log('Error reported to Sentry:', err.message); // 根据环境返回响应 if (dev) { // 开发环境返回详细错误信息 res.status(500).json({ message: err.message, stack: err.stack, }); } else { // 生产环境返回友好提示 res.status(500).send('Internal Server Error'); } // 如果不需要继续传递错误,就不用调用next(err) }); // 6. 可选:添加Sentry自带的错误处理中间件(自动处理错误并返回响应) server.use(Sentry.Handlers.errorHandler()); // 启动服务器 app.prepare().then(() => { server.listen(3000, (err) => { if (err) throw err; console.log('> Ready on http://localhost:3000'); }); });
2. Next.js页面错误的捕获
如果是页面渲染(比如getInitialProps)抛出的错误,在pages/_error.js中捕获并上报:
import * as Sentry from '@sentry/node'; function Error({ statusCode }) { return ( <div style={{ padding: '2rem' }}> <h1>{statusCode ? `Error ${statusCode}` : 'An error occurred'}</h1> </div> ); } Error.getInitialProps = async ({ res, err }) => { const statusCode = res ? res.statusCode : err ? err.statusCode : 404; // 上报页面错误到Sentry if (err) { Sentry.captureException(err); } return { statusCode }; }; export default Error;
关键注意事项
- 错误中间件位置:必须放在所有路由(包括Next.js的
handle)之后,否则Express找不到它处理错误。 - 4参数强制要求:错误处理中间件必须有
err, req, res, next四个参数,缺一不可。 - Sentry中间件顺序:
Sentry.Handlers.requestHandler()必须在最前面,Sentry.Handlers.errorHandler()放在最后。
内容的提问来源于stack exchange,提问作者Aral Roca




