architecture/frontend

One Notification, Eight Apps, Zero Duplicates

Duplicate notifications are the kind of bug that makes a whole product feel broken. Ours were showing up across a suite of eight separate web apps, each with its own service worker and Firebase setup. The fix wasn’t clever — it was consolidation — but finding the real cause took ruling out everything that looked more interesting.

The suite is the problem

People, Global, Inventory, Docs, Tasks, Calendar, Partners — eight PWAs, each shipping its own service worker and Firebase messaging config. Every one of them registered to receive push. When a user had more than one open, or a stale worker lingered from a previous version, a single backend send could surface as two or three system notifications.

The symptom looked like a backend fan-out bug. It wasn’t. The backend sent once; the client side received and displayed more than once.

Why duplicates actually happen

Firebase messaging can deliver a notification through both the foreground `onMessage` handler and the service worker’s background handler. If both display it, you get two. Add a second registered service worker from another app on the same origin scope, or an old worker that never got unregistered, and you get three.

So there were two failure axes: foreground-plus-background double display within one app, and multiple/stale worker registrations across the suite.

// The trap: showing the notification in BOTH places.
// Foreground handler:
onMessage(messaging, (payload) => {
  // DON'T also call showNotification here if the SW already will.
  renderInAppToast(payload);          // in-app UI only
});

// firebase-messaging-sw.js (background):
onBackgroundMessage(messaging, (payload) => {
  self.registration.showNotification(payload.notification.title, {
    body: payload.notification.body,
    data: payload.data,               // single source of the system notification
  });
});

Consolidation, not cleverness

The fix was to standardize one service worker + Firebase config setup and roll the exact same registration and messaging logic across all eight apps, so scope and lifecycle were identical everywhere. Foreground handlers stopped showing system notifications and did in-app UI only; the service worker became the single place a system notification is created.

Then the unglamorous part: removing the debug logging that had been added to trace the duplicates, and making sure old workers were superseded cleanly on deploy instead of lingering.

How you know it’s actually fixed

Duplicate-notification bugs are easy to “fix” and have quietly return. The verification that mattered was cross-app: open several suite apps, trigger one backend send, and confirm exactly one system notification appears — then repeat after a version bump to make sure a replaced service worker doesn’t resurrect the old behavior.

Consolidating the setup didn’t just kill the duplicates; it meant the next notification feature only had to be built once, not eight times.

← All postsNext: Making a Notification Tap Land on the Right Screen