devto 2026-06-25 원문 보기 ↗
I understand how browser push infrastructure works and designed it properly.
Push notifications have become a core part of modern applications.
Whenever you receive:
At first glance, sending a notification looks simple:
Backend sends message → User receives notification
But internally, it involves multiple systems working together:
In this blog, we will understand how Web Push Notifications work internally and implement a complete push notification system using React.js and Golang.
Web Push allows servers to send messages to browsers even when the website is not open.
The browser displays notifications using a background process called a Service Worker.
Backend → Push Service → Browser → Service Worker → Notification
The important idea: Your backend never directly communicates with the browser.
Instead, every browser provides a Push Service that acts as a delivery system. for example; Chrome: Google Firebase Cloud Messaging, Firefox: Mozilla Push Service
Our system contains five major components.
Subscription Flow:
React Application → Service Worker Registration → Push Subscription → Golang Backend → Database
Notification Delivery Flow:
Golang Backend → Browser Push Service → Service Worker → User Notification
Before creating subscriptions, our application needs public/private keys.
Generate them using:
package main
import (
"fmt"
webpush "github.com/SherClockHolmes/webpush-go"
)
func main(){
privateKey, publicKey, _ := webpush.GenerateVAPIDKeys()
fmt.Println("Public:",publicKey)
fmt.Println("Private:",privateKey)
}
The generated keys:
Public Key:
Private Key:
The frontend handles:
A Service Worker is a JavaScript file that runs separately from your React application.
It works even when:
Responsibilities:
This is managed by browsers.
Backend responsibilities:
Before sending notifications, the browser needs to register itself.
User Opens Website → Allow Notification Permission → Register Service Worker → Generate Push Subscription → Send Subscription To Backend → Store In Database
First, check browser support.
if ( "serviceWorker" in navigator && "PushManager" in window ) { console.log("Push supported"); }
Browsers require user approval.
async function requestPermission() {
const permission = await Notification.requestPermission();
if(permission !== "granted"){
return;
}
console.log( "Notifications enabled" );
}
React cannot listen for background events. We register a worker:
const registration = await navigator.serviceWorker.register("/worker.js" );
console.log( "Worker registered", registration );
Now we create a browser subscription.
const subscription = await registration.pushManager.subscribe({ userVisibleOnly:true, applicationServerKey: VAPID_PUBLIC_KEY });
await fetch( "/api/subscriptions", { method:"POST", body: JSON.stringify(subscription) });
The generated subscription contains:
{ "endpoint":"https://push-service.com/xxx", "keys":{ "p256dh":"public-key", "auth":"secret-key" } }
This is the browser address where notifications will be delivered.
Create: public/worker.js
A Service Worker listens for push events.
self.addEventListener( "push", event => {
const data = event.data.json();
event.waitUntil(self.registration.showNotification(data.title,
{ body:data.message, icon:"/icon.png" }));
});
Important: The notification is created outside React. React does not even need to be running.
For implementing Web Push Protocol in Golang, we use:
go get github.com/SherClockHolmes/webpush-go
Import the package:
import (
webpush "github.com/SherClockHolmes/webpush-go"
)
This package handles:
Example implementation:
func SendNotification(subscription Subscription, message string) error {
_,err := webpush.SendNotification([]byte(message),
&webpush.Subscription{Endpoint: subscription.Endpoint,
Keys:webpush.Keys{Auth: subscription.AuthKey, P256dh: subscription.P256dhKey}},
&webpush.Options{VAPIDPublicKey: PUBLIC_KEY, VAPIDPrivateKey: PRIVATE_KEY})
return err
}
The Go server:
VAPID stands for: Voluntary Application Server Identification
It proves that:
This notification came from an authorized backend.
It uses two keys:
Public Key : Shared with Browser
Private Key : Stored only on Backend
Never expose VAPID_PRIVATE_KEY in frontend code.
A demo notification system is easy. A production notification system requires handling edge cases.
A single user may have: Chrome Desktop, Android Device, Firefox
Each browser creates a different subscription.
Bad design: One user = One subscription
Better: One user = Many subscriptions
Push subscriptions can expire.
Reasons:
Remove invalid subscriptions.
Sending notifications directly from APIs works for small systems.
But imagine: 1 Million users
Better architecture:
Application Event → Kafka → Notification Workers → Browser Push Services → Users
Benefits:
Failures happen because of:
A production system should have:
Example:
Notification Failed → Retry Queue → Try Again → Dead Letter Queue
WebSockets are great when users are actively using the application.
Examples:
But WebSockets require an active connection.
When:
your React application cannot receive messages.
Web Push solves this using Service Workers that can wake up independently from the application.
A few things to remember: