CodeKits.devNew developer offer

React Real-Time Notifications with SSE

A practical walkthrough of react real-time notifications with sse, with code excerpts, failure checks and integration guidance.

By Jonathan · 5 min read

Published

React Real-Time Notifications with SSE tutorial cover
In this article

This walkthrough covers React Real-Time Notifications with SSE. An authenticated same-origin React and Node application. This lesson uses server-sent events for one-way notifications and a durable notification table for recovery.

The video is an illustrated code walkthrough with AI narration. Code blocks are integration excerpts, not a complete tested application. Helper names such as stores, middleware and clients describe responsibilities you must implement in your existing project. No live server, payment provider or device execution is claimed.

Before you begin

Use a separate development environment, fictional data and test credentials. Record dependency versions and follow the official references below for your installed versions. Keep a working baseline and add one workflow at a time.

1. Choose the transport for the job

For one-way notification updates, server-sent events can provide a straightforward persistent channel. Ordinary requests still handle user actions. Keep the notification records in durable storage so a disconnected browser can recover. The live stream is a delivery mechanism, not the only copy of the message. If you need bidirectional low-latency interaction, evaluate a different transport against those requirements.

Choose the transport for the job
Server -> browser: server-sent events
Browser -> server: ordinary authenticated requests
Durable state: notifications table
Live connection: delivery hint, not the database

2. Authenticate before opening the stream

Apply authentication before sending stream headers. Bind the connection to the verified user rather than a user identifier in the URL. Scope every delivered event to that user or their authorized audience. Native EventSource supports same-origin cookies, but not arbitrary authorization headers. Choose an authentication design that fits the client and avoid putting reusable access tokens into query strings.

Authenticate before opening the stream
app.get("/api/events", requireSession, (req,res) => {
 res.set({
  "Content-Type": "text/event-stream",
  "Cache-Control": "no-cache",
  "X-Accel-Buffering": "no"
 });
 res.flushHeaders();
 // Register this connection only for req.user.id.
});

3. Send a well-formed event

Each event has a stable identifier, an optional type, and a data payload followed by a blank line. Serialize the payload consistently. Keep events small and avoid including sensitive fields the interface doesn't need. Handle slow clients and backpressure instead of allowing an unbounded queue per connection. The durable notification record lets a client reload detail through an ordinary authorized request.

Send a well-formed event
res.write("id: " + event.id + "\n");
res.write("event: notification\n");
res.write("data: " + JSON.stringify(event.payload) + "\n\n");

4. Subscribe and clean up in React

Create the connection in an effect and close it when the component unmounts or the account changes. Validate received data and deduplicate by notification identifier before updating state. Show connection status without treating a temporary disconnect as data loss. Keep the notification list accessible, and avoid repeatedly interrupting assistive technology with every minor background event.

Subscribe and clean up in React
useEffect(() => {
 const source = new EventSource("/api/events");
 source.addEventListener("notification", event => {
  receiveNotification(JSON.parse(event.data));
 });
 return () => source.close();
}, []);

5. Replay missed events safely

Reconnects can include the last event identifier. Validate it and replay only the current user's authorized events after that point. Bound the replay window and size. If the cursor is too old, tell the client to fetch a fresh snapshot. Avoid a race between replay and subscribing to new events by using a coordinated durable feed or a documented reconciliation strategy.

Replay missed events safely
const cursor = req.get("Last-Event-ID");
// Validate cursor and scope replay to req.user.id.
// SELECT ... WHERE user_id=? AND id>? ORDER BY id
// Cap replay size; offer a fresh snapshot if too old.

6. Release disconnected clients

Clean up heartbeat timers and subscriptions when the connection closes. Configure proxy timeouts and buffering for streaming, and test through the real proxy rather than only localhost. Long-lived streams also need an authentication expiry policy. Close or revalidate connections when sessions expire or permissions change. Multiple Node processes need shared event distribution; an in-memory subscriber map reaches only one process.

Release disconnected clients
const heartbeat = setInterval(() => res.write(": keepalive\n\n"), 20000);
req.on("close", () => {
 clearInterval(heartbeat);
 unsubscribeCurrentConnection();
});

7. Keep read state on the server

Marking a notification as read is a normal protected mutation. Persist it so several tabs and devices can converge on the same state. Derive counts from an authoritative source or reconcile optimistic updates after failures. Don't assume every event is delivered exactly once. A reliable notification feature combines durable records, permission checks, deduplication, and recovery rather than relying only on a continuously open connection.

Keep read state on the server
PATCH /api/notifications/:id
Body: {read: true}
Server: verify owner, persist read_at
Client: reconcile list and unread count

Integration checks

Test disconnect/reconnect, duplicate events, another user’s cursor, expired sessions, slow clients, proxy buffering and multiple application instances.

These are verification instructions, not reported test results. The excerpts have not been executed as a complete integrated application. Review authentication, database schema, dependency versions and operational settings before deployment.

Official references

Explore CodeKits development services.