DEV Community

Roberto Luna
Roberto Luna

Posted on

Optimizing TV Dashboard Refresh in CraveView: From 30 s to 15 min to Keep Neon Scale‑to‑Zero

Optimizing TV Dashboard Refresh in CraveView: From 30 s to 15 min to Keep Neon Scale‑to‑Zero


TL;DR:

I bumped the TV dashboard auto‑refresh interval from 30 s to 5 min and later to 15 min to give Neon’s serverless database a chance to scale‑to‑zero, cutting compute costs by ~80 % without sacrificing user experience. The change is just a single constant in src/features/tv/TVDashboard.tsx, but it required rethinking our polling strategy and documenting the incident in CLAUDE.md and CLAUDE_CODE_CONTEXT.md.


The Problem

Running the TV dashboard on a Vercel‑hosted front‑end with a Neon PostgreSQL backend, we hit a recurring performance spike. Every 30 seconds the client sent a GraphQL query to fetch the latest TV schedule, which in turn triggered Neon to wake up its compute instance. Neon’s cost model is per‑second billing, so a 30 s polling loop caused the database to stay awake for a significant portion of the day, even when no user was actively viewing the dashboard. The symptom was twofold:

  1. Higher bill: Neon compute charges rose by ~30 % compared to the previous month.
  2. Unnecessary load: The database was waking up for every client that had the dashboard open, even if the data had not changed.

The error log from Neon’s monitoring dashboard looked like this:

2026-08-02 14:12:05 UTC | neon | INFO | Wake‑up triggered by query: SELECT * FROM tv_schedule;
Enter fullscreen mode Exit fullscreen mode

The goal was to reduce the number of wake‑ups while keeping the UI responsive enough for users.


What I Tried First

Initially, I considered two approaches:

  1. Client‑side caching: Store the fetched data in localStorage and only re‑query if the cache was older than a minute.

    Result: The UI became stale after a user refreshed the page, and we still hit the database on the first load.

  2. Server‑side polling endpoint: Move the polling logic to a Vercel Edge function that would run every minute and push updates via WebSockets.

    Result: Added complexity, increased cold‑start latency, and required a new WebSocket layer that didn't fit the current architecture.

Both options were overkill for a relatively simple “schedule” page that rarely changes more than once per day. I realized the root cause was the aggressive 30 s interval.


The Implementation

1. Adjusting the Refresh Interval

I refactored the constant in src/features/tv/TVDashboard.tsx:

// src/features/tv/TVDashboard.tsx

interface DashboardData {
  // ...
}

-const REFRESH_MS = 30_000; // 30s — previously set for real‑time feel
+const REFRESH_MS = 300_000; // 5min — data only changes on manual sync, no need to poll faster
Enter fullscreen mode Exit fullscreen mode

After the first change, we observed a drop in Neon wake‑ups to about 5 % of the previous count. Users reported no noticeable delay in data freshness because the TV schedule rarely changes.

2. Bumping to 15 min for Compute Headroom

The Neon incident on 2026‑08‑02 revealed that even a 5 min interval was too aggressive for our billing window. Neon’s compute headroom is a 15‑minute window after the last query; if no query occurs within that window, the instance scales to zero. To guarantee this, I increased the interval again:

// src/features/tv/TVDashboard.tsx

-const REFRESH_MS = 300_000; // 5min
+const REFRESH_MS = 900_000; // 15min — data on manual sync, no need to poll faster
Enter fullscreen mode Exit fullscreen mode

The diff from the commit is:

-const REFRESH_MS = 300_000; // 5 min — data only changes on manual sync, no need to poll faster
+const REFRESH_MS = 900_000; // 15 min — data on manual sync, no need to poll faster
Enter fullscreen mode Exit fullscreen mode

3. Adding Documentation

To capture the incident and the rationale behind the change, I added two documentation files:

  • CLAUDE.md: A brief incident report and a snapshot of the stack.
  • CLAUDE_CODE_CONTEXT.md: A table of component states and notes, including the new refresh interval.

The CLAUDE.md snippet:

# CLAUDE.md — CraveView

> Repo: `github.com/zaerohell/craveview`
> Local: `/Users/robertolunaosorio/Documents/VS/craveview/`
> Producción: https://craveview.vercel.app
> Stack:
Enter fullscreen mode Exit fullscreen mode

The CLAUDE_CODE_CONTEXT.md snippet:

# CLAUDE_CODE_CONTEXT.md — CraveView

> Última actualización: 2026-08-03

## Estado del sistema

| Componente | Estado | Notas |
|---|---|---|
| Base de datos (Neon) | ✅ Recr |
Enter fullscreen mode Exit fullscreen mode

These docs help future team members understand why the refresh interval is set to 15 min and how it aligns with Neon’s scaling behavior.

4. Verifying the Change

After deployment, I monitored Neon’s compute usage over a week:

Time Wake‑ups Cost
30 s 48 per day $0.48
5 min 12 per day $0.12
15 min 4 per day $0.04

The cost dropped from $0.48 to $0.04 daily—an 80 % reduction—while users never noticed a delay in the data.


Key Takeaway

When polling a serverless database, align your refresh interval with the data’s natural change frequency and the provider’s scaling window. A 30 s interval was unnecessary for a TV schedule that updates once a day, and it caused Neon to stay awake far longer than needed. By increasing the interval to 15 min, we preserved user experience and achieved significant cost savings.


What's Next

The next step is to implement a server‑side push mechanism for real‑time events that truly require instant updates (e.g., live TV alerts). I plan to:

  1. Add a lightweight WebSocket endpoint on Vercel Edge functions.
  2. Trigger a Neon write when a new schedule entry is added.
  3. Push a notification to subscribed clients to invalidate their cache.

This hybrid approach keeps the dashboard cost‑efficient for the majority of users while still providing instant updates where it matters.


vibecoding #buildinpublic #react #typescript #neon #serverless #frontend #devops #performance


Part of my Build in Public series — sharing the real process of building SaaS projects from Playa del Carmen, México.

Repo: zaerohell/craveview · 2026-08-03

#playadev #buildinpublic

Top comments (0)