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

Express+Firebase登录功能出现ERR_HTTP_HEADERS_SENT重复错误求助

解决Express+Firebase中ERR_HTTP_HEADERS_SENT错误

问题背景

开发官网登录/会员功能,基于Express.js结合Google Firebase实现账号系统,登录成功后多次刷新首页(点击官网图标重新加载),后端反复触发以下错误:

Error [ERR_HTTP_HEADERS_SENT]: Cannot set headers after they are sent to the client
    at new NodeError (node:internal/errors:372:5)
    at ServerResponse.setHeader (node:_http_outgoing:576:11)
    at ServerResponse.header (/Users/leziyang/Code/ttumc/node_modules/express/lib/response.js:794:10)
    at ServerResponse.contentType (/Users/leziyang/Code/ttumc/node_modules/express/lib/response.js:624:15)
    at ServerResponse.send (/Users/leziyang/Code/ttumc/node_modules/express/lib/response.js:149:14)
    at done (/Users/leziyang/Code/ttumc/node_modules/express/lib/response.js:1035:10)
    at tryHandleCache (/Users/leziyang/Code/ttumc/node_modules/ejs/lib/ejs.js:280:5)
    at View.exports.renderFile [as engine] (/Users/leziyang/Code/ttumc/node_modules/ejs/lib/ejs.js:491:10)
    at View.render (/Users/leziyang/Code/ttumc/node_modules/express/lib/view.js:135:8)
    at tryRender (/Users/leziyang/Code/ttumc/node_modules/express/lib/application.js:657:10)

错误不影响登出功能,但频繁出现影响开发体验。

错误原因分析

  1. onAuthStateChanged重复注册回调:每次访问首页(/路由)都会调用onAuthStateChanged注册新的监听回调,当用户登录状态变化时,所有已注册的回调都会执行,导致多次调用res.render,试图多次向客户端发送响应头,触发错误。
  2. 全局变量signStatus的竞态问题:使用全局变量signStatus存储登录状态,多请求并发场景下会被不同请求覆盖,不仅导致页面渲染状态不准确,还会加剧重复响应的问题。
  3. 异步操作未等待完成:注册用户时的setDoc操作未使用await,可能在数据库操作未完成时就跳转,存在潜在的数据一致性问题。

修复方案

  • 移除路由中onAuthStateChanged的重复注册,改用getAuth().currentUser实时获取当前用户状态,避免多次回调执行。
  • 删除全局变量signStatus,每次请求时直接判断用户状态,避免竞态问题。
  • 异步操作添加await,确保数据库操作完成后再进行响应跳转。

完整修复代码

const express = require("express");
const bodyParser = require("body-parser");
const ejs = require("ejs");
const _ = require("lodash");

const app = express();

app.use(express.json());
app.set('view engine', 'ejs');
app.use(bodyParser.urlencoded({ extended: true }));
app.use(express.static("public"));

const { initializeApp } = require("firebase/app");
const {
    getAuth, signInWithEmailAndPassword, createUserWithEmailAndPassword, signOut
} = require("firebase/auth");

const {
    getFirestore, doc, setDoc
} = require("firebase/firestore");

const credentials = require(__dirname + "/serviceAccountKey.json");
const initApp = initializeApp(credentials);

const auth = getAuth(initApp);
const db = getFirestore(initApp);

// 首页路由:直接获取当前用户状态,无需重复注册监听
app.get("/", function (req, res) {
    const user = auth.currentUser;
    const signStatus = user ? 1 : 0;
    res.render("index", { signStatus: signStatus });
});

app.get("/login", function (req, res) {
    const user = auth.currentUser;
    const signStatus = user ? 1 : 0;
    res.render("login", { signStatus: signStatus });
});

app.post("/login", async function (req, res) {
    const email = req.body.email;
    const password = req.body.password;
    try {
        await signInWithEmailAndPassword(auth, email, password);
        res.redirect("/");
    } catch (error) {
        res.send("<h1>登入失敗囉低能兒!</h1><br>");
    }
});

app.get("/signup", function (req, res) {
    const user = auth.currentUser;
    const signStatus = user ? 1 : 0;
    res.render("signup", { signStatus: signStatus });
});

app.post('/signup', async (req, res) => {
    const email = req.body.email;
    const password = req.body.password;
    const userName = req.body.userName;
    const schoolNumber = req.body.schoolNumber;
    const id = req.body.email;
    const userJson = {
        email: email,
        userName: userName,
        schoolNumber: schoolNumber,
        adminPower: false,
    };
    try {
        const userCredential = await createUserWithEmailAndPassword(auth, email, password);
        await setDoc(doc(db, "users", id), userJson);
        res.redirect("/");
    } catch (error) {
        const errorCode = error.code;
        const errorMessage = error.message;
        console.log(errorCode, errorMessage);
        res.send("<h1>創建失敗囉低能兒!</h1><br>" + errorMessage);
    }
});

app.get('/signout', async function (req, res) {
    try {
        await signOut(auth);
        res.redirect("/");
    } catch (error) {
        res.send("<h1>好像出了點問題 QAQ</h1>");
    }
});

app.listen(3000, function () {
    console.log("Server started on port 3000");
});

额外说明

  • auth.currentUser会实时返回当前登录的用户对象,未登录则为null,无需依赖监听回调即可获取状态。
  • 所有异步操作(signInWithEmailAndPassword、createUserWithEmailAndPassword、setDoc、signOut)都改用async/await处理,代码逻辑更清晰,避免回调嵌套和未完成操作导致的问题。

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

相关产品推荐
方舟 Agent Plan

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

最近更新时间:2026.08.17 07:20:35