DEV Community

Cover image for Why my Next.js dashboard was always showing stale data — and the one-line fix
Ronald
Ronald

Posted on

Why my Next.js dashboard was always showing stale data — and the one-line fix

The timestamp was updating. The AI recommendations kept coming in. Everything looked alive.

But the sensor values hadn't moved in twenty minutes.

That's the kind of bug that gets inside your head. Not because it's complicated — but because it makes no sense. Half the app was working perfectly, which meant the problem wasn't obvious. It was hiding somewhere in the middle, watching me look in all the wrong places.

This is SensorWatch AI — an industrial sensor monitoring dashboard built with Next.js 14, TypeScript, PostgreSQL, and an LLM integration via OpenRouter. Three simulated industrial sensors, real-time anomaly detection, and an AI pipeline that generates maintenance recommendations in natural language. A system that, at least on paper, was working fine.

Except it wasn't. And I was about to spend the next several hours finding out why.


Everything I tried that didn't work

My first suspect was the AI.

OpenRouter's free tier is slow. Sometimes painfully slow. My theory: the LLM call was taking so long that it was colliding with other requests — some kind of race condition between the simulation and the anomaly analysis. It felt logical. It was wrong.

I separated the responsibilities. Instead of running the AI call and the sensor simulation together in a single Promise.all, I split them into two independent flows. Cleaner architecture, I thought. The bug didn't care.

I added console.logs. The AI was indeed slow — sometimes 15, 20 seconds per response. That confirmed my theory, or so I thought. I kept digging in the backend, convinced the problem lived there. It didn't.

Eventually, and somewhat desperately, I moved to the frontend. Maybe the useCallback wasn't re-executing because it had no dependencies. It wasn't logical — I could see it being called correctly in the useEffect — but at that point I was throwing things at the wall. That wasn't it either.

Then I started placing console.logs everywhere. Not strategically. Desperately. And that's when I finally saw it: the database was sending the exact same data on every single request. Same values. Same timestamps. Every time.

The sensors weren't frozen. The data was.


The cause

The bug was in the database query. One word. That's it.

const readings = await prisma.sensorReading.findMany({
  where: { sensorId: sensor.id },
  orderBy: { createdAt: "asc" }, // ← here
  take: 50,
});
Enter fullscreen mode Exit fullscreen mode

Looks innocent. It isn't.

Here's what was actually happening: every time a sensor generated a new reading, it got saved to the database correctly. No problem there. But when the dashboard asked for the latest data, this query said: "give me 50 records, oldest first."

The database had thousands of records. The 50 oldest never change — they were written days ago and they'll stay there forever. So every single request returned the exact same 50 rows, while all the new data sat ignored at the other end of the table.

The sensors weren't broken. The simulation wasn't broken. The AI wasn't broken. The frontend wasn't broken.

I was just always reading from the wrong end of the database.

The timestamp kept updating because it was set client-side with new Date() on every successful fetch — and the fetches were succeeding. The AI recommendations kept appearing because that query had its own separate endpoint, ordered correctly. Everything that looked alive was alive. Everything that looked frozen was frozen because of one word in one query.

asc instead of desc. That's the whole story.


The fix

const readings = await prisma.sensorReading.findMany({
  where: { sensorId: sensor.id },
  orderBy: { createdAt: "desc" }, // newest first
  take: 50,
  select: {
    value: true,
    isAnomaly: true,
    createdAt: true,
  },
});

// Reverse so the chart displays time left to right
const sortedReadings = readings.reverse();
Enter fullscreen mode Exit fullscreen mode

Two things happening here:

desc means the query now fetches the 50 most recent records — the ones that actually change with every simulation cycle.

.reverse() puts them back in chronological order for the chart. If you skip this, the chart displays time backwards — newest on the left, oldest on the right. The data is correct but the visualization lies.

The current value for the status cards becomes reliable too:

currentValue: sortedReadings[sortedReadings.length - 1]?.value ?? null,
currentIsAnomaly: sortedReadings[sortedReadings.length - 1]?.isAnomaly ?? false,
Enter fullscreen mode Exit fullscreen mode

Always the last element of a chronologically sorted array — always the most recent reading.


The lesson

When something isn't displaying correctly, verify the data before touching the UI.

I spent hours in the frontend, in the AI pipeline, in the fetch logic — because that's where the symptoms were visible. But symptoms and causes don't always live in the same place.

One console.log at the API response level would have shown me identical data on every request. A five-second check that would have saved hours of debugging in the wrong direction.

The bug wasn't clever. The fix wasn't clever. What cost me time was the assumption that a visual problem must have a visual cause.

It didn't. It had a database cause. And database causes don't announce themselves in the UI.

Check your data first. Then your code.


SensorWatch AI is open source — github.com/RonaldGGA/sensorwatch-ai

Live demo: sensorwatch-ai.vercel.app

Top comments (0)