Skip to main content
← Back to blog

How a 2-Line Code Change Spiked My Firestore Bill from $80 to $300

4 min readReadMeter Team

The silent danger of repeated queries in Next.js—and why I stopped trusting my localhost Network tab.

For the first six months after launching my app, my infrastructure costs were beautifully boring. My Firestore bill hovered predictably around $80 to $90 a month. The app was profitable, user growth was steady, and I was feeling confident enough to start shipping some “nice-to-have” features.

Then I shipped a minor UI update. A week later, I woke up to a Google Cloud billing alert. My projected monthly cost had shot past $250 and was aggressively trending toward $300.

I had not gone viral. I had not suddenly acquired thousands of new users overnight. So where did the traffic come from?

The investigation

I opened the Firebase Console, expecting to find a clear answer. Instead, all I got was a terrifying hockey-stick graph: my document reads jumped from a steady 150,000 per day to over 1.2 million.

The console told me that I was bleeding reads, but it refused to tell me where. That blind spot is the same visibility gap every Firestore team learns about the hard way: totals without causes.

I spent hours commenting out components, deploying to a staging environment, and clicking around trying to replicate the spike.

The culprit: a “harmless” notification bell

The week prior, I had added a global notification bell to my Next.js navigation bar. It was a simple feature: fetch the current user’s unread notification count and display a little red dot if the count was greater than zero.

Here is the logic I wrote:

// components/Navbar.tsx
export default function Navbar({ userId }) {
  const [unreadCount, setUnreadCount] = useState(0);

  useEffect(() => {
    if (!userId) return;
    const fetchNotifications = async () => {
      const q = query(
        collection(db, 'notifications'),
        where('userId', '==', userId),
        where('read', '==', false)
      );
      const snapshot = await getDocs(q);
      setUnreadCount(snapshot.docs.length);
    };
    fetchNotifications();
  }, [userId]); // The trap was set here

  return <Bell icon={unreadCount > 0} />;
}

On its own, this code works. But I had placed this Navbar inside a layout component that was unmounting and re-mounting on specific route changes due to how I structured my Next.js page transitions.

Every time an active user clicked a link—from the dashboard, to their profile, to settings, and back—the Navbar re-mounted and re-fetched the entire unread notification collection.

If a user had 5 unread notifications and navigated through 10 pages during their session, that single user generated 50 document reads just for the red dot. Multiply that by hundreds of active users doing the same thing every day, and suddenly that $300 bill made perfect sense. For how those reads compound in pricing, see how Firestore read costs work.

The fix

The technical fix took about three minutes. I moved the notification fetch logic into a global context provider at the root of the app, so the fetch ran once after login, and I updated local state whenever a notification was marked read.

Reads for that feature dropped by roughly 90% immediately. My bill returned to normal the next month.

The bigger problem (and why I built ReadMeter)

While fixing the code was easy, finding the problem was a nightmare.

During development, my test account only had one notification. Even if the component re-mounted 10 times, that was only 10 reads. The Network tab in my browser did not make it look like an emergency. It was completely invisible on localhost—and a financial disaster in production. Same pattern as the success disaster: small data and dev habits hide what production scale will invoice.

I realized I could not keep shipping features if I was constantly afraid of a tiny architectural mistake wiping out my margins. I needed to see document reads per function while I was writing the code.

That is why I built ReadMeter.

Instead of waiting for a cloud dashboard to update, ReadMeter injects a local widget right into your Next.js app. If ReadMeter had been installed when I built that notification bell, I would have seen a warning: fetchNotifications() executed 10× — 50 reads. I could have caught the repeated query before git push.

If you are building with Firestore, you need to know what your code costs before your users do.

Get ReadMeter — $9 one-time

See your Firestore reads while you build

Get visibility into read patterns before traffic arrives. One-time purchase, one device.

Get ReadMeter — $9 (one-time)