DriversRecommendedOutdated drivers can make a good PC feel brokenScan driver issues before chasing fixes manually.Scan NowOctober DealsAmazon USOctober deal check: compare before you payAmazon US: current deals, useful picks and tech finds.Check DealsSlow PC?RecommendedPC slow today? Run a repair scan before it gets worseResolve common Windows issues and optimize system performance.Scan Now×
Skip to content
World desk6 min

Next.js SSE: Reliable Server-Sent Events with the App Router

Build a Next.js App Router SSE endpoint with a Web ReadableStream, protocol-correct event records, explicit reconnect and replay behavior, and production checks for buffering and cleanup.
Special offer. See more information about Outbyte and uninstall instructions. Please review EULA and Privacy policy.

Use a Next.js App Router GET Route Handler to return a Web ReadableStream, then format every message according to the Server-Sent Events protocol. A stream that works in the handler can still appear all at once if a proxy buffers it, and EventSource reconnecting does not recover messages your application never saved. This guide covers the endpoint, the wire format, reconnect and replay decisions, and checks to run through your actual deployment path.

How do I use Server-Sent Events (SSE) with the Next.js App Router?

Create a Route Handler at an app/**/route.ts path and return a Web Response whose body is a ReadableStream. The handler is the endpoint primitive; it does not format SSE records for you. Next.js documents the Route Handler conventions and generic stream mechanics in its Route Handler reference.

As an Amazon Associate I earn from qualifying purchases.

This minimal example emits a JSON message every five seconds. It is a live-only example: it does not store messages or promise that a reconnecting client will catch up. Replace the interval producer with your application’s event source, such as a subscription to a job or notification service.

Special offer. See more information about Outbyte and uninstall instructions. Please review EULA and Privacy policy.
const encoder = new TextEncoder();

export async function GET(request: Request) {
  let timer: ReturnType<typeof setInterval> | undefined;
  let closed = false;
  let controllerRef: ReadableStreamDefaultController<Uint8Array> | undefined;

  const cleanup = () => {
    if (closed) return;
    closed = true;
    if (timer !== undefined) clearInterval(timer);
    request.signal.removeEventListener("abort", cleanup);
  };

  const stream = new ReadableStream<Uint8Array>({
    start(controller) {
      controllerRef = controller;

      const send = (value: unknown) => {
        if (closed) return;
        try {
          // JSON.stringify produces a single-line data value for ordinary JSON payloads.
          controller.enqueue(encoder.encode(`data: ${JSON.stringify(value)}nn`));
        } catch {
          cleanup();
          try {
            controller.close();
          } catch {
            // The consumer may already have cancelled the stream.
          }
        }
      };

      send({ type: "connected", at: new Date().toISOString() });
      timer = setInterval(() => {
        send({ type: "update", at: new Date().toISOString() });
      }, 5000);

      if (request.signal.aborted) cleanup();
      else request.signal.addEventListener("abort", cleanup, { once: true });
    },
    cancel() {
      cleanup();
      controllerRef = undefined;
    },
  });

  return new Response(stream, {
    headers: {
      "Content-Type": "text/event-stream; charset=utf-8",
      "Cache-Control": "no-cache, no-transform",
    },
  });
}

The example demonstrates stream construction, encoding, and timer cleanup; it is not a production event broker. In a real handler, ensure the cancellation path unsubscribes from upstream work as well as clearing timers. The Web Streams cancellation lifecycle is the place to stop the producer when the consumer cancels; whether a disconnect is promptly propagated through the whole hosting stack must be checked in that deployment.

Cache-Control above is a practical choice for a live feed, not an SSE-mandated header. Select cache behavior for your use case. The essential protocol response type is text/event-stream.

What must an SSE event contain?

An EventSource response is UTF-8 text with fields written as lines. A blank line ends a record. For the protocol details, see the MDN SSE guide and the WHATWG HTML Standard, Server-sent events.

  • data: carries the event payload. Multiple data: lines are joined with newline characters by the browser.
  • event: optionally gives the event a name, which the client can listen for specifically.
  • id: optionally sets the event ID used by EventSource when reconnecting.
  • retry: optionally supplies a reconnection delay in milliseconds.
  • A line beginning with : is a comment, not an application event. It can be used as a keepalive.

For a JSON object, serializing with JSON.stringify gives a convenient single-line data: value. If sending arbitrary multiline text rather than JSON, emit each line as its own data: field and then terminate the record with a blank line. Do not treat a comment or heartbeat as a data event.

Special offer. See more information about Outbyte and uninstall instructions. Please review EULA and Privacy policy.
event: order-update
id: 8421
data: {"orderId":"A17","status":"shipped"}

: keepalive

In a browser, the default message listener receives records without an event: name. Named records use addEventListener:

const source = new EventSource("/api/events");

source.onmessage = (event) => {
  const data = JSON.parse(event.data);
  console.log(data);
};

source.addEventListener("order-update", (event) => {
  const data = JSON.parse((event as MessageEvent).data);
  console.log(data);
});

source.onerror = () => {
  // EventSource normally attempts to reconnect unless closed by the application.
};

How do I reconnect to a Next.js SSE stream?

The browser’s EventSource reconnects after a dropped connection by default. A server can send a retry: field to set the reconnection delay. If the stream has sent an id:, the browser retains that ID and sends it on a later connection in the Last-Event-ID request header, as specified by the WHATWG standard.

Those mechanics restore a connection; they do not create a message history. Decide explicitly whether reconnecting clients need to resume from a cursor:

  • Live-only feed: omit IDs if they have no useful meaning, or document that a reconnect receives only future events. Messages sent while disconnected may be missed.
  • Catch-up feed: assign each event a stable ID from a durable ordered log or equivalent cursor source. Read and validate Last-Event-ID, then replay events after that cursor before switching to live delivery.

Do not use an in-memory counter or a process-local value as a durable cursor when requests can land on different instances or a process can restart. Define what happens if the cursor is malformed, expired, or no longer present in the event store; for example, reject it or direct the client to refresh its state. Exactly-once delivery does not follow from EventSource reconnecting: applications that require reliable processing need their own cursor, replay, and duplicate-handling policy.

Special offer. See more information about Outbyte and uninstall instructions. Please review EULA and Privacy policy.

Why does my SSE endpoint send events all at once?

A handler may enqueue events incrementally while an intermediary holds the response until it has buffered enough data or the response ends. Next.js warns that reverse proxies and load balancers must pass streaming responses without buffering; its self-hosting guide says nginx or a similar proxy may need buffering disabled, suggests X-Accel-Buffering: no for nginx, and notes that some cloud load balancers buffer by default.

Configure the infrastructure actually in front of the application: CDN, gateway, reverse proxy, load balancer, and hosting runtime. For nginx, the response header can be added where appropriate:

"X-Accel-Buffering": "no"

That header is an nginx-related hint, not a universal switch for every intermediary. Confirm each layer’s buffering and idle-timeout behavior in its own configuration. Vercel documents streaming support for Next.js, but it is one hosting option rather than a requirement; self-hosting can stream when the proxy and load-balancer path is configured to do so.

Keepalive comments may help prevent an otherwise idle connection from being closed, but choose the heartbeat interval against the real idle timeouts on the route. The protocol sources do not establish one universally safe interval.

Special offer. See more information about Outbyte and uninstall instructions. Please review EULA and Privacy policy.
Independent reader supportYour contribution helps us test, update, and keep practical guides available for everyone.Support on Ko-Fi

How should I test the full production path?

Test the deployed URL rather than relying only on a local Route Handler test. The point is to distinguish application framing from buffering or disconnect behavior in the network path.

  1. Open the endpoint with a streaming-capable client, for example curl -N https://your-host.example/api/events. Verify the response has Content-Type: text/event-stream and that complete records arrive at intervals instead of appearing only when the connection ends.
  2. Leave the connection idle long enough to exercise your heartbeat and the relevant proxy or load-balancer idle timeout. Confirm comments do not appear as application messages in the browser.
  3. Drop the connection and let the browser’s EventSource reconnect. If the product promises catch-up, verify that the new request’s Last-Event-ID is validated and that missing records are replayed from the chosen durable source.
  4. Close the browser tab or cancel the client connection. Confirm the Route Handler’s cancellation path stops timers and upstream subscriptions rather than leaving producer work running.

Run these checks after deployment or proxy changes as well as during development: an endpoint can be correctly framed and still fail to deliver incrementally through a changed infrastructure path.

How many SSE connections can a browser keep open?

MDN Web Docs, accessed October 4, 2026, reports a limit of six open SSE connections per browser and domain outside HTTP/2. With HTTP/2, the number of simultaneous streams is negotiated, with a default of 100 cited by MDN. These are browser and transport-qualified figures, not universal server capacity limits; actual negotiated settings and deployment resources matter. The figures are documented in the MDN SSE guide.

If a page opens multiple feeds, account for connections shared across tabs and consider consolidating events behind a single stream per relevant origin. Separately assess server-side connection capacity, subscription fan-out, and the chosen HTTP transport.

Free tools Windows power users keep installed

One-click scans. No signup required.

Special offer. See more information about Outbyte and uninstall instructions. Please review EULA and Privacy policy.

Is Next.js page streaming the same as SSE?

No. Next.js and React can stream server-rendered page output, for example through route loading UI and Suspense, while an SSE endpoint is a long-lived response with the EventSource MIME type and event-record framing. The capabilities may both use streaming underneath, but streaming rendered HTML does not turn a page response into an SSE feed. Vercel documents Next.js streaming across Route Handlers, Functions, and React Server Components in its Next.js documentation; use a Route Handler and the SSE wire format when the browser should consume server-to-browser events.

Product prices and availability are accurate as of the date/time indicated and are subject to change. Any price and availability information displayed on Amazon at the time of purchase will apply.

Leave a Reply

Your email address will not be published. Required fields are marked *

Special offer. See more information about Outbyte and uninstall instructions. Please review EULA and Privacy policy.

More from the Wire

  1. World desk4 min
    How to Spot an AI Voice Scam Before Sending MoneyDon’t rely on how a caller sounds. Pause, call back through a known number, and verify the emergency with another trusted person before sending money.
  2. Mountain View desk4 min
    Google’s SynthID Detector: How to Check AI-Generated Images, Video and AudioGoogle’s SynthID Detector looks for an embedded watermark in supported images, video and audio. Here is what its results do—and do not—show.
  3. Redmond desk20 min
    How to create a link to File or Folder in Windows 11Windows 11 gives you several ways to point to a file or folder without moving or duplicating it. You can create a desktop shortcut,…
Recommended PC Tool
Recommended PC Tool
Crashes, No Sound, or Screen Glitches?Free driver scan
Windows Errors? Fix Them Before They SpreadFree repair scan

Two free Windows tools

One Free Minute Could Fix That PC

Before you go - each of these free tools takes about a minute and tackles what quietly slows a Windows PC down.

Special offer. View Outbyte info, uninstall instructions, EULA, and Privacy Policy.