0Pricing
Next.js 15 Fullstack (App Router + Server Actions) · Leçon

Intégrer des services WebSocket dans un environnement sans serveur

Connectez-vous à des fournisseurs WebSocket gérés, car les fonctions sans serveur ne peuvent pas conserver des sockets ouverts durablement.

Intégrer des services WebSocket dans un environnement sans serveur est une leçon Next.js 15 Fullstack (App Router + Server Actions) gratuite sur CoddyKit. Ceci est la leçon 2 sur 4. Tu peux lire la leçon complète ci-dessous gratuitement — puis la pratiquer en direct dans le navigateur avec un éditeur de code intégré et un tuteur IA 24/7. Elle fait partie du parcours d'apprentissage Next.js 15 Fullstack (App Router + Server Actions), et ta progression se synchronise sur le web et l'application CoddyKit. Le cours Next.js 15 Fullstack (App Router + Server Actions) comprend 4 leçons au total.

Certaines parties de cette leçon n'ont pas encore été traduites et s'affichent en anglais.

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 useRef or 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 Pusher instance from a server-only module 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/auth Route 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-js in a usePusherChannel hook that subscribes on mount and cleans up on unmount.

This pattern gives you reliable, scalable real-time features without managing any WebSocket infrastructure yourself.

Questions Fréquemment Posées

La leçon « Intégrer des services WebSocket dans un environnement sans serveur » est-elle gratuite ?

Oui — le texte complet de « Intégrer des services WebSocket dans un environnement sans serveur » est gratuit à lire ici sur le web. Pour la pratiquer de manière interactive (un éditeur de code intégré et un tuteur IA 24/7) et déverrouiller le reste du cours Next.js 15 Fullstack (App Router + Server Actions), passe à CoddyKit PRO. Le cours Next.js 15 Fullstack (App Router + Server Actions) comprend 4 leçons au total.

Qu'est-ce que j'apprendrai dans « Intégrer des services WebSocket dans un environnement sans serveur » ?

Connectez-vous à des fournisseurs WebSocket gérés, car les fonctions sans serveur ne peuvent pas conserver des sockets ouverts durablement. Tu pratiques Next.js 15 Fullstack (App Router + Server Actions) avec du code pratique que tu exécutes directement dans le navigateur, et un tuteur IA 24/7 répond à tes questions au fur et à mesure que tu avances dans la leçon.

Dois-je avoir de l'expérience pour commencer Next.js 15 Fullstack (App Router + Server Actions) ?

Aucune expérience préalable n'est requise. Next.js 15 Fullstack (App Router + Server Actions) sur CoddyKit est structuré pour les débutants jusqu'aux apprenants avancés, donc tu peux commencer ici ou depuis le début et avancer à ton rythme. Ceci est la leçon 2 sur 4.

Combien de temps prend la leçon « Intégrer des services WebSocket dans un environnement sans serveur » ?

La plupart des leçons CoddyKit prennent environ 5–10 minutes. Chacune est courte et interactive, tu progresses régulièrement et tu repiques exactement où tu t'es arrêté sur le web et l'app.

Peux-tu écrire et exécuter du code dans cette leçon Next.js 15 Fullstack (App Router + Server Actions) ?

Oui. Chaque leçon Next.js 15 Fullstack (App Router + Server Actions) inclut un éditeur de code intégré, tu écris et exécutes du vrai code directement dans ton navigateur et tu reçois des retours IA instantanés — aucune configuration locale requise.

Toutes les leçons de ce cours

  1. Événements envoyés par le serveur depuis les gestionnaires de routes
  2. Intégrer des services WebSocket dans un environnement sans serveur
  3. Diffuser les réponses de l’IA jeton par jeton
  4. Présence, curseurs et état de collaboration en direct
← Retour à Next.js 15 Fullstack (App Router + Server Actions)