Problema, kurią visi ignoruoja
Dauguma kūrėjų, kurie stato naujienų SPA programas, daro tą pačią klaidą – jie tiesiog meta setInterval į kodą ir vadina tai „realaus laiko atnaujinimu”. Techniškai tai veikia. Praktiškai – tai tingus sprendimas, kuris anksčiau ar vėliau atsirūgs.
Realaus laiko turinys naujienų kontekste reiškia, kad vartotojas mato naują informaciją iš karto, kai ji atsiranda – be puslapio perkrovimo, be rankinio refresh mygtuko spaudymo ir, svarbiausia, be to, kad programa nuolat bombarduotų serverį bereikalingomis užklausomis.
WebSocket vs SSE – pasirink teisingai
Čia prasideda tikras galvos skausmas. Daugelis iš karto šoka prie WebSocket, nes tai skamba įspūdingai. Bet naujienų programai, kur duomenys teka tik viena kryptimi (serveris → klientas), Server-Sent Events (SSE) dažnai yra protingesnis pasirinkimas.
SSE yra paprastesnis, automatiškai persijungia nutrūkus ryšiui ir puikiai veikia per HTTP/2. WebSocket reikia, kai klientas taip pat siunčia duomenis atgal – komentarai realiuoju laiku, pokalbiai ir panašiai. Naujienų srautui to nereikia.
Paprastas SSE ryšys atrodo taip:
const eventSource = new EventSource('/api/news-stream');
eventSource.onmessage = (event) => {
const newsItem = JSON.parse(event.data);
updateNewsStore(newsItem);
};
eventSource.onerror = (error) => {
console.error('Ryšys nutrūko:', error);
// SSE pats bandys persijungti
};
Būsenos valdymas – čia klysta beveik visi
Gauti duomenis realiuoju laiku yra vienas dalykas. Juos teisingai integruoti į esamą programos būseną – visai kitas. Jei naudoji React su Redux arba Zustand, turi nuspręsti: ar naujos naujienos atsiranda sąrašo viršuje, ar apačioje? Ar senos naujienos automatiškai išstumiamos? Kiek įrašų laikyti atmintyje?
Tipinė klaida – leisti sąrašui augti be ribų. Po kelių valandų naršyklė pradeda strigti, o vartotojas nė nesupranta kodėl. Nustatyk maksimalų įrašų skaičių:
const MAX_NEWS_ITEMS = 100;
function updateNewsStore(newItem) {
setNews(prev => {
const updated = [newItem, ...prev];
return updated.slice(0, MAX_NEWS_ITEMS);
});
}
Vartotojo patirtis – ne tik technika
Čia daugelis kūrėjų visiškai pameta galvą. Jie padaro, kad naujienos atsinaujina realiuoju laiku, bet pamiršta vieną esminį dalyką: jei vartotojas skaito straipsnį ir turinys staiga persijungia ar perjumpa – tai ne funkcija, tai erzinanti klaida.
Sprendimas – „silent update” principas. Naujas turinys kraunamas fone, o vartotojui rodomas diskretus pranešimas: „Yra 5 naujos naujienos. Spustelėk, kad pamatytum.” Tik tada, kai vartotojas sutinka, turinys atsinaujina. Taip daro „Twitter” (dabar X), taip daro didžiosios naujienų platformos.
Ryšio praradimas ir atsigavimas
Niekas nekalbas apie tai, bet tai kritiškai svarbu. Vartotojai naudoja programas su nestabiliu internetu. SSE turi automatinį reconnect mechanizmą, bet jis nėra tobulas – jei ryšys nutrūksta ilgesniam laikui, programa gali praleisti naujienų paketą.
Serveryje kiekvienas įvykis turi turėti unikalų ID. Klientas, prisijungdamas iš naujo, siunčia paskutinį gautą ID, o serveris atsiunčia viską, ko trūksta:
// Serverio pusė (Node.js/Express)
res.write(`id: ${newsItem.id}\n`);
res.write(`data: ${JSON.stringify(newsItem)}\n\n`);
Klientas automatiškai siunčia Last-Event-ID antraštę – tereikia, kad serveris tai apdorotų.
Kai „realus laikas” tampa iliuzija
Svarbu būti sąžiningam: tikras realus laikas be kompromisų yra brangus. Kiekvienas atviras SSE ryšys – tai atviras serverio resursas. Su tūkstančiu vienalaikių vartotojų tai dar valdoma. Su šimtu tūkstančių – jau rimta infrastruktūros problema.
Todėl prieš diegiant šią architektūrą verta paklausti: ar jūsų naujienų programa tikrai reikalauja milisekundinės tikslios atnaujinimų? Ar pakaktų atnaujinimo kas 30 sekundžių? Kartais paprastas polling su protingu intervalu yra geresnis sprendimas nei sudėtinga SSE infrastruktūra, kurią reikia palaikyti.
Techninis sprendimas visada turi tarnauti tikslui, o ne atvirkščiai. SSE ir WebSocket yra galingi įrankiai – bet įrankiai, kuriuos reikia naudoti tada, kai jie tikrai reikalingi, o ne todėl, kad tai skamba moderniai.