Naar de inhoud
maarten.
Alle stories

SSE of WebSockets voor realtime webapplicaties

Maarten Soetens 12 min lezen

Server-Sent Events en WebSockets kunnen allebei updates vanuit een server direct in een browser tonen, maar hun communicatie en beheer verschillen wezenlijk. Lees hoe beide technieken werken, waar verbindingen in de praktijk vastlopen en welke keuze past bij jouw toepassing.

Wat realtime communicatie in een webapplicatie betekent

Een webpagina die gegevens ophaalt wanneer iemand op een knop klikt, werkt niet vanzelf realtime. De browser moet dan zelf opnieuw een verzoek sturen om te ontdekken of er iets is veranderd. Met polling gebeurt dat op vaste intervallen: bijvoorbeeld iedere paar seconden. Dat is eenvoudig te bouwen, maar veroorzaakt verzoeken zonder nieuwe gegevens en introduceert vertraging tussen een gebeurtenis op de server en de weergave in de browser.

Bij live communicatie blijft een verbinding beschikbaar, zodat de server een update kan doorgeven zodra die ontstaat. Dat maakt een verschil voor dashboards, notificaties, statusoverzichten en samenwerkingstools. Realtime betekent daarbij niet automatisch dat ieder bericht onmiddellijk aankomt. Netwerkvertraging, browsergedrag, tussenliggende proxies en serverbelasting spelen mee. De relevante vraag is daarom meestal niet of een systeem realtime is, maar welke vertraging en uitval het gebruik nog aankan.

Ook de richting van het verkeer telt. Een koersoverzicht ontvangt vooral updates; een gedeelde editor verstuurt voortdurend wijzigingen én ontvangt die van anderen. Die patronen vragen niet noodzakelijk dezelfde techniek. Door eerst te bepalen wie berichten verstuurt, hoe vaak dat gebeurt en wat een gemist bericht betekent, voorkom je dat een protocol wordt gekozen op basis van alleen de term realtime.

Hoe Server-Sent Events updates naar de browser sturen

Server-Sent Events (SSE) gebruiken een langdurige HTTP-respons om tekstuele gebeurtenissen van server naar browser te sturen. De browser opent een verzoek met de JavaScript-interface EventSource; de server antwoordt met het MIME-type text/event-stream en houdt de respons open. Nieuwe gebeurtenissen verschijnen als regels tekst in een vast formaat. De browser kan daarop reageren met een standaardhandler of met een handler voor een benoemd event.

De verbinding is bewust eenrichtingsverkeer: de server stuurt data naar de client. Voor acties zoals een filter wijzigen of een bericht plaatsen blijft een gewone HTTP-aanroep geschikt. Die scheiding is vaak praktisch: updates lopen via SSE, terwijl mutaties als normale API-verzoeken verlopen. Het protocol gebruikt bestaande HTTP-infrastructuur en is daardoor in veel omgevingen eenvoudiger te inspecteren dan een eigen tweerichtingsverbinding.

SSE heeft ook concrete beperkingen. De inhoud is tekst, dus binaire data moet worden gecodeerd of via een ander endpoint worden opgehaald. De browser probeert een verbroken verbinding doorgaans opnieuw op te bouwen. Met event-ID's en een passende serverimplementatie kan de client aangeven waar hij was gebleven. Dat werkt alleen als de server gebeurtenissen kan terugvinden; anders krijgt de client na reconnect hooguit nieuwe updates en kan een tussenliggende wijziging ontbreken.

Hoe WebSockets een open tweerichtingsverbinding maken

WebSockets beginnen met een HTTP-handshake en schakelen daarna over naar een eigen protocol over dezelfde TCP-verbinding. Zodra die upgrade is gelukt, kunnen browser en server allebei berichten sturen zonder voor ieder bericht een nieuw HTTP-verzoek te openen. De API in de browser biedt handlers voor het openen, ontvangen, sluiten en afhandelen van fouten. Berichten kunnen tekst of binaire data bevatten, wat WebSockets geschikt maakt voor toepassingen met intensieve interactie of verschillende datatypen.

De tweerichtingsverbinding is nuttig wanneer de client voortdurend kleine berichten verstuurt, zoals cursorposities, spelacties of wijzigingen in een gedeeld document. De server kan daarop reageren en tegelijk berichten van andere gebruikers doorgeven. Een WebSocket-verbinding vervangt echter niet automatisch je HTTP-API. Inloggen, gegevens laden, bestanden versturen en beheeracties blijven vaak gewone HTTP-verzoeken. Een duidelijke taakverdeling voorkomt dat alle applicatielogica in één langdurige verbinding belandt.

De extra vrijheid vraagt om expliciet beheer. De applicatie moet bepalen hoe berichten worden getypeerd, gevalideerd en gerouteerd, en wat er gebeurt wanneer een socket sluit. Ook is een open socket niet hetzelfde als een werkende applicatiesessie: een netwerk kan stil uitvallen zonder direct een nette sluitmelding te leveren. Heartbeats, time-outs en herverbindingen zijn daarom onderdeel van het ontwerp, niet slechts details van de browser-API.

Eenrichtingsverkeer of tweerichtingsverkeer: het verschil in gebruik

Het belangrijkste functionele verschil tussen SSE en WebSockets is de richting van de communicatie. Bij SSE publiceert de server gebeurtenissen en ontvangt de browser die. Wanneer de gebruiker iets terugstuurt, doet de client dat via een afzonderlijk HTTP-verzoek. Bij WebSockets kunnen beide kanten berichten versturen binnen dezelfde verbinding. Dat maakt WebSockets niet automatisch sneller of beter; het maakt vooral een ander interactiepatroon mogelijk.

Voor een monitoringdashboard dat iedere paar seconden nieuwe meetwaarden toont, is inkomend verkeer vaak dominant. SSE past daar goed bij, terwijl filters en selectiecriteria via reguliere API-aanroepen kunnen lopen. Voor een multiplayeromgeving waarin spelers voortdurend acties uitwisselen, kan het tweerichtingsverkeer van WebSockets juist de kern van het systeem vormen. Een chatapplicatie kan met beide technieken worden gebouwd: SSE voor inkomende berichten en HTTP voor verzenden, of WebSockets voor beide richtingen.

Een bruikbare keuze volgt uit het berichtpatroon, niet uit de naam van de functie. Breng in kaart hoeveel berichten een browser verstuurt, hoe vaak de server publiceert en hoe snel beide kanten op elkaar moeten reageren. Kijk ook naar de gevolgen van een bericht dat verloren gaat. Een statusupdate kan vaak worden vervangen door de nieuwste waarde; een betaal- of bewerkingsactie vraagt om expliciete bevestiging en betrouwbare verwerking. Dat onderscheid beïnvloedt zowel het protocol als de applicatielaag.

Verbindingen beheren: reconnects, time-outs en sessies

Een langdurige verbinding vraagt om een andere levenscyclus dan een kort API-verzoek. Mobiele apparaten wisselen tussen wifi en mobiel netwerk, laptops gaan in slaapstand en browsers beperken activiteiten op achtergrondtabbladen. De verbinding kan daardoor wegvallen zonder dat de gebruiker de pagina sluit. Zowel bij SSE als bij WebSockets moet de client rekening houden met opnieuw verbinden, terwijl de server oude verbindingen tijdig moet opruimen.

Automatisch reconnecten herstelt alleen het transport. Het vertelt niet vanzelf of de client alle gemiste gegevens heeft ontvangen. SSE kan met event-ID's een hervattingspunt doorgeven, maar de server moet gebeurtenissen bewaren en op basis daarvan opnieuw aanbieden. Bij WebSockets bouw je doorgaans zelf een berichtnummer, cursor of synchronisatieverzoek in. Een client kan na reconnect eerst de actuele toestand ophalen en daarna weer live updates ontvangen. Dat is vaak betrouwbaarder dan proberen ieder tussenliggend bericht onbeperkt te bewaren.

Time-outs moeten passen bij de infrastructuur. Als een proxy een verbinding na een periode zonder verkeer sluit, kan de applicatie periodieke commentaarregels bij SSE of ping- en pongberichten bij WebSockets nodig hebben. Te frequente heartbeats verbruiken capaciteit; te trage heartbeats maken uitval laat zichtbaar. Ontwerp bovendien op dubbele verwerking: een client kan een bericht opnieuw ontvangen als de ontvangstbevestiging verloren ging. Idempotente verwerking voorkomt dat zo'n herhaling dezelfde wijziging meerdere keren toepast.

SSE en WebSockets achter proxies en load balancers

Een verbinding die lang open blijft, stelt eisen aan alle onderdelen tussen browser en applicatieserver. Reverse proxies en load balancers kunnen buffering toepassen, idle time-outs instellen of het aantal gelijktijdige verbindingen begrenzen. Bij SSE kan buffering ertoe leiden dat gebeurtenissen pas in een grotere batch aankomen, hoewel de applicatie ze al heeft verstuurd. De serverconfiguratie moet daarom streaming ondersteunen en de respons niet onbedoeld verzamelen voordat die naar de browser gaat.

WebSockets vereisen dat de proxy de protocolupgrade correct doorlaat en de verbinding niet behandelt als een gewone korte HTTP-aanroep. Niet iedere netwerkroute ondersteunt dit op dezelfde manier. Een verbinding kan ook op een andere server terechtkomen na reconnect. Als de server lokale sessiestatus gebruikt, kan de gebruiker dan zijn abonnement of context verliezen. Sticky sessions kunnen dat tijdelijk maskeren, maar maken herverdeling en herstel na serveruitval ingewikkelder.

Bij meerdere applicatie-instanties is een gedeelde berichtenlaag vaak nodig. Een gebeurtenis die op server A ontstaat, moet ook clients bereiken die verbonden zijn met server B. Een message broker of eventbus kan gebeurtenissen verspreiden, maar brengt eigen vragen mee over volgorde, duplicaten en achterstand. Meet daarom niet alleen het aantal open verbindingen. Volg ook de tijd tussen publicatie en aflevering, de hoeveelheid gebufferde data, reconnectfrequentie en het aantal verbindingen dat onverwacht wordt gesloten.

Authenticatie en beveiliging van live verbindingen

Een live verbinding blijft actief nadat de initiële aanvraag is geaccepteerd. De server moet daarom weten bij welke gebruiker en welke rechten de verbinding hoort, en hoe die rechten tijdens de sessie veranderen. SSE gebruikt een browserinterface die niet dezelfde vrijheid biedt als een willekeurige HTTP-client om requestheaders in te stellen. Cookiegebaseerde sessies zijn vaak bruikbaar, maar vereisen aandacht voor cross-originbeleid en de herkomst van de aanvraag. Een bearer-token in een URL is doorgaans onwenselijk, omdat URL's in logs, browsergeschiedenis en monitoring terecht kunnen komen.

Bij WebSockets zijn authenticatie en autorisatie eveneens afzonderlijke stappen. De handshake kan informatie uit cookies gebruiken, maar de server moet de Origin-header controleren en niet aannemen dat iedere browseraanvraag betrouwbaar is. Na het openen van de verbinding moet ieder bericht worden gevalideerd. Een gebruiker die een kanaal mag openen, heeft niet automatisch toegang tot alle onderwerpen of objecten die via dat kanaal bereikbaar zijn.

Rechten kunnen bovendien veranderen terwijl de verbinding openstaat, bijvoorbeeld wanneer een sessie wordt ingetrokken of een gebruiker uit een project wordt verwijderd. Bepaal of de server zulke verbindingen actief beëindigt, of bij ieder bericht opnieuw autoriseert. Stel limieten in voor berichtgrootte, frequentie en het aantal verbindingen per account. Zonder die grenzen kunnen foutieve clients of misbruik onnodig veel geheugen en servercapaciteit vasthouden.

Backpressure en berichtenstromen onder belasting

Een server kan sneller gebeurtenissen produceren dan een browser ze verwerkt. Een tabblad op de achtergrond kan bijvoorbeeld vertraagd worden, terwijl een datastroom onverminderd doorgaat. Als berichten zich opstapelen in buffers, groeit het geheugengebruik per verbinding. Bij duizenden open verbindingen kan een bescheiden achterstand daardoor een serieus capaciteitsprobleem worden. Zowel SSE- als WebSocket-implementaties moeten bepalen wat er gebeurt wanneer een client niet kan bijhouden.

Niet ieder bericht verdient dezelfde behandeling. Bij meetwaarden of voortgangsstatus kan het voldoende zijn om alleen de nieuwste waarde te bewaren; oudere updates voegen dan weinig toe. Bij een chatbericht of financiële gebeurtenis kan overslaan onacceptabel zijn. In dat geval zijn opslag, volgorde en hervatten belangrijker dan het beperken tot één actuele toestand. Een ontwerp kan daarom verschillende kanalen gebruiken: een compacte stroom voor vluchtige status en een aparte, duurzame route voor gebeurtenissen die later moeten kunnen worden opgehaald.

Backpressure moet zichtbaar zijn in de applicatie en monitoring. Meet wachtrijlengte, hoeveelheid data per verbinding en het aantal clients dat structureel achterloopt. Stel grenzen in en kies een expliciete reactie, zoals berichten samenvoegen, clients verbreken of een synchronisatie vanaf de actuele toestand afdwingen. Zonder beleid bepaalt de buffer uiteindelijk wat verloren gaat, vaak pas wanneer het systeem al onder druk staat. Test dit met trage clients en piekbelasting, niet alleen met een browser op een snelle ontwikkelmachine.

Veelgestelde vragen

Hoe stuur ik databasewijzigingen door naar een SSE- of WebSocket-client?

Koppel databasewijzigingen aan een centrale gebeurtenissenstroom en laat live verbindingen daarop abonneren. Laat niet iedere open verbinding voortdurend zelf de database controleren; dat kan onnodige belasting veroorzaken en levert niet vanzelf consistente updates op.

Een gebruikelijke aanpak is dat de applicatie na een geslaagde wijziging een gebeurtenis publiceert, eventueel via een message broker. Als een gebeurtenis niet verloren mag gaan wanneer de databasewijziging wel slaagt maar het publiceren mislukt, kan een transactional outbox helpen: de wijziging en een te publiceren gebeurtenis worden samen opgeslagen. Een achtergrondproces verstuurt die gebeurtenis vervolgens naar de aangesloten clients.

Hoe gebruik ik SSE of WebSockets in een React-app zonder dubbele verbindingen?

Beheer de verbinding op een gedeeld niveau en sluit haar op wanneer de bijbehorende component niet meer nodig is. Als iedere component afzonderlijk een EventSource of WebSocket opent, kunnen meerdere verbindingen ontstaan voor dezelfde gebruiker en dezelfde gegevens.

In React kan een effect de verbinding openen en in de opruimfunctie sluiten. Voor data die meerdere componenten nodig hebben, is een context of een gedeelde clientmodule vaak geschikter dan een verbinding per component. Houd ook rekening met opnieuw renderen: veranderende objecten of functies als effectafhankelijkheid kunnen het effect opnieuw starten. Controleer in ontwikkeling of er na navigeren en terugkomen geen oude verbindingen blijven bestaan.

Hoeveel SSE-verbindingen kan een browser tegelijk openhouden?

Er is geen vast maximum dat voor iedere browser en netwerkomstandigheid geldt. Bij HTTP/1.1 geldt voor browsers vaak een relatief lage limiet aan gelijktijdige verbindingen per domein; zes is een bekende grens, maar het precieze gedrag verschilt. Met HTTP/2 kunnen meerdere streams dezelfde verbinding delen, waarbij de server en browser het aantal gelijktijdige streams bepalen.

Ontwerp daarom niet op de aanname dat iedere widget een eigen SSE-verbinding kan openen. Deel een verbinding waar dat logisch is, laat componenten zich op de benodigde gebeurtenissen abonneren en sluit ongebruikte verbindingen. Test daarnaast met meerdere tabbladen en de browsers die je gebruikers daadwerkelijk gebruiken.

Hoe test ik of live updates betrouwbaar blijven werken na een netwerkstoring?

Test niet alleen of een bericht aankomt bij een stabiele verbinding, maar ook wat er gebeurt wanneer de verbinding tijdelijk wegvalt en terugkomt. Onderbreek in een testomgeving het netwerk, sluit de verbinding vanaf de server en simuleer een client die langere tijd niet reageert. Controleer vervolgens of de client opnieuw verbindt en de verwachte gegevens herstelt.

Test ook berichten die dubbel aankomen, vertraagd worden of in een andere volgorde arriveren. Voor geautomatiseerde tests kun je de serverreactie of transportlaag vervangen door een gecontroleerde testserver. Controleer ten slotte dat een afgesloten component geen berichten meer verwerkt en dat reconnectpogingen niet onbeperkt blijven doorgaan.

Hoe voorkom ik dat live updates in mijn webapplicatie verouderde gegevens tonen?

Geef updates een versie- of volgnummer en laat de client oudere versies negeren. Een tijdstempel kan helpen bij het vergelijken, maar een oplopend versienummer per object of stroom maakt de volgorde vaak duidelijker, vooral wanneer klokken op verschillende servers niet exact gelijk lopen.

Na een reconnect kan de client controleren of er een gat in de reeks zit. Als ontbrekende gebeurtenissen niet opnieuw beschikbaar zijn, haalt de client een actuele momentopname op en gebruikt die als nieuwe basis. Maak de verwerking van gebeurtenissen bovendien idempotent, zodat het opnieuw ontvangen van hetzelfde bericht geen dubbele wijziging veroorzaakt. Zo blijft de getoonde toestand herstelbaar, ook als niet iedere update afzonderlijk aankomt.

Portret van Maarten

Maarten

Freelance developer in Nijmegen

Even kennismaken?

Vertel kort wat er speelt. Dan hoor je wat er kan, wat ik anders zou doen en waar AI bij jou wél en niet iets toevoegt. Vrijblijvend.

[email protected]
Het kantoor in Nijmegen
© 2026 maarten.online Sitemap Privacy Algemene voorwaarden
Het kantoor in Nijmegen

Maarten.

Freelance developer in Nijmegen. Liever direct contact? Dat kan ook.

Kennismaken

Laat je gegevens achter, dan kijken we of het klikt. Vrijblijvend en zonder verkooppraat.

Maarten

Stuur een bericht via WhatsApp

Hoi! Waar kan ik je mee helpen?

nu