DEV Community

EL E
EL E

Posted on

Your ops dashboard does not need a frontend framework

The dashboard I actually look at every morning is one HTML file with a <script> tag
and no build step. It polls three JSON files on disk and renders a table. It has been
running unattended for weeks.

I want to argue for that shape, because the default reflex — spin up a framework,
a bundler, a component library, a dev server — costs more than it returns for this
specific category of page.

What an internal dashboard actually is

Strip away the ceremony and an ops dashboard is:

  1. Read some state that another process already wrote down.
  2. Show it.
  3. Do it again in fifteen seconds.

There is no routing. There is usually one viewer. There is no SEO, no SSR, no
hydration, no design system. The interaction budget is "click a link, maybe sort a
column".

<script>
async function tick() {
  const [status, targets, alerts] = await Promise.all(
    ["status.json", "targets.json", "alerts.json"].map(f =>
      fetch(f + "?t=" + Date.now()).then(r => r.json())));
  render(status, targets, alerts);
}
tick();
setInterval(tick, 15000);
</script>
Enter fullscreen mode Exit fullscreen mode

That is the whole architecture. The hard parts of this page were never rendering.

The hard parts are elsewhere

Freshness. A dashboard that silently shows yesterday's numbers is worse than a
blank page, because a blank page makes you go and check. Mine prints the generation
timestamp of every source file next to the data, and marks anything older than its
expected interval. The number is never shown without saying when it was true.

Saying "unknown" instead of zero. If a source file fails to parse, the honest
render is unknown, not 0. I learned this the expensive way: a JSON file written
by a different tool carried a byte-order mark, json.load raised, the exception was
swallowed, and the page rendered a beautifully empty table. It looked fine. It was
just empty. (The fix was encoding="utf-8-sig". The lesson was that the failure had a
display mode indistinguishable from success.)

Counting what you did not check. Any panel that says "all green" has to also say
how many things it examined. "12/12 checks passed" and "0 checks found" are wildly
different states that render identically if you only show a colour.

None of those three are frontend problems. A framework helps with none of them.

What you give up

Honestly: components, hot reload, type checking across the template boundary, and the
ability to hand the page to someone who only writes React.

For a page with four panels and one reader, I have not missed any of them. At around
a dozen panels with real interaction I would reconsider — but I would reconsider by
adding one small library, not by starting a project.

The part that actually matters

Whatever you build it with, the dashboard's job is to be trusted. That means:

  • every number carries its timestamp,
  • a source that could not be read says so instead of rendering zero,
  • and any "passed" claim states its denominator.

A page that gets those three right in plain HTML is more useful than a beautiful one
that quietly shows stale data — because the first one you can act on, and the second
one you will eventually act on when it is wrong.


I build these: single-page operational dashboards, health checks, and the collectors
behind them. No framework, no build step, no monthly service — one file you own and
can read end to end. Available for hire on Fiverr.

Top comments (0)