Server responses so slow the user abandoned! Trace using NEL and an example in Node.js

Network Error Logging ?

1000ft. view of NEL

The power to monitor “abandonment”

  1. Valid, not erroneous(400–500) or redirect, headers are received in the server response.
  2. The browser has not managed to fully read the response body from the server.

In plain English:

User leaves before response is complete

The Importance

  • Client side analytics have no power there as the whole response body is not yet fully transmitted, let alone analytics scripts being run.
  • Server logs depending on your infrastructure at the worst case scenario have logged a 200 status code or nothing at all. When a CDN is serving your pages, you usually don’t even have access to their logs.

Example Node.js implementation

A Simple Web Server

$ mkdir nel-abandoned && cd nel-abandoned
$ npm init -y
$ npm install express
$ touch app.js
const express = require("express");
const app = express();
const PORT = 3000;
/*
Allow express to parse the special content type
of the NEL report.
*/
app.use(express.json({ type: "application/reports+json" }));
/* Home route just sending nothing back*/
app.get("/", async (req, res) => {
res.end();
});
/*
NEL collector endpoint.
In a real environment, the reporting endpoint would be
in a completely different server IP, domain and CDN.
*/
app.post("/report", (req, res) => {
// Log the reports received on the terminal
console.log(JSON.stringify(req.body));
});
app.listen(PORT, () => {
console.log(`Listening on ${PORT}`);
});
$ npx nodemon app.js
// On different terminal now
$ ngrok http 3000
const NelMiddleware = function (req, res, next) {

res.setHeader(
"Report-To",
JSON.stringify({
group: "network-errors",
// Expire in day
max_age: 86400,
// Here use the secure URL you are gonna get from ngrok
endpoints: [{ url: "NGROK_URL/report" }],
})
);
res.setHeader(
"NEL",
JSON.stringify({
report_to: "network-errors",
// Cache the policy for a day
max_age: 86400,
})
);
next();
};
/* Use the middleware before registering the routes */
app.use(NelMiddleware);

Simulating & monitoring an “abandonment” error

app.get("/", async (req, res) => {
setTimeout(() => res.end(), 10000);
});
Example “abandoned” error report

Case of not fully delivered response body

/* Helper for artificial delay */
const delay = (ms) => new Promise((resolve) => setTimeout(resolve, ms));
app.get("/", async (req, res) => {
res.setHeader("Content-Type", "text/html");
await res.write(`
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<title>Document</title>
</head>
<body>
`);
await delay(5000);
res.write(`<p>Hello World</p></body>`);
await delay(5000);
res.write(`</html>`);
res.end();
});

Closing notes

--

--

Front-end engineer@ Clerk; Loves software development books. Building foundations in software architecture.

Get the Medium app

A button that says 'Download on the App Store', and if clicked it will lead you to the iOS App store
A button that says 'Get it on, Google Play', and if clicked it will lead you to the Google Play store
Petros Perlepes

Front-end engineer@ Clerk; Loves software development books. Building foundations in software architecture.