Next.js does not keep WebSocket connections open natively because its core model is request-response, but you have three viable paths in 2026: server-sent events for one-way updates, managed real-time services like Pusher or Ably for zero-ops bidirectional messaging, and the experimental native WebSocket support in Next.js that upgrades requests inside App Router route handlers. For production-grade bidirectional features such as chat, calls, and collaborative editing, most teams pair Next.js with a dedicated real-time layer like VideoSDK rather than managing raw sockets themselves. This guide walks through all three options, their trade-offs, and how to deploy each safely.
Real-time features have shifted from nice-to-have to table stakes. Live dashboards, in-app chat, collaborative cursors, presence indicators, and instant notifications all share one requirement: the server must push data to the client without the client asking first.
Here's the friction: Next.js was architected around the request-response cycle. Every render path it offers, from static generation to server-side rendering to edge functions, assumes a request arrives, work happens, and a response leaves. A WebSocket inverts that assumption. The connection stays open, state accumulates, and the server holds a long-lived conversation with each client.
By the end of this article you'll understand the architectural mismatch, the three realistic options for adding real-time behavior to a Next.js app, how the experimental native WebSocket support works, what deployment on Vercel and self-hosted servers changes, and the pitfalls that bite most teams on their first attempt.

Why Next.js and WebSockets Don't Naturally Fit Together

Next.js is a rendering framework, not a connection framework. Every rendering model it ships, whether static site generation, server-side rendering, or edge rendering, terminates when the response is sent. Nothing in that pipeline is designed to hold a socket open for minutes or hours.
The bigger constraint is the serverless function model. When you deploy Next.js to Vercel, your route handlers run inside ephemeral compute instances. These instances are spun up on demand, have hard execution-time caps, and can be frozen or recycled between requests. A WebSocket connection is fundamentally stateful: it assumes the process holding the socket stays alive for the lifetime of the conversation. Serverless compute is fundamentally stateless. Those two assumptions collide directly.
This mismatch is why so many "Next.js WebSocket" tutorials quietly break in production. They work on a local development server, where the Node process never dies, then fail the moment they hit a platform that recycles functions mid-connection.
The good news is that the landscape has matured. You now have three realistic paths, each with a clear fit, and Next.js itself is experimenting with first-class WebSocket support. Understanding which path matches your use case is the real engineering decision.

Option 1: Server-Sent Events for One-Way Updates

Server-sent events, or SSE, deliver server-to-client streaming over a plain HTTP connection, and they are the simplest real-time option available to Next.js today. The client opens a normal HTTP request, the server keeps the response body open, and data flows one way: from server to browser.
SSE is sufficient whenever the client only needs to receive updates. Build progress bars, live notification feeds, price tickers, status changes on an order, and streaming AI responses all fit this pattern. The browser handles reconnection automatically through its native event-stream implementation, which removes an entire class of reconnection bugs you'd otherwise write yourself.
The limitations are equally clear. SSE provides no client-to-server channel, so anything interactive, like chat, multiplayer editing, or signaling for video calls, needs a separate mechanism for upstream messages. On Vercel, streaming responses are also subject to function duration caps, so a long-lived SSE stream can be cut off unless you design your client to re-subscribe.
The decision rule is simple: if data only flows downhill, from server to client, choose SSE. The moment the client needs to talk back on the same low-latency channel, you need a WebSocket or a managed real-time service.

Option 2: Managed Real-Time Services

Managed real-time platforms, including Pusher, Ably, and Supabase Realtime, move the WebSocket layer out of your Next.js app entirely. Your server publishes events through the provider's API, clients subscribe through the provider's browser SDK, and the provider handles the connection mesh, presence, reconnection, and scaling.
The benefits are substantial. There is no server infrastructure for you to operate, connections scale automatically with your audience, and most providers ship built-in authentication and channel-level authorization so you don't hand-roll token validation for every subscriber. For a team shipping a chat feature in a sprint, this is often the fastest path to production.
The drawbacks are vendor lock-in, per-message or per-connection pricing that grows with usage, and an extra network hop that adds latency between your Next.js server and the provider's edge. If your use case is real-time video or audio specifically, a purpose-built communication SDK beats a generic pub/sub platform. VideoSDK, for example, provides video calling SDKs with rooms, participants, and tracks as first-class concepts, plus a Prebuilt UI Kit that embeds a full call interface without custom socket code, which removes the entire signaling layer from your scope.

Option 3: Native WebSocket Support in Next.js

Next.js now offers experimental native WebSocket support, enabled through a feature flag in the framework configuration and consumed through an upgrade-style response API in App Router route handlers. This is the most significant change to the Next.js real-time story in years, because for the first time the framework itself acknowledges persistent connections as a supported pattern rather than something you bolt on with a custom server.
The architecture splits cleanly between client and server. On the client side, a component marked with the client-only directive creates the WebSocket instance and manages its lifecycle inside an effect hook. On the server side, a route handler receives the HTTP upgrade request and hands your application logic a live socket object with which you can send and receive messages.
The following diagram shows the full flow, from the browser's initial upgrade request through the route handler to your application logic:
Architecture Diagram
Two caveats shape whether you should adopt this today. First, the feature is explicitly experimental: the API surface can change between releases, and you should treat any production use as early-adopter risk. Second, compatibility varies by deployment target. The experimental API is designed to work with Vercel's edge-capable functions, but behavior differs from a traditional long-running Node process, and duration limits still apply. Test on your actual hosting platform before committing.

Setting Up the Experimental Flag

Enabling native WebSocket support means editing your Next.js configuration file to turn on the experimental WebSockets flag. The intent is straightforward: you are telling the framework to allow route handlers to accept upgrade requests instead of rejecting them.
Because this is a feature gate on an unstable API, treat it as a staging-environment experiment first. Pin your Next.js version, read the release notes before upgrading, and keep the surface area of code that depends on the experimental API as small as possible so a future breaking change is cheap to absorb.

Creating a WebSocket Route Handler

A WebSocket route handler lives in your App Router directory structure like any other route, but instead of returning a rendered response, it exports a handler that receives the upgrade request and yields a live socket to your application code.
The lifecycle works in four phases. First, the client sends an HTTP upgrade request, which the framework routes to your handler. Second, your handler accepts the upgrade, and the framework establishes the persistent connection. Third, you attach listeners to the socket for incoming messages, connection close events, and error events. Fourth, and this is the part most tutorials skip, you clean up.
Cleanup matters more than it first appears. Every listener you attach, every timer you start, and every reference you store per-connection accumulates in server memory. If a connection closes and you don't remove its listeners and stored references, you leak memory in direct proportion to your traffic. A graceful close handler that tears down all per-connection state is not optional hygiene; it is the difference between a stable service and one that degrades over hours.
Error handling deserves equal attention. A socket that errors without a registered handler can crash the surrounding execution context or leave half-cleaned state behind. Register an error listener on every socket, log the failure, and route the connection through the same teardown path as a normal close.

Consuming the Socket in a Client Component

On the client side, the WebSocket instance belongs in a component marked with the client-only directive, because the browser's WebSocket implementation only exists at runtime in the browser, not during server rendering.
The established pattern has a clear shape. Inside an effect hook that runs when the component mounts, you construct the WebSocket against your endpoint URL and store the resulting reference so later renders can reach it. You register a message handler that receives incoming server events, and you register close and error handlers that trigger your reconnection logic.
One guard is essential: check that the code is running in a browser context before constructing the socket. During server-side rendering or static generation, the global WebSocket constructor doesn't exist, and attempting to instantiate it during a server render throws immediately. This is the single most common source of build-time crashes in Next.js real-time code.
Reconnection is the other half of a production-grade client. Networks drop, laptops sleep, and servers restart. A robust client detects the close event, waits with an increasing delay between attempts to avoid hammering a recovering server, and re-subscribes to any channels or state it needs once the connection is re-established. Without this, your real-time feature silently dies the first time a user's train goes through a tunnel.

Deployment Considerations for Next.js WebSockets

Where you host determines which options actually work, and this is where architecture decisions get made or broken.
On Vercel, functions have execution-time caps and instances can be recycled, which historically made raw WebSockets impossible. The experimental native support is designed to work within the edge-capable function model, but you must verify current duration limits and behavior for your plan, because the constraints differ between edge and standard functions. For mission-critical persistent connections, many teams still route around the platform entirely.
Self-hosting changes everything. Running Next.js behind your own Node process, typically with a custom server built on Express or Fastify combined with the widely used ws library, gives you full control over connection limits, timeouts, and lifecycle. The trade is operational: you now own process supervision, TLS termination, and horizontal scaling.
Scaling introduces two hard problems. First, with multiple server instances behind a load balancer, a client's messages must keep reaching the same instance that holds its socket, which requires sticky sessions at the load balancer. Second, when a message must reach clients connected to different instances, the instances need a shared message broker, with Redis pub/sub or NATS being the standard choices, to fan out events across the cluster:
Architecture Diagram
Security is the final deployment layer. Validate the origin of every upgrade request so unknown sites can't open sockets against your server. Authenticate connections, typically by passing a short-lived signed token with the upgrade request and verifying it server-side before accepting the connection. And serve everything over TLS, because an unencrypted WebSocket is readable by every hop in between.

Common Pitfalls and How to Avoid Them

Most Next.js WebSocket failures trace back to four recurring mistakes.
The first is referencing the browser's WebSocket global during server rendering. The fix is the browser-context guard plus keeping all socket construction inside client-marked components and effects.
The second is connection drops caused by serverless cold starts and instance recycling. If your handler runs on ephemeral compute, connections die when the instance does. The fix is either the experimental edge-compatible path, a long-running self-hosted process, or a managed service that owns connection persistence for you.
The third is forgetting to close sockets when a component unmounts. A user navigates away, the component unmounts, but the socket reference lives on, holding a connection open and leaking handlers. The fix is closing the socket in the effect's cleanup phase, every time, without exception.
The fourth is misconfigured infrastructure: proxies or load balancers that strip the upgrade headers, or CORS policies that block the WebSocket handshake from cross-origin clients. The fix is ensuring every proxy in the chain forwards upgrade and connection headers, and that your origin checks explicitly allowlist your own client domains.

Choosing the Right Approach for Your Project

The right choice follows from four questions: does data flow both ways, what latency does the experience need, where is the app hosted, and how much operational overhead can the team absorb?
Approach Direction Latency Hosting Fit Operational Load Best For
Server-Sent Events Server to client only Low Works on Vercel with limits Minimal Notifications, progress, tickers
Managed service (Pusher, Ably, VideoSDK) Bidirectional Low, provider-dependent Any host Minimal, but vendor cost Chat, presence, live calls
Native experimental WebSockets Bidirectional Lowest, direct Edge-capable, verify limits Medium, still beta Custom protocols, early adopters
Custom server with ws library Bidirectional Lowest, direct Self-hosted only High, full ownership Full control, strict compliance
Consider two concrete scenarios. A team building a simple notification feed for a SaaS dashboard needs one-way pushes a few times per hour. SSE is the obvious answer: no vendor, no experimental flags, automatic browser reconnection. A team building a live chat feature with typing indicators and presence needs bidirectional low-latency messaging at scale. A managed real-time layer, or a communication-focused SDK like VideoSDK for anything involving audio and video, gets them there in days instead of weeks, while the native experimental route suits a team that wants full protocol control and can absorb beta risk.

Definitions Glossary

WebSocket: A persistent, bidirectional communication channel that begins as an HTTP upgrade request and then stays open, allowing both server and client to push messages at any time without new requests.
Server-Sent Events (SSE): A one-way streaming protocol where the server keeps an HTTP response open and pushes updates to the browser, which reconnects automatically if the stream drops.
Upgrade request: The initial HTTP request a client sends to convert a normal connection into a WebSocket, which servers and proxies must explicitly support and forward.
Sticky sessions: A load balancer behavior that routes all requests from one client to the same server instance, required so a WebSocket's messages keep reaching the instance holding the connection.
Message broker: A middleware layer, such as Redis pub/sub or NATS, that lets multiple server instances broadcast events to clients connected on different machines.
Reconnection strategy: The client-side logic that detects a dropped socket, retries with increasing delays, and restores subscriptions once the connection is re-established.

Key Takeaways

  • Next.js is built on a request-response model, so persistent WebSocket connections require one of three deliberate architectural choices rather than a default path.
  • Server-sent events are the simplest option and fully sufficient whenever data only flows from server to client, such as notifications and progress updates.
  • Managed real-time services remove connection management entirely, and for audio or video use cases, purpose-built SDKs like VideoSDK's video calling SDKs handle rooms, participants, and signaling out of the box.
  • Native experimental WebSocket support in Next.js brings upgrade handling into App Router route handlers, but it remains beta and should be validated on your actual hosting platform before production use.
  • Deployment realities, including function duration caps, sticky sessions, message brokers, and origin-checked authentication, determine whether your real-time feature survives contact with production traffic.

Conclusion

Next.js and WebSockets are no longer incompatible, but the fit depends on your architecture matching your use case. Server-sent events cover one-way updates with minimal effort. Managed services and communication SDKs like VideoSDK handle bidirectional real-time, including full video and audio calling, without you operating connection infrastructure. The experimental native WebSocket support opens a direct path for teams that need full control and can absorb beta risk.
Experiment with the experimental flag in a development environment first, and lean on managed real-time infrastructure when production stability matters more than protocol control. You can explore the official Next.js documentation for the latest WebSocket API status, browse working examples in the VideoSDK code samples, and start free at app.videosdk.live/login.
What are you building with Next.js WebSockets? Drop a comment, and join the VideoSDK Discord community to compare notes with other developers shipping real-time features.

Free $20 Balance for AI Voice Agents & Video Calls

FAQ