如何在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无内容,减少响应体积 });
排查验证技巧
- 打开浏览器DevTools的Network面板,找到sendBeacon的请求,查看
Request Headers里的Content-Type,确认是否为text/plain(默认情况)或application/json(方案1处理后)。 - 检查Express中间件的顺序,确保处理text/plain的逻辑在
express.json()之前,否则会被跳过。 - 测试页面卸载时的上报情况,确保fetch降级时加了
keepalive: true,避免请求被取消。
内容的提问来源于stack exchange,提问作者philolegein
相关产品推荐
相关产品推荐

