6 min read
ably
nextjs
realtime
web-sockets
event-driven-architecture

Real Time Features with Ably in Next.js

A practical guide to building real-time features in Next.js with Ably for live tracking, presence, and low-latency notifications without managing raw WebSocket infrastructure.

Driver ApppublisherAbly Channelpub/sub streamCustomer AppsubscriberDispatch DashboardsubscriberPresencepublishsubscribesubscribe

Real-Time Features with Ably in Next.js

I built a towing platform where drivers, dispatchers, and customers all needed the same thing at the same time: live, trustworthy updates. Once the product crossed 1,000+ concurrent users, the hard part was not sending events—it was keeping connections stable, preserving message ordering, and ensuring the UI remained accurate when the network misbehaved.

Ably fit that problem well because its channels, presence, reconnect handling, message ordering, and SSE support let me focus on product logic instead of operating real-time infrastructure myself.

Why Real-Time Gets Messy

At small scale, real-time feels easy:

At production scale, the failure modes show up quickly:

Ably's documentation makes an important operational detail explicit: connection minutes are billed for each open realtime connection, and concurrent connections are a first-class scaling concern.

That is why I treat realtime as a systems problem, not just a frontend feature.

The user-facing promise is simple:

See where the driver is right now.

The implementation must handle reconnects, permissions, delivery ordering, and the reality that thousands of clients may be attached to channels simultaneously.

Channels and Pub/Sub

Channels are the core building block I rely on first.

Publishers send messages to a channel.

Subscribers receive messages from a channel.

Multiple channels can share a single connection.

For a towing platform, I typically separate channels by concern:

dispatch:job:{jobId}
presence:dispatch
notifications:user:{userId}
admin:ops

This partitioning matters because it:

The channel name itself becomes part of the domain language.

A Simple Publish Flow

// app/api/jobs/[jobId]/location/route.ts
 
import { NextResponse } from "next/server";
import Ably from "ably";
 
const ably = new Ably.Rest(process.env.ABLY_API_KEY!);
 
export async function POST(
  req: Request,
  { params }: { params: Promise<{ jobId: string }> },
) {
  const { jobId } = await params;
  const body = await req.json();
 
  await ably.channels.get(`dispatch:job:${jobId}`).publish("location.updated", {
    jobId,
    lat: body.lat,
    lng: body.lng,
    speed: body.speed,
    ts: Date.now(),
  });
 
  return NextResponse.json({ ok: true });
}

This is the kind of write path I prefer:

  1. Route Handler receives trusted input.
  2. The event is published.
  3. The request completes.

The client never receives privileged credentials, and the channel name becomes part of the domain model.

Presence and Online State

Presence is the feature I use whenever I need to answer questions like:

In a dispatch platform, I may use presence to show active drivers and dispatchers while keeping the authoritative status in the domain database.

Presence should be treated as a live activity signal, not durable business state.

Presence in Next.js

"use client";
 
import { useEffect } from "react";
import Ably from "ably";
 
const client = new Ably.Realtime({
  authUrl: "/api/ably-token",
});
 
export function DispatchPresence({ dispatchId }: { dispatchId: string }) {
  useEffect(() => {
    const channel = client.channels.get(`presence:dispatch:${dispatchId}`);
 
    channel.presence.enter({
      role: "dispatcher",
    });
 
    return () => {
      channel.presence.leave();
    };
  }, [dispatchId]);
 
  return null;
}

I keep presence state narrow and ephemeral.

If I need durable "last seen" data, I store it separately.

If I need live activity, presence is the right abstraction.

Reconnects and Ordering

Reconnect handling is where managed realtime infrastructure becomes valuable.

Ably automatically reconnects clients, preserves connection state when possible, and reattaches channels after interruptions.

Even so, I always include timestamps or sequence numbers in realtime payloads.

let lastSeq = 0;
 
function applyLocationUpdate(event: { seq: number; lat: number; lng: number }) {
  if (event.seq <= lastSeq) return;
 
  lastSeq = event.seq;
 
  updateMarker(event.lat, event.lng);
}

This tiny guard prevents:

SSE, WebSockets, and Ably

I think about SSE, WebSockets, and Ably as different tools rather than competing technologies.

Use SSE when:

Use WebSockets when:

Use Ably when:

For most product teams, managed realtime is the fastest route to production.

What Breaks at Scale

The transport layer is rarely the first thing that fails.

Usually, the surrounding application logic breaks first.

Excessive Work Per Event

A location update should not trigger:

Connection Discipline

Every open realtime connection counts.

Unused tabs, duplicate sessions, and forgotten dashboards accumulate quickly.

Poor Channel Design

One giant channel means:

Separating channels early keeps fanout targeted and predictable.

Notifications That Feel Instant

The fastest notification systems I have built treat notifications as events rather than synchronous work.

Instead of waiting for unrelated operations to complete:

  1. Publish an event.
  2. Let subscribers react.
  3. Update the UI immediately.

I also avoid sending large payloads through realtime channels.

An event should usually contain:

How I Wire It in Next.js

My setup is intentionally simple:

This gives me a clean separation between business logic and transport concerns.

The page renders its initial state on the server, and Ably layers realtime updates on top.

Practical Takeaways

Ably works well when I need realtime features without owning the entire WebSocket lifecycle.

Channels provide the pub/sub backbone.

Presence provides live activity signals.

Reconnect handling eliminates many edge-case failures.

SSE remains useful when a simple server-to-browser stream is enough.

The biggest scaling risks are:

References

  1. Pub/Sub | Channel Concepts
  2. Pub/Sub How To
  3. Presence
  4. Capabilities
  5. Pricing Overview
  6. Ably Pricing
  7. Messages
  8. Connection State Recovery
  9. SSE Support
  10. Server-Sent Events and Ably