Integración de servicios WebSocket en un entorno serverless
Conéctese a proveedores de WebSocket administrados, ya que las funciones serverless no pueden mantener sockets de larga duración.
Integración de servicios WebSocket en un entorno serverless es una lección gratuita de Next.js 15 Fullstack (App Router + Server Actions) en CoddyKit. Esta es la lección 2 de 4. Puedes leer la lección completa abajo gratuitamente — luego la practicas en el navegador con un editor de código integrado y un tutor de IA 24/7. Forma parte de la ruta de aprendizaje de Next.js 15 Fullstack (App Router + Server Actions), y tu progreso se sincroniza en la web y la app de CoddyKit. El curso de Next.js 15 Fullstack (App Router + Server Actions) incluye 4 lecciones en total.
Partes de esta lección aún no han sido traducidas y se muestran en inglés.
Why Serverless and WebSockets Don't Mix
Serverless functions (like Next.js API routes deployed to Vercel, AWS Lambda, or similar platforms) are designed to be stateless and short-lived. Each invocation boots, handles a request, and terminates.
WebSockets, on the other hand, require a persistent, long-lived TCP connection between client and server. This creates a fundamental mismatch:
- Serverless runtimes have execution time limits (e.g. 10–30 seconds).
- There is no persistent process to hold an open socket across requests.
- Horizontal scaling means each new instance has no shared in-memory state.
The solution is to delegate real-time communication to a managed WebSocket provider — a dedicated service that maintains connections on your behalf while your serverless functions focus on business logic.
The Managed WebSocket Provider Pattern
Managed WebSocket providers expose an HTTP API that your serverless functions can call to push messages into persistent connections they maintain. Popular options include:
- Pusher / Pusher Channels — battle-tested, generous free tier, first-class Next.js support.
- Ably — low-latency pub/sub with edge-friendly token auth.
- Soketi — open-source, self-hosted Pusher-compatible server.
- PartyKit — built specifically for Next.js and Cloudflare Workers.
The typical flow is:
- Client connects to the provider's WebSocket endpoint and subscribes to a channel.
- Your Next.js Server Action or Route Handler calls the provider's REST API to publish an event.
- The provider fans the event out to all subscribed clients instantly.
Your serverless function never touches a socket directly — it simply makes an HTTP POST.
Setting Up Pusher in a Next.js 15 Project
Install both the server-side SDK (used in Server Actions / Route Handlers) and the client-side SDK (used in Client Components):
npm install pusher pusher-js
Add your Pusher credentials to .env.local. Never expose the secret key to the browser — prefix only the public values with NEXT_PUBLIC_:
// .env.local
PUSHER_APP_ID="your_app_id"
PUSHER_KEY="your_key"
PUSHER_SECRET="your_secret"
PUSHER_CLUSTER="eu"
NEXT_PUBLIC_PUSHER_KEY="your_key"
NEXT_PUBLIC_PUSHER_CLUSTER="eu"Creating a Singleton Pusher Server Instance
To avoid creating a new Pusher instance on every server-side call, export a singleton from a utility module. In Next.js 15 App Router the module is evaluated once per worker process (or edge invocation), so a module-level constant works perfectly.
This singleton is server-only — it imports the Node.js pusher package which must never reach the browser bundle. Mark the file accordingly with the Next.js convention.
// lib/pusher-server.ts
import Pusher from 'pusher';
// 'server-only' prevents accidental client-side imports
import 'server-only';
if (
!process.env.PUSHER_APP_ID ||
!process.env.PUSHER_KEY ||
!process.env.PUSHER_SECRET ||
!process.env.PUSHER_CLUSTER
) {
throw new Error('Missing Pusher environment variables');
}
export const pusherServer = new Pusher({
appId: process.env.PUSHER_APP_ID,
key: process.env.PUSHER_KEY,
secret: process.env.PUSHER_SECRET,
cluster: process.env.PUSHER_CLUSTER,
useTLS: true,
});Publishing Events from a Server Action
Server Actions in Next.js 15 are perfect for triggering real-time events: the user submits a form, the action persists data, then immediately publishes to Pusher. All of this happens server-side — the client never sees the secret key.
The pusherServer.trigger(channel, event, data) call makes an HTTPS request to Pusher's API, which is well within serverless execution time limits.
// app/chat/actions.ts
'use server';
import { pusherServer } from '@/lib/pusher-server';
interface ChatMessage {
id: string;
user: string;
text: string;
sentAt: string;
}
export async function sendMessage(
channelName: string,
user: string,
text: string
): Promise<void> {
const message: ChatMessage = {
id: crypto.randomUUID(),
user,
text,
sentAt: new Date().toISOString(),
};
// Persist to your database here (e.g. Supabase, Prisma, etc.)
// await db.messages.create({ data: message });
// Publish to all subscribers on this channel
await pusherServer.trigger(channelName, 'new-message', message);
}Creating the Client-Side Pusher Hook
On the client, create a reusable React hook that wraps pusher-js. The hook should:
- Initialise the Pusher client once (via
useRefor module-level singleton). - Subscribe to the requested channel and event on mount.
- Clean up the subscription on unmount to prevent memory leaks.
Using a module-level singleton for the PusherClient instance avoids reconnecting every time the hook re-renders.
// lib/pusher-client.ts
import PusherClient from 'pusher-js';
export const pusherClient = new PusherClient(
process.env.NEXT_PUBLIC_PUSHER_KEY!,
{ cluster: process.env.NEXT_PUBLIC_PUSHER_CLUSTER! }
);
// hooks/usePusherChannel.ts
import { useEffect } from 'react';
import { pusherClient } from '@/lib/pusher-client';
export function usePusherChannel<T>(
channelName: string,
eventName: string,
onEvent: (data: T) => void
): void {
useEffect(() => {
const channel = pusherClient.subscribe(channelName);
channel.bind(eventName, onEvent);
return () => {
channel.unbind(eventName, onEvent);
pusherClient.unsubscribe(channelName);
};
}, [channelName, eventName, onEvent]);
}Building the Real-Time Chat Client Component
Wire the custom hook into a Client Component. The component renders an optimistic list of messages and uses the Server Action to send new ones. Incoming Pusher events are appended to state — no polling required.
Mark the component with 'use client' since it uses browser APIs (useState, useEffect) and the Pusher SDK.
'use client';
import { useState, useCallback, useTransition } from 'react';
import { usePusherChannel } from '@/hooks/usePusherChannel';
import { sendMessage } from './actions';
interface Message {
id: string;
user: string;
text: string;
sentAt: string;
}
export function ChatRoom({
channelName,
currentUser,
}: {
channelName: string;
currentUser: string;
}) {
const [messages, setMessages] = useState<Message[]>([]);
const [input, setInput] = useState('');
const [isPending, startTransition] = useTransition();
const handleNewMessage = useCallback((msg: Message) => {
setMessages((prev) => [...prev, msg]);
}, []);
usePusherChannel<Message>(channelName, 'new-message', handleNewMessage);
function handleSubmit(e: React.FormEvent) {
e.preventDefault();
if (!input.trim()) return;
startTransition(() => sendMessage(channelName, currentUser, input));
setInput('');
}
return (
<div>
<ul>
{messages.map((m) => (
<li key={m.id}><strong>{m.user}:</strong> {m.text}</li>
))}
</ul>
<form onSubmit={handleSubmit}>
<input value={input} onChange={(e) => setInput(e.target.value)} />
<button type="submit" disabled={isPending}>Send</button>
</form>
</div>
);
}Authenticating Private Channels
Public Pusher channels are open to anyone who knows the channel name. For private rooms or per-user data, use private channels (prefixed with private-). These require a server-side authentication step.
Pusher's client SDK automatically requests a token from an auth endpoint before subscribing. Create this endpoint as a Next.js Route Handler:
// app/api/pusher/auth/route.ts
import { NextRequest, NextResponse } from 'next/server';
import { pusherServer } from '@/lib/pusher-server';
import { auth } from '@/lib/auth'; // your session helper
export async function POST(req: NextRequest) {
const session = await auth();
if (!session?.user) {
return NextResponse.json({ error: 'Unauthorized' }, { status: 401 });
}
const body = await req.text();
const params = new URLSearchParams(body);
const socketId = params.get('socket_id')!;
const channelName = params.get('channel_name')!;
// Optional: verify the user is allowed to join this channel
const authData = pusherServer.authorizeChannel(socketId, channelName, {
user_id: session.user.id,
user_info: { name: session.user.name },
});
return NextResponse.json(authData);
}Configuring the Client for Private Channels
Point the Pusher client SDK at your auth endpoint so it can obtain tokens before subscribing to private- channels. The channelAuthorization option specifies the URL and transport method.
Once configured, subscribing to a private channel looks identical to subscribing to a public one — the SDK handles the auth handshake transparently.
// lib/pusher-client.ts (updated)
import PusherClient from 'pusher-js';
export const pusherClient = new PusherClient(
process.env.NEXT_PUBLIC_PUSHER_KEY!,
{
cluster: process.env.NEXT_PUBLIC_PUSHER_CLUSTER!,
channelAuthorization: {
endpoint: '/api/pusher/auth',
transport: 'ajax',
},
}
);
// Usage in a component — identical for public or private channels:
// usePusherChannel('private-room-42', 'new-message', handler);Triggering Events from a Route Handler
Server Actions are ideal for form submissions, but sometimes you need to trigger events from an external webhook — for example, a payment provider notifying your app that a transaction completed. In that case, use a Route Handler instead of a Server Action.
The pattern is the same: validate the incoming request, then call pusherServer.trigger() to fan out the event to connected clients.
// app/api/webhooks/payment/route.ts
import { NextRequest, NextResponse } from 'next/server';
import { pusherServer } from '@/lib/pusher-server';
interface PaymentPayload {
orderId: string;
status: 'paid' | 'failed';
userId: string;
}
export async function POST(req: NextRequest) {
// Validate webhook signature (provider-specific)
const signature = req.headers.get('x-webhook-signature');
if (!isValidSignature(signature, await req.text())) {
return NextResponse.json({ error: 'Invalid signature' }, { status: 400 });
}
const payload: PaymentPayload = await req.json();
// Notify the specific user on their private channel
await pusherServer.trigger(
`private-user-${payload.userId}`,
'payment-update',
{ orderId: payload.orderId, status: payload.status }
);
return NextResponse.json({ received: true });
}
function isValidSignature(sig: string | null, _body: string): boolean {
// Replace with real HMAC check for your provider
return sig !== null;
}Presence Channels: Knowing Who Is Online
Pusher presence channels (prefixed presence-) extend private channels with a member roster. Each authenticated subscriber is added to a shared list that all members can observe in real time.
This enables features like "3 people are viewing this document" or live user cursors. Key events fired by Pusher on the client:
pusher:subscription_succeeded— initial member list.pusher:member_added— a new user joined.pusher:member_removed— a user disconnected.
The auth endpoint is reused; the user data you pass in user_info becomes the member object visible to all subscribers — so only include non-sensitive fields like display names or avatars.
Knowledge Check: Triggering Events from Serverless
A Next.js 15 application is deployed to Vercel (serverless). A Server Action must notify all connected clients whenever a user submits a form. Which approach is correct?
Lesson Recap: WebSockets in a Serverless World
Here is what you learned in this lesson:
- Serverless and long-lived sockets are incompatible — functions terminate too quickly to hold WebSocket connections.
- Managed providers (Pusher, Ably, PartyKit) maintain persistent connections on your behalf. Your serverless code communicates with them via standard HTTPS calls.
- Server singleton — export a single
Pusherinstance from aserver-onlymodule to prevent accidental client-side inclusion of your secret key. - Server Actions and Route Handlers both call
pusherServer.trigger(channel, event, data)to publish events after business logic completes. - Private channels require a
/api/pusher/authRoute Handler that validates the user session before issuing a subscription token. - Presence channels add a live member roster on top of private channel security, enabling online indicators and collaborative features.
- Client hook — wrap
pusher-jsin ausePusherChannelhook that subscribes on mount and cleans up on unmount.
This pattern gives you reliable, scalable real-time features without managing any WebSocket infrastructure yourself.
Preguntas frecuentes
¿La lección «Integración de servicios WebSocket en un entorno serverless» es gratis?
Sí — el texto completo de «Integración de servicios WebSocket en un entorno serverless» es gratis para leer aquí en la web. Para practicarla de forma interactiva (editor de código integrado y tutor de IA 24/7) y desbloquear el resto del curso de Next.js 15 Fullstack (App Router + Server Actions), actualiza a CoddyKit PRO. El curso de Next.js 15 Fullstack (App Router + Server Actions) incluye 4 lecciones en total.
¿Qué aprenderé en «Integración de servicios WebSocket en un entorno serverless»?
Conéctese a proveedores de WebSocket administrados, ya que las funciones serverless no pueden mantener sockets de larga duración. Practicas Next.js 15 Fullstack (App Router + Server Actions) con código real que ejecutas directamente en el navegador, y un tutor de IA 24/7 responde tus preguntas mientras trabajas en la lección.
¿Necesito experiencia previa para empezar Next.js 15 Fullstack (App Router + Server Actions)?
No se requiere experiencia previa. Next.js 15 Fullstack (App Router + Server Actions) en CoddyKit está estructurado para principiantes hasta estudiantes avanzados, así que puedes empezar aquí o desde el inicio y avanzar a tu ritmo. Esta es la lección 2 de 4.
¿Cuánto tiempo toma la lección «Integración de servicios WebSocket en un entorno serverless»?
La mayoría de las lecciones de CoddyKit toman alrededor de 5–10 minutos. Cada una es compacta e interactiva, así que avanzas constantemente y retomas exactamente por donde dejaste en la web y la app.
¿Puedo escribir y ejecutar código en esta lección de Next.js 15 Fullstack (App Router + Server Actions)?
Sí. Cada lección de Next.js 15 Fullstack (App Router + Server Actions) incluye un editor de código integrado, así que escribes y ejecutas código real directamente en tu navegador y obtienes retroalimentación instantánea de IA — sin configuración local necesaria.
Todas las lecciones de este curso
- Eventos enviados por el servidor desde handlers de rutas
- Integración de servicios WebSocket en un entorno serverless
- Streaming de respuestas de IA token a token
- Presencia, cursores y estado de colaboración en tiempo real