Temps Réel à Grande Échelle : WebSockets avec Laravel Reverb et Nuxt 4 Echo

Par Devinsto
Posté le 2 novembre 2026
Mis à jour le 3 novembre 2026
15 min de lecture
10 vues

ffrir des fonctionnalités interactives instantanées (messagerie en direct, notifications temps réel, indicateurs de présence, mise à jour instantanée d'un tableau de bord de trading ou d'e-learning) est devenu un standard incontournable de l'expérience utilisateur moderne.

Historiquement, la mise en œuvre de WebSockets sous PHP imposait de recourir à des services tiers onéreux (Pusher) ou à des passerelles complexes en Node.js (Socket.io).

Avec l'arrivée de Laravel Reverb, PHP dispose désormais d'un serveur WebSocket natif, ultra-rapide, capable de maintenir des dizaines de milliers de connexions simultanées avec une consommation de ressources minimale.

Découvrons comment interconnecter Laravel Reverb et Nuxt 4 derrière un proxy inverse Traefik.


1. Comment Fonctionne l'Architecture Reverb

Reverb s'exécute comme un démon autonome asynchrone propulsé par une boucle d'événements interne. Il communique avec vos travailleurs d'arrière-plan via Redis pour diffuser (broadcast) instantanément les événements vers les clients connectés.

Le Cycle d'un Événement Temps Réel

  1. Une action utilisateur survient sur l'API (ex: nouveau commentaire soumis).

  2. L'API déclenche un événement Laravel implémentant le contrat ShouldBroadcastNow ou ShouldBroadcast.

  3. Le message est publié dans un canal Redis Pub/Sub.

  4. Reverb capte le message et le pousse immédiatement dans le socket de tous les navigateurs connectés et autorisés.

  5. Le composant Vue.js met à jour son état réactif en quelques millisecondes sans rechargement de page.


2. Configuration du Routage WebSocket sous Traefik

Pour que les flux de données persistants transitent sans coupure à travers Traefik en HTTPS (wss://), Traefik doit être configuré pour reconnaître la négociation de protocole (HTTP Upgrade).

Configuration Docker Compose pour Reverb

services:
  reverb:
    image: mon-api:latest
    restart: unless-stopped
    command: ["php", "artisan", "reverb:start", "--host=0.0.0.0", "--port=8080"]
    environment:
      REVERB_APP_ID: mon-app-reverb
      REVERB_APP_KEY: cle-reverb-securisee-987452
      REVERB_APP_SECRET: secret-reverb-ultra-securise
      REVERB_HOST: 0.0.0.0
      REVERB_PORT: 8080
      REVERB_SCHEME: https
    networks:
      - dokploy-network
      - inte
al-net
    labels:
      - "traefik.enable=true"
      - "traefik.docker.network=dokploy-network"
      - "traefik.http.routers.reverb.rule=Host(`api.domaine.com`) && PathPrefix(`/app`)"
      - "traefik.http.routers.reverb.entrypoints=websecure"
      - "traefik.http.routers.reverb.tls=true"
      - "traefik.http.routers.reverb.tls.certresolver=letsencrypt"
      - "traefik.http.services.reverb.loadbalancer.server.port=8080"

3. Déclaration d'un Événement Diffusé (Broadcast Event)

Dans Laravel, la création d'un événement diffusable nécessite l'implémentation de ShouldBroadcastNow (pour une diffusion immédiate) ou ShouldBroadcast (pour un passage par la file d'attente) :

namespace App\Events;

use App\Models\Comment;
use Illuminate\Broadcasting\Channel;
use Illuminate\Broadcasting\InteractsWithSockets;
use Illuminate\Contracts\Broadcasting\ShouldBroadcastNow;
use Illuminate\Foundation\Events\Dispatchable;

class NewCommentEvent implements ShouldBroadcastNow
{
    use Dispatchable, InteractsWithSockets;

    public function __construct(public Comment $comment) {}

    /**
     * Canal public sur lequel l'événement est diffusé.
     */
    public function broadcastOn(): array
    {
        return [
            new Channel('posts.' . $this->comment->post_id),
        ];
    }

    /**
     * Nom d'événement personnalisé reçu par le frontend.
     */
    public function broadcastAs(): string
    {
        return 'comment.created';
    }

    /**
     * Données précises injectées dans le payload WebSocket.
     */
    public function broadcastWith(): array
    {
        return [
            'id' => $this->comment->id,
            'content' => $this->comment->content,
            'author_name' => $this->comment->user->name,
            'created_at' => $this->comment->created_at->toISOString(),
        ];
    }
}

4. Intégration Client dans Nuxt 4 avec Laravel Echo

Côté frontend, nous exploitons la combinaison de laravel-echo et du driver pusher-js (pleinement compatible avec Reverb).

Plugin d'Initialisation Nuxt 4 (app/plugins/echo.client.ts)

import Echo from 'laravel-echo'
import Pusher from 'pusher-js'

declare global {
  interface Window {
    Pusher: typeof Pusher
    Echo: Echo<any>
  }
}

export default defineNuxtPlugin(() => {
  const config = useRuntimeConfig()

  if (import.meta.server) return

  window.Pusher = Pusher

  const echo = new Echo({
    broadcaster: 'reverb',
    key: config.public.reverbAppKey,
    wsHost: config.public.reverbHost,
    wsPort: Number(config.public.reverbPort) || 443,
    wssPort: Number(config.public.reverbPort) || 443,
    forceTLS: config.public.reverbScheme === 'https',
    enabledTransports: ['ws', 'wss'],
  })

  return {
    provide: {
      echo
    }
  }
})

Écoute Réactive dans un Composant Vue 3

<script setup lang="ts">
const props = defineProps<{ postId: number }>()
const comments = ref<any[]>([])
const { $echo } = useNuxtApp()

onMounted(() => {
  // Souscription au canal dynamique du post
  $echo.channel(`posts.${props.postId}`)
    .listen('.comment.created', (event: any) => {
      // Ajout instantané du commentaire en tête de liste
      comments.value.unshift(event)
    })
})

onUnmounted(() => {
  // Nettoyage impératif pour éviter les écoutes fantômes en mémoire
  $echo.leaveChannel(`posts.${props.postId}`)
})
</script>

<template>
  <div class="space-y-4">
    <div 
      v-for="c in comments" 
      :key="c.id" 
      class="p-4 rounded-xl border border-muted bg-card shadow-sm transition-all animate-in fade-in slide-in-from-top-2"
    >
      <div class="flex items-center justify-between text-xs text-muted-foreground mb-1">
        <span class="font-bold">{{ c.author_name }}</span>
        <span>{{ new Date(c.created_at).toLocaleTimeString() }}</span>
      </div>
      <p class="text-sm">{{ c.content }}</p>
    </div>
  </div>
</template>

5. Bonnes Pratiques de Production pour Reverb

  1. Ping / Pong Interval : Réglez l'intervalle de pulsation (Heartbeat) à 25 ou 30 secondes pour que les routeurs et pare-feu intermédiaires ne ferment pas prématurément les connexions inactives.

  2. Autorisation des Canaux Privés : Pour les canaux privés (PrivateChannel) et de présence (PresenceChannel), assurez-vous que la route /broadcasting/auth est déclarée dans vos SANCTUM_STATEFUL_DOMAINS pour valider les permissions utilisateur via le cookie de session sans régénérer de jeton.

  3. Nettoyer les écouteurs à la destruction du composant : Appelez toujours $echo.leaveChannel() dans onUnmounted() pour éviter les fuites de mémoire dans les applications Single-Page.

Ajouter un commentaire

Vous devez être connecté pour laisser un commentaire.

Soyez le premier à commenter cet article !