DEV Community

Haber Teknoloji
Haber Teknoloji

Posted on

Null Is Not Zero: Building a JavaScript SEO Audit That Admits Its Limits

We moved a server-side SEO engine into a Chrome extension. Measuring the page was the easy half. Saying what we could not measure was the hard half.

We had been running an on-page analysis engine on our own servers for years. You give it a URL, it fetches the page, it reports. Ordinary.

Then we moved that engine into the browser, because a server cannot reach localhost, a staging box, an intranet, or anything behind a login. The browser can.

Porting the analysis was mechanical work. What took the real time was a category of problem that barely exists on the server: in a live tab, half the things you want to measure are sometimes unavailable, and the honest answer is not a number.

This post is about the decisions that came out of that, with the code that implements them.


The One Rule: Null Is Not Zero

Every derivation in the engine returns number | null, and the two mean different things.

0 means we measured it and it is zero. A page with no layout shift really does score zero.

null means we could not measure it. No interaction happened yet, the browser does not support that entry type, or the document came from another origin and the size fields were zeroed out.

A zero printed where a null belongs is a made-up number. It is worse than an empty cell, because the reader has no way to tell it apart from a real measurement. So the two never collapse: the derivation keeps them separate and the UI renders them differently.

That sounds obvious written down. It is surprisingly easy to violate, and the next section is the most common way.


PerformanceObserver Fails Silently, So Ask It First

Here is the trap. Calling observe() with an entry type the browser does not support does not throw. It does not warn. It quietly does nothing, and your handler is simply never called.

Which means an unsupported metric produces exactly the same result as a measured zero. The one thing the rule above forbids.

The fix is to ask before you observe, and to record the refusal:


js
const SUPPORTED =
  (typeof PerformanceObserver !== "undefined" &&
    PerformanceObserver.supportedEntryTypes) ||
  [];

function observe(type, handler, extra) {
  if (!SUPPORTED.includes(type)) {
    perf.missing.push(type);   // the panel leaves that metric empty
    return false;
  }
  try {
    const po = new PerformanceObserver((list) => {
      for (const entry of list.getEntries()) handler(entry);
    });
    po.observe({ type, buffered: true, ...extra });
    return true;
  } catch {
    perf.missing.push(type);
    return false;
  }
}
Enter fullscreen mode Exit fullscreen mode

Top comments (0)