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

如何在Express中解析navigator.sendBeacon发送的JSON数据?

问题原因与解决方案

核心问题

navigator.sendBeacon 有个关键限制:无法自定义请求头。当你直接传入JSON字符串时,浏览器会自动把请求的 Content-Type 设为 text/plain,而你的Express后端用的 express.json() 中间件只处理 Content-Type: application/json 的请求,所以会跳过解析,导致 req.body 为空。而fetch能正常工作,是因为你手动设置了正确的 Content-Type 头。


方案1:前端适配(推荐,不破坏原有fetch逻辑)

通过 Blob 包装JSON数据,给sendBeacon请求指定正确的 Content-Type,这样后端的 express.json() 就能正常解析了,同时fetch降级逻辑完全不受影响。

function reportWebVitals(metric) {
  const dataStr = JSON.stringify(metric);
  
  if (navigator.sendBeacon) {
    // 用Blob包装,强制指定Content-Type为application/json
    const blob = new Blob([dataStr], { type: 'application/json; charset=utf-8' });
    // sendBeacon返回false时说明队列满了,降级到fetch
    const success = navigator.sendBeacon('/web-vitals', blob);
    if (!success) fallbackFetch(dataStr);
  } else {
    fallbackFetch(dataStr);
  }
}

function fallbackFetch(dataStr) {
  fetch('/web-vitals', {
    method: 'POST',
    headers: { 'Content-Type': 'application/json' },
    body: dataStr,
    keepalive: true, // 模拟sendBeacon的后台发送特性,页面卸载时也能完成请求
  });
}

方案2:后端适配(前端无需改动)

如果不想改前端代码,就在后端加一个中间件,专门处理 text/plain 类型的请求,把请求体解析成JSON,同时保留原有 express.json() 中间件处理fetch的请求。

注意:中间件顺序很重要,处理text/plain的逻辑要放在 express.json() 前面。

const express = require('express');
const app = express();

// 先处理text/plain类型的请求(sendBeacon默认发送的类型)
app.use((req, res, next) => {
  if (req.headers['content-type'] === 'text/plain') {
    let rawBody = '';
    req.on('data', chunk => rawBody += chunk.toString());
    req.on('end', () => {
      try {
        req.body = JSON.parse(rawBody);
        // 如果需要rawBody做签名验证,同步赋值
        req.rawBody = Buffer.from(rawBody);
      } catch (err) {
        req.body = {};
      }
      next();
    });
  } else {
    next();
  }
});

// 原有express.json中间件,处理fetch发送的application/json请求
app.use(express.json({
  verify: (req, res, buf) => {
    req.rawBody = buf; // 保留Webhook签名验证需要的rawBody
  }
}));

// WebVitals上报接口
app.post('/web-vitals', (req, res) => {
  console.log('Received metric:', req.body);
  res.status(204).send(); // 204无内容,减少响应体积
});

排查验证技巧

  1. 打开浏览器DevTools的Network面板,找到sendBeacon的请求,查看 Request Headers 里的 Content-Type,确认是否为 text/plain(默认情况)或 application/json(方案1处理后)。
  2. 检查Express中间件的顺序,确保处理text/plain的逻辑在 express.json() 之前,否则会被跳过。
  3. 测试页面卸载时的上报情况,确保fetch降级时加了 keepalive: true,避免请求被取消。

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

相关产品推荐
方舟 Agent Plan

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

最近更新时间:2026.08.25 09:06:35