Blog
3 min read

Cannot Set Headers After They Are Sent to the Client: How to Fix It

"Error [ERR_HTTP_HEADERS_SENT]: Cannot set headers after they are sent to the client" means your Express or Node.js code tried to send a second response to one request. The common causes — a missing return, callbacks and loops that respond twice, next() after res.send(), async errors — and how to fix each.

Error [ERR_HTTP_HEADERS_SENT]: Cannot set headers after they are sent to the client

Every HTTP request gets exactly one response. This error means your Node.js code (usually Express) tried to respond twice to the same request. The first response went out fine; the second attempt crashed.

The fix is always the same idea: make sure each code path sends one response and then stops.

Cause 1: Missing return (the big one)

app.get('/user/:id', async (req, res) => {
  const user = await db.findUser(req.params.id)
  if (!user) {
    res.status(404).json({ error: 'Not found' })   // sends a response...
  }
  res.json(user)                                   // ...then sends another 💥
})

res.json() sends the response but doesn't stop your function. Add return:

  if (!user) {
    return res.status(404).json({ error: 'Not found' })
  }
  return res.json(user)

Writing return res... everywhere is a good habit.

Cause 2: Responding inside a loop

for (const item of items) {
  if (item.invalid) res.status(400).send('Invalid item') // may run many times
}
res.send('OK')

Collect the result first, then respond once:

const invalid = items.find(item => item.invalid)
if (invalid) return res.status(400).send('Invalid item')
return res.send('OK')

Cause 3: Calling next() after responding

In middleware, either respond or call next() — not both:

function requireAuth(req, res, next) {
  if (!req.user) {
    return res.status(401).json({ error: 'Login required' })
  }
  next()
}

Without that return, the request continues to the route handler, which responds again.

Cause 4: Callbacks and events that fire more than once

Old-style callbacks, stream 'data' events, or an on('error') plus a success callback can each try to respond. Make sure only one path ends the request, or track it with if (res.headersSent) return.

Cause 5: Async errors after a response

res.json({ ok: true })
await sendWelcomeEmail(user)   // throws...
// ...and your error handler tries to send a 500 too

If work continues after you respond, the error handler can't respond again. Either do the work before responding, or move it to a background job and catch its errors there. (Background Jobs and Cron)

Express 5 forwards rejected promises from async handlers to your error handler automatically, which makes these double responses show up more clearly. Your error handler should check first:

app.use((err, req, res, next) => {
  if (res.headersSent) return next(err)
  res.status(500).json({ error: 'Something went wrong' })
})

Cause 6: Setting a header after writing the body

res.setHeader(), res.cookie() and res.status() must come before res.send()/res.json()/res.write(). Once the body starts, headers are locked. (HTTP Headers Explained)

The summary

  • One request, one response. This error means you sent two.
  • Add return before every res.send/res.json, respond once outside loops, and don't call next() after responding.
  • Check res.headersSent in error handlers.

EasySpawn runs your Express app and its logs on the same server as Claude Code, so it can trace which code path answered twice and fix it. See how it works or join the waitlist.

Related: What Is Express? · Deploy an Express App · async/await in JavaScript · 500 Internal Server Error

Keep reading