Lesson 52 of 81
A WebSocket needs one server process holding a connection open for minutes or hours. Next.js's deployment story is built around short-lived, independently-scaled function invocations — the two models don't fit together, and knowing why shapes which workaround you reach for.
12 min read
Every option explains itself — why the right one is right, and why each wrong one is wrong. Miss one and it comes back later on a spaced-repetition schedule.
This topic is about judgment and practical tradeoffs more than a single right answer — a mock interview is the better way to test yourself on it.
Be interviewed on this →Hints without spoilers, and a plain explanation of why a wrong answer was wrong — answered only from this lesson. Free account, no card.
Sign up freeWhat interviewers actually ask about this, and what a strong answer has to cover.
A strong answer covers
Likely follow-ups
A WebSocket connection is a single TCP connection, upgraded from an HTTP request, that both client and server keep open indefinitely so either side can push messages at any time. That requires one specific server process to hold that connection in memory for the connection's whole lifetime.
Next.js's default deployment target — Vercel, and most serverless/edge platforms modeled on it — runs each request as an independent, short-lived function invocation. There's no guarantee that two requests from the same browser land on the same process, invocations are expected to finish quickly (and get killed if they run long), and the whole scaling model assumes requests are stateless and interchangeable. A Route Handler can't hold a socket open for an hour without either the platform killing it or the platform being unable to horizontally scale, because "which server has this socket" becomes state the platform now has to track. This isn't a Next.js limitation exactly — it's a mismatch between what WebSockets require (a stateful, long-lived process) and what serverless compute is designed to make cheap (stateless, short-lived ones).
The real patterns route around this rather than fighting it:
fetch-based Route Handler far better than a raw socket does, at the cost of losing client-to-server push.// app/api/notifications/stream/route.ts
export async function GET() {
const encoder = new TextEncoder();
const stream = new ReadableStream({
start(controller) {
const send = (data: string) => {
controller.enqueue(encoder.encode(`data: ${data}\n\n`));
};
const interval = setInterval(() => {
send(JSON.stringify({ count: getUnreadCount() }));
}, 5000);
// Runs when the client disconnects — without this, the interval
// keeps the connection (and the server-side work) alive forever.
return () => clearInterval(interval);
},
});
return new Response(stream, {
headers: {
"Content-Type": "text/event-stream",
"Cache-Control": "no-cache",
Connection: "keep-alive",
},
});
}
// app/notifications/notification-badge.tsx
"use client";
import { useEffect, useState } from "react";
export function NotificationBadge() {
const [count, setCount] = useState(0);
useEffect(() => {
const source = new EventSource("/api/notifications/stream");
source.onmessage = (event) => setCount(JSON.parse(event.data).count);
return () => source.close();
}, []);
return <span>{count}</span>;
}
This gets server-to-client push over a Route Handler's own Response object — no separate socket infrastructure, no NextRequest/NextResponse extensions needed, just a streamed HTTP response the browser's built-in EventSource knows how to consume. The tradeoff is baked into the protocol: SSE is one-directional, so a chat app that needs the client to send messages back still needs a second channel (a normal fetch POST, or a Server Action) alongside it.
next start (or a custom server) as one long-lived process rather than deploying to serverless functions, you can attach a ws server to the same HTTP server and it works exactly like it would in any Node app — the constraint is about the deployment model, not about Next.js as a framework.This connects directly to the runtimes lesson: the Node.js and Edge runtimes Next.js Route Handlers can run in are both built around short-lived invocations, which is the same root constraint that rules out a long-lived WebSocket inside one. It's also a sibling to server-rendered streaming with Suspense — both are about pushing data to the client incrementally over time — but Suspense streams a single response once and finishes, where a live feed (via SSE or a socket) keeps sending indefinitely after the initial response has already completed.