Module A-14·27 min read

Standalone output internals, static export feature graveyard (what silently breaks), WebSocket authentication (HttpOnly cookie on upgrade handshake, token validation on reconnect, secret rotation with 40K live connections), Pusher/Ably vs Partykit vs self-hosted socket layer, and the cold start optimisation playbook.

JJS
Written by Jatin Jain Saraf · Senior Software Engineer

A-14 — Self-Hosting vs Serverless, WebSockets, and Long-Lived Connections

Who this is for: Architects making infrastructure decisions for Next.js applications that have outgrown the "just deploy to Vercel" answer — teams self-hosting on Kubernetes, applications that need WebSockets or Server-Sent Events, and anyone who needs to understand the real constraints that serverless imposes on connection-oriented features.


The Fundamental Serverless Constraint

Serverless (Vercel Functions, AWS Lambda, Cloudflare Workers) is the dominant deployment model for Next.js because it matches the request-per-invocation model well. A request comes in, the function runs, the response goes out. For stateless HTTP, this is ideal.

The constraint: serverless functions don't persist between requests. Each invocation may run in a new container. There's no in-process state, no long-lived connections, no background threads.

This constraint rules out a specific category of features:

  • WebSocket servers (require persistent TCP connections)
  • Server-Sent Events that outlive a response (require persistent HTTP connections)
  • In-memory job queues (state lost on restart)
  • Background processing beyond the request duration
  • In-process caches shared across requests (each instance has its own memory)

These are not framework limitations — they're infrastructure physics. The question is which of these your application actually needs, and what the alternatives are.


WebSockets in a Next.js Application

Next.js doesn't have built-in WebSocket support. The Route Handler model (request → response) doesn't map to WebSockets (persistent bidirectional connection). This is not a missing feature — it's an architectural mismatch.

The three architectural patterns for WebSockets alongside Next.js:

Pattern 1: Separate WebSocket server

The most common production pattern. A dedicated Node.js service (Socket.io, ws, uWebSockets.js) handles WebSocket connections. The Next.js application communicates with it via HTTP or a message broker.

text
tsx

Pattern 2: Partykit / Cloudflare Durable Objects

Cloudflare Durable Objects are stateful edge workers — they persist state and accept WebSocket connections. Partykit is a higher-level abstraction built on Durable Objects.

ts

This runs entirely at the edge — WebSocket connections go to the nearest Cloudflare datacenter, not a central origin server. For real-time applications with global users, this is a compelling architecture.

Pattern 3: Serverless WebSockets (Pusher, Ably, Soketi)

Managed WebSocket infrastructure as a service. Your Next.js application publishes events to the service via HTTP. The service maintains WebSocket connections to clients and delivers events.

ts
tsx

The trade-off: operational simplicity (no WebSocket server to manage) vs. cost (per-message pricing at scale) and latency (extra hop through the managed service).


Server-Sent Events

Server-Sent Events (SSE) are a lighter-weight alternative to WebSockets for one-directional server-to-client streaming. They use regular HTTP, work through most proxies, and automatically reconnect.

In Next.js Route Handlers, SSE works on self-hosted deployments but not on Vercel Functions — Vercel Functions have a maximum response duration, and SSE requires a persistent connection:

ts

On Vercel, SSE responses are limited to Vercel's streaming response duration limit. For indefinite streams, you need self-hosted or a managed SSE service.


Self-Hosting on Kubernetes

For teams that need persistent connections, long-lived processes, or full infrastructure control, Kubernetes is the production target.

The canonical self-hosted architecture:

text

The key difference from serverless: pods persist between requests. In-process state (Prisma connection pools, warm Module singletons) survives across requests. This is why the Prisma globalThis singleton pattern (P-5) matters — on Kubernetes, the singleton is reused across thousands of requests per pod. On serverless, the singleton is created anew each cold start.

Kubernetes deployment manifest (simplified):

yaml

The livenessProbe and readinessProbe point to the health check Route Handler from P-14. Kubernetes uses these to determine which pods should receive traffic and whether to restart unhealthy pods.


ISR on Self-Hosted Kubernetes

ISR on Kubernetes has the distributed cache invalidation problem from A-3 — each pod has its own filesystem cache. revalidatePath('/products') on Pod A doesn't invalidate Pod B.

Sign in to keep reading

The rest of this module is free — sign in with Google to unlock it and track your progress.

Sign in & Register

Discussion

0

Join the discussion

Loading comments...

© 2026 Jatin Jain Saraf (JJS). All rights reserved.