Fix “Cannot set headers after they are sent” in Node.js
Find response paths that write headers twice, stop branch fallthrough, and hand Express errors to the right middleware.
This error means code tried to change response headers after they had already been sent. Find every path that can write the response, then make sure those paths cannot compete. Returning from a response branch prevents fallthrough in that function; it does not cancel another callback or background operation.
Remove duplicate responses
This route sends twice and is intentionally incorrect:
app.get('/', (req, res) => {
res.send('First response');
res.send('Second response');
});
Send one response from the route instead. Header writes can also happen inside json(), redirect(), writeHead(), or the first body write, so the failing code need not call setHeader() directly.
End a completed branch
In an existing Express application:
app.get('/hello', (req, res) => {
if (typeof req.query.name !== 'string' || !req.query.name.trim()) {
return res.status(400).send('A name is required');
}
return res.send(`Hello, ${req.query.name}`);
});
Without the first return, the second send would still run after the error response. res.send() finishes the response, not the JavaScript function that called it.
Give middleware a clear responsibility
Install middleware that sets response headers before routes that send the body. A middleware usually either completes the request or calls next() to transfer control. Calling next() and then sending a competing response leaves two parts of the application writing to the same client.
For asynchronous work, forward failures to error middleware. Here fetchData() is an application-provided function:
app.get('/data', async (req, res, next) => {
try {
const data = await fetchData();
return res.json(data);
} catch (error) {
return next(error);
}
});
// Register after the routes.
app.use((error, req, res, next) => {
if (res.headersSent) return next(error);
return res.status(500).json({ error: 'Internal server error' });
});
Express 4 needs rejected asynchronous work to reach next() explicitly. Express 5 forwards rejections from returned promises automatically; the explicit pattern above also works. If headers are already sent, forwarding the error allows Express to handle the failed connection rather than attempting another JSON response.
Trace the first write
The stack usually identifies the later, invalid header operation. The earlier write may live in a different callback or middleware, not simply a few lines above it. Trace the request through all send, json, end, redirect, and streaming paths. Correlated request logs help establish which path ran first.
FAQ
Does headersSent mean the response is finished? No. Headers can be sent while a streamed body is still in progress. The flag is useful in error handling, but it is not a substitute for clear response ownership.
Does adding return everywhere fix asynchronous races? It only ends the current function. Consolidate competing callbacks or promise branches so one path decides the response.
Can this crash the process? An error that escapes the application's error handling can terminate the process. Express catches some route errors, but arbitrary callbacks and rejected work need appropriate handling. Fix the underlying double write even if the server continues running.
Related reading
References
Contact us
Join the community
to join the community
Try Guance
Start online and pay only for what you use.
Get startedChoose a Guance plan