Přejít k navigační liště

Zdroják » Webový vývoj » WebSocket: Jak dostat webovou aplikaci do reálného času

WebSocket: Jak dostat webovou aplikaci do reálného času

Články ‐ ‐ Webový vývoj ‐

Chat, živé notifikace, společná úprava dokumentu nebo průběžně aktualizované kurzy. Uživatelé dnes čekají, že se webová aplikace změní ve chvíli, kdy se změní data, a ne až po stisknutí klávesy F5. Protokol WebSocket je nejrozšířenější způsob, jak mezi prohlížečem a serverem vytvořit trvalý obousměrný kanál. V článku se podíváme, jak funguje pod kapotou, jak ho použít v prohlížeči i na serveru a na co si dát pozor při nasazení do produkce.

Nálepky:

Proč nestačí obyčejné HTTP

HTTP stojí na modelu požadavek–odpověď: klient se zeptá, server odpoví. Server nemůže klientovi poslat data z vlastní iniciativy. Aplikace, které potřebují reagovat na události na serveru, proto dlouho používaly několik obezliček.

Nejjednodušší je polling, tedy pravidelné dotazování. Klient se v pevném intervalu, třeba každých pět sekund, ptá serveru, zda nemá nová data. Implementace je snadná, ale neefektivní. Většina odpovědí je prázdná a každý požadavek nese kompletní HTTP hlavičky, často včetně cookies o velikosti několika kilobajtů. Zpoždění je navíc v průměru rovno polovině intervalu. Kratší interval sice zpoždění sníží, ale úměrně zvýší zátěž serveru.

Long polling je chytřejší varianta. Server nechá požadavek otevřený, dokud nemá co poslat nebo dokud nevyprší časový limit. Klient po obdržení odpovědi hned odešle další požadavek. Zpoždění je výrazně menší, ale stále platíte režii za každý nový požadavek a server musí udržovat velké množství rozpracovaných požadavků. Navíc je potřeba pohlídat, aby se neztratila zpráva, která vznikne právě v okamžiku mezi dvěma požadavky.

Třetí možností jsou Server-Sent Events (SSE), které jsou součástí specifikace HTML. Server odpoví s typem obsahu text/event-stream a odpověď průběžně doplňuje o další události. Prohlížečové rozhraní EventSource samo obnovuje přerušené spojení a pomocí hlavičky Last-Event-ID umožňuje navázat tam, kde se skončilo. SSE je výborný nástroj, ale je jednosměrný: data tečou pouze ze serveru ke klientovi. Pokud potřebujete s nízkým zpožděním posílat data i opačným směrem, musíte použít samostatné HTTP požadavky.

WebSocket tyto kompromisy odstraňuje. Po jednorázovém navázání spojení mohou obě strany kdykoli posílat zprávy, a to s minimální režií.

Co je WebSocket

Protokol WebSocket popisuje RFC 6455 z roku 2011, programové rozhraní pro prohlížeče pak definuje standard WebSockets organizace WHATWG. Jde o plně duplexní komunikační kanál nad jedním spojením TCP. Plně duplexní znamená, že obě strany mohou vysílat současně a nezávisle na sobě.

Protokol má vlastní schémata URL: ws:// pro nešifrované spojení a wss:// pro spojení zabezpečené pomocí TLS. Komunikace začíná jako běžný HTTP požadavek, díky čemuž využívá standardní porty 80 a 443 a lépe prochází firewally i proxy servery. Po úspěšném navázání spojení se však HTTP opouští a dál se komunikuje binárními rámci (anglicky frames), jejichž hlavička má pouze 2 až 14 bajtů.

Stejně důležité je vědět, co WebSocket sám o sobě nedělá. Nezaručuje doručení zprávy v aplikačním smyslu: pokud spojení spadne, zprávy, které byly zrovna na cestě, jsou ztraceny. Nemá vestavěné obnovení spojení, neřeší rozesílání zpráv podle témat ani autentizaci uživatelů. To všechno je na vás, případně na knihovně, kterou nad WebSocketem použijete.

Handshake: jak se z HTTP stane WebSocket

Navázání spojení začíná požadavkem GET s hlavičkami, které žádají o přepnutí protokolu:

GET /chat HTTP/1.1
Host: example.com
Upgrade: websocket
Connection: Upgrade
Sec-WebSocket-Key: dGhlIHNhbXBsZSBub25jZQ==
Sec-WebSocket-Version: 13
Origin: https://example.com
Code language: HTTP (http)

Hlavička Sec-WebSocket-Key obsahuje náhodnou šestnáctibajtovou hodnotu zakódovanou do Base64. Server, který s přepnutím souhlasí, odpoví stavovým kódem 101:

HTTP/1.1 101 Switching Protocols
Upgrade: websocket
Connection: Upgrade
Sec-WebSocket-Accept: s3pPLMBiTxaQ9kYGzzhZRbK+xOo=
Code language: HTTP (http)

Hodnota Sec-WebSocket-Accept vznikne tak, že server ke klíči od klienta připojí pevně daný řetězec 258EAFA5-E914-47DA-95CA-C5AB0DC85B11, z výsledku spočítá hash SHA-1 a ten zakóduje do Base64. V Node.js to vypadá takto:

import { createHash } from 'node:crypto';

const GUID = '258EAFA5-E914-47DA-95CA-C5AB0DC85B11';

function acceptKey(key) {
  return createHash('sha1').update(key + GUID).digest('base64');
}

console.log(acceptKey('dGhlIHNhbXBsZSBub25jZQ=='));
// s3pPLMBiTxaQ9kYGzzhZRbK+xOo=
Code language: JavaScript (javascript)

Tento mechanismus není zabezpečením v kryptografickém smyslu. Slouží k tomu, aby klient poznal, že mu odpověděl server, který protokolu WebSocket skutečně rozumí, a ne například špatně nastavená proxy vracející odpověď z mezipaměti. Hlavičky začínající předponou Sec- navíc nemůže nastavit JavaScript na stránce, takže WebSocketový handshake nelze podvrhnout obyčejným voláním fetch().

V handshaku se může objevit také hlavička Sec-WebSocket-Protocol, pomocí které se klient a server dohodnou na aplikačním podprotokolu (například graphql-transport-ws), a hlavička Sec-WebSocket-Extensions pro rozšíření. Nejpoužívanějším rozšířením je permessage-deflate definované v RFC 7692, které komprimuje obsah zpráv. Komprese šetří přenosové pásmo, ale stojí procesorový čas a paměť na každé spojení, takže u serverů s desítkami tisíc spojení je dobré ji zapínat s rozmyslem.

Rámce, maskování a řídicí zprávy

Po handshaku se data přenášejí v rámcích. První dva bajty každého rámce obsahují bit FIN, který určuje, zda jde o poslední rámec zprávy, čtyřbitový operační kód (opcode) udávající typ rámce, bit MASK, který říká, zda je obsah maskován, a sedmibitovou délku obsahu. Má-li délka hodnotu 126, následují další dva bajty se skutečnou délkou, při hodnotě 127 je to osm bajtů. Jedna zpráva může být rozdělena do více rámců, což umožňuje posílat data, jejichž celkovou velikost odesílatel předem nezná.

Operační kód 0x1 označuje textový rámec, jehož obsah musí být platné UTF-8, kód 0x2 binární rámec a kód 0x0 pokračování rozdělené zprávy. Dále existují řídicí rámce: 0x8 pro uzavření spojení, 0x9 pro ping a 0xA pro pong. Obsah řídicího rámce smí mít nejvýše 125 bajtů, řídicí rámce se nesmějí dělit a mohou být vloženy mezi jednotlivé části jiné zprávy. Díky tomu lze odpovědět na ping, i když se zrovna přenáší velká rozdělená zpráva.

Zvláštností protokolu je maskování. Všechny rámce, které posílá klient serveru, musí být maskovány náhodným čtyřbajtovým klíčem, zatímco rámce od serveru ke klientovi maskovány být nesmějí. Maskování nemá nic společného se šifrováním, protože klíč se posílá přímo v rámci. Jeho účelem je zabránit útokům na mezilehlé proxy servery, takzvanému otrávení mezipaměti (cache poisoning). Útočník by jinak mohl pečlivě zvolenými bajty přimět naivní proxy, aby obsah WebSocketového rámce považovala za HTTP požadavek. Díky náhodnému maskování nemá útočník kontrolu nad tím, jaké bajty skutečně putují po síti.

Spojení se ukončuje uzavíracím handshakem. Strana, která chce komunikaci ukončit, pošle rámec close se stavovým kódem a volitelným textovým důvodem. Druhá strana odpoví také rámcem close a teprve poté se uzavře spojení TCP. Nejčastější stavové kódy jsou 1000 pro běžné ukončení, 1001, když strana odchází (zavírá se záložka nebo se restartuje server), 1008 při porušení pravidel, 1009, pokud je zpráva příliš velká, a 1011 při neočekávané chybě serveru. Kód 1006 se nikdy neposílá v rámci. Prohlížeč jej hlásí ve chvíli, kdy spojení skončilo bez řádného uzavíracího handshaku, typicky při výpadku sítě. Rozsah 4000 až 4999 je vyhrazen pro vlastní potřebu aplikací.

WebSocket v prohlížeči

Rozhraní v prohlížeči je záměrně jednoduché:

const socket = new WebSocket('wss://example.com/chat');

socket.addEventListener('open', () => {
  socket.send(JSON.stringify({ type: 'join', room: 'obecne' }));
});

socket.addEventListener('message', (event) => {
  const msg = JSON.parse(event.data);
  console.log('Přišla zpráva:', msg);
});

socket.addEventListener('close', (event) => {
  console.log(`Spojení uzavřeno: ${event.code} ${event.reason}`);
});

socket.addEventListener('error', () => {
  console.error('Chyba spojení');
});
Code language: JavaScript (javascript)

Stav spojení najdete ve vlastnosti readyState, která nabývá hodnot CONNECTING (0), OPEN (1), CLOSING (2) a CLOSED (3). Volání send() ve stavu CONNECTING vyhodí výjimku InvalidStateError, proto posílejte data až po události open.

Událost error z bezpečnostních důvodů neobsahuje žádné podrobnosti o chybě. Prohlížeč tak brání tomu, aby stránka pomocí WebSocketu zkoumala vnitřní síť uživatele. Po chybě vždy následuje událost close, takže logiku obnovení spojení stačí umístit do jejího obsluhovače.

Binární data přicházejí ve výchozím nastavení jako objekt Blob. Pokud s nimi chcete pracovat přímo, nastavte socket.binaryType = 'arraybuffer'. Metoda close(code, reason) přijímá pouze kód 1000 nebo kódy z rozsahu 3000 až 4999 a důvod smí mít v kódování UTF-8 nejvýše 123 bajtů.

Prohlížečové API má i několik omezení, o kterých je dobré vědět předem. Neumožňuje posílat rámce ping, ani nastavit vlastní hlavičky handshaku, takže nelze použít hlavičku Authorization. Vlastnost bufferedAmount pak udává, kolik bajtů čeká ve vyrovnávací paměti na odeslání. Pokud posíláte velké objemy dat, sledujte ji, jinak může paměť klienta nekontrolovaně růst.

Server v Node.js

Node.js od verze 22 obsahuje vestavěného WebSocketového klienta, server však vestavěný nemá. Nejpoužívanější knihovnou je ws. Následující příklad ukazuje server, který kontroluje hlavičku Origin, ověřuje uživatele ještě před dokončením handshaku, omezuje velikost zpráv a rozesílá zprávy všem připojeným klientům:

import { createServer } from 'node:http';
import { WebSocketServer, WebSocket } from 'ws';

const ALLOWED_ORIGINS = new Set(['https://example.com']);

const server = createServer();
const wss = new WebSocketServer({ noServer: true, maxPayload: 64 * 1024 });

function reject(socket, status) {
  socket.write(`HTTP/1.1 ${status}\r\nConnection: close\r\n\r\n`);
  socket.destroy();
}

server.on('upgrade', (req, socket, head) => {
  if (!ALLOWED_ORIGINS.has(req.headers.origin)) {
    return reject(socket, '403 Forbidden');
  }

  const user = authenticate(req); // vlastní funkce, např. ověření session cookie
  if (!user) {
    return reject(socket, '401 Unauthorized');
  }

  wss.handleUpgrade(req, socket, head, (ws) => {
    wss.emit('connection', ws, req, user);
  });
});

function broadcast(payload) {
  const data = JSON.stringify(payload);
  for (const client of wss.clients) {
    if (client.readyState === WebSocket.OPEN) {
      client.send(data);
    }
  }
}

wss.on('connection', (ws, req, user) => {
  ws.isAlive = true;
  ws.on('pong', () => { ws.isAlive = true; });

  ws.on('message', (data, isBinary) => {
    if (isBinary) return;

    let msg;
    try {
      msg = JSON.parse(data.toString());
    } catch {
      ws.send(JSON.stringify({ type: 'error', reason: 'invalid_json' }));
      return;
    }

    if (msg.type === 'chat' && typeof msg.text === 'string') {
      broadcast({ type: 'chat', from: user.name, text: msg.text.slice(0, 2000) });
    }
  });
});

server.listen(8080);
Code language: JavaScript (javascript)

Funkce authenticate je zde jen naznačena; její implementace závisí na tom, jak ve vaší aplikaci funguje přihlášení. Klíčové je, že ověření proběhne ještě před voláním handleUpgrade. Nepřihlášený klient tak dostane obyčejnou HTTP odpověď a WebSocketové spojení se vůbec nevytvoří.

Parametr maxPayload omezuje maximální velikost přijaté zprávy. Knihovna ws má ve výchozím stavu limit 100 MiB, což je pro většinu aplikací zbytečně mnoho a otevírá to prostor pro vyčerpání paměti serveru. Pokud klient limit překročí, knihovna spojení ukončí s kódem 1009.

Udržení spojení naživu

Otevřené spojení, po kterém delší dobu nic neteče, je ohrožené. Síťové prvky s překladem adres (NAT), firewally a proxy servery neaktivní spojení po určité době tiše zahazují. TCP navíc samo neodhalí situaci, kdy protější strana zmizela bez rozloučení, například když notebook usne nebo mobil přejde mezi sítěmi. Server pak může dlouho držet takzvaná polootevřená spojení, která spotřebovávají paměť a která nikdy nic nedoručí.

Řešením je pravidelný heartbeat. Server v intervalu posílá rámec ping a čeká na pong. Prohlížeče na ping odpovídají automaticky, aniž byste museli psát jakýkoli kód. Pokud pong do dalšího kola nepřijde, server spojení ukončí:

const interval = setInterval(() => {
  for (const ws of wss.clients) {
    if (!ws.isAlive) {
      ws.terminate();
      continue;
    }
    ws.isAlive = false;
    ws.ping();
  }
}, 30_000);

wss.on('close', () => clearInterval(interval));
Code language: JavaScript (javascript)

Interval volte kratší, než je nejkratší časový limit nečinnosti na cestě mezi klientem a serverem. Běžná hodnota je 20 až 30 sekund.

Protože prohlížeč neumí ping odeslat sám, klient výpadek serveru pozná až ve chvíli, kdy se pokusí něco poslat, nebo když operační systém spojení zavře. Pokud potřebujete rychlou detekci i na straně klienta, zaveďte ping na aplikační úrovni, tedy běžnou zprávu typu {"type":"ping"}, na kterou server odpoví.

Druhou polovinou spolehlivosti je obnovení spojení. Spojení dříve či později spadne vždy a klient se musí umět znovu připojit. Nikdy to však nedělejte okamžitě a v pevném intervalu. Když se restartuje server s deseti tisíci klienty, všichni by se ve stejnou chvíli pokusili připojit znovu a server by pod náporem opět spadl. Používejte exponenciální prodlevu s náhodnou složkou:

function connect(url, onMessage) {
  let attempt = 0;
  let socket;

  function open() {
    socket = new WebSocket(url);

    socket.addEventListener('open', () => { attempt = 0; });
    socket.addEventListener('message', onMessage);
    socket.addEventListener('close', (event) => {
      if (event.code === 1000) return; // řádné ukončení, znovu se nepřipojujeme

      const base = Math.min(30_000, 1000 * 2 ** attempt);
      const delay = base / 2 + Math.random() * (base / 2);
      attempt++;
      setTimeout(open, delay);
    });
  }

  open();

  return {
    send: (data) => socket.send(data),
    close: () => socket.close(1000),
  };
}
Code language: JavaScript (javascript)

Tento příklad je záměrně zjednodušený. V produkční aplikaci budete chtít zprávy odeslané během výpadku ukládat do fronty a odeslat je po obnovení spojení. Také je potřeba vyřešit zprávy, které klient během výpadku zmeškal. Osvědčeným postupem je číslovat zprávy ze serveru pořadovým číslem a po obnovení spojení poslat serveru číslo poslední přijaté zprávy. Server pak chybějící zprávy dodá, případně klientovi sdělí, že musí načíst aktuální stav znovu. Jde o stejný princip, jaký u SSE zajišťuje hlavička Last-Event-ID.

Návrh aplikačního protokolu

WebSocket přenáší zprávy, ale neříká nic o jejich významu. Ten si musíte navrhnout sami a vyplatí se to udělat hned na začátku. Osvědčila se jednotná obálka, například {"type": "...", "id": "...", "payload": {...}}. Pole type určuje, jak se zpráva zpracuje, a pole id umožňuje párovat odpovědi s požadavky. Na rozdíl od HTTP totiž WebSocket nemá pojem odpovědi; pokud klient pošle dva požadavky, odpovědi mohou přijít v libovolném pořadí.

Každou příchozí zprávu na serveru validujte, ideálně pomocí schématu. Že se klient úspěšně připojil, ještě neznamená, že posílá smysluplná data. Pro verzování protokolu se hodí zmíněná hlavička Sec-WebSocket-Protocol: klient nabídne například chat.v2, chat.v1 a server vybere verzi, kterou podporuje.

Formát JSON je pohodlný a snadno se ladí v nástrojích pro vývojáře v prohlížeči. Pokud přenášíte velké objemy dat nebo velmi časté aktualizace, například polohy hráčů ve hře, zvažte binární formát, jako je MessagePack nebo Protocol Buffers.

Bezpečnost

Na WebSocket se nevztahuje politika stejného původu (same-origin policy) ani mechanismus CORS. Jakákoli stránka může otevřít spojení na váš server a prohlížeč k handshaku přiloží cookies uživatele, pokud to neomezí jejich atribut SameSite. Pokud tedy autentizujete pomocí cookies a nekontrolujete hlavičku Origin, útočník může z vlastní stránky otevřít spojení jménem přihlášeného uživatele a číst jeho data. Tento útok se nazývá Cross-Site WebSocket Hijacking a obranou je právě kontrola hlavičky Origin proti seznamu povolených domén, jak ukazuje příklad serveru výše. Hlavičku Origin lze sice podvrhnout mimo prohlížeč, tam ale útočník nemá k dispozici cookies oběti.

Protože prohlížeč neumí poslat hlavičku Authorization, používá se pro autentizaci několik přístupů. Kromě cookies to může být token poslaný v první zprávě po otevření spojení, kdy server do úspěšného ověření žádné jiné zprávy nezpracovává. Další možností je krátkodobý jednorázový lístek v URL, který si klient předem vyžádá běžným HTTP požadavkem. Dlouhodobý token do URL nevkládejte, protože adresy se běžně ukládají do logů serverů a proxy.

Nezapomínejte ani na to, že spojení může trvat hodiny. Oprávnění uživatele se mezitím mohou změnit, platnost jeho relace může vypršet nebo může být jeho účet zablokován. Oprávnění proto kontrolujte u každé operace, ne jen při připojení, a při odhlášení uživatele jeho otevřená spojení uzavřete.

Mezi další zásady patří: v produkci vždy používejte wss://, omezte velikost zpráv i jejich počet za časovou jednotku, omezte počet současných spojení jednoho uživatele a obsah zpráv nikdy nevkládejte do stránky pomocí innerHTML. Zpráva od jiného uživatele je stejně nedůvěryhodný vstup jako obsah formuláře.

Nasazení za reverzní proxy a škálování

Většina aplikací běží za reverzní proxy, která musí o WebSocketu vědět. Hlavičky Upgrade a Connection jsou takzvané hop-by-hop hlavičky, a proxy je proto bez výslovného nastavení dál nepředá. Konfigurace pro nginx vypadá takto:

map $http_upgrade $connection_upgrade {
    default upgrade;
    ''      close;
}

server {
    location /ws/ {
        proxy_pass http://backend;
        proxy_http_version 1.1;
        proxy_set_header Upgrade $http_upgrade;
        proxy_set_header Connection $connection_upgrade;
        proxy_set_header Host $host;
        proxy_read_timeout 120s;
    }
}
Code language: PHP (php)

Direktiva proxy_read_timeout má výchozí hodnotu 60 sekund. Pokud během této doby spojením nic neprojde, nginx ho uzavře. Proto je interval heartbeatu potřeba sladit s časovými limity všech prvků na cestě, včetně cloudových load balancerů, které mívají vlastní limit nečinnosti.

Při horizontálním škálování narazíte na zásadní rozdíl oproti bezstavovému HTTP. Každé spojení je trvale navázáno na jednu instanci serveru. Pokud uživatel A je připojen k instanci 1 a uživatel B k instanci 2, instance 1 nemůže zprávu pro uživatele B doručit sama. Potřebujete sdílenou vrstvu pro rozesílání zpráv, typicky systém publish/subscribe, jako je Redis Pub/Sub nebo NATS. Každá instance se přihlásí k odběru relevantních kanálů a zprávy předává svým lokálně připojeným klientům.

Samotný WebSocket nevyžaduje takzvané sticky sessions, protože po navázání spojení už všechna komunikace přirozeně teče na jednu instanci. Potřebné jsou až u knihoven, které jako zálohu používají long polling složený z mnoha samostatných HTTP požadavků, například Socket.IO.

Pamatujte také na nasazování nových verzí. Při restartu serveru pošlete klientům rámec close s kódem 1001, aby se mohli řízeně připojit k jiné instanci, a spoléhejte na náhodnou prodlevu při obnovení spojení. Na serverech s velkým počtem spojení hlídejte limit otevřených souborových deskriptorů operačního systému, protože každé spojení jeden spotřebuje.

WebSocket lze od vydání RFC 8441 provozovat i nad HTTP/2 a díky RFC 9220 také nad HTTP/3. Tyto varianty podporují některé prohlížeče i servery, v praxi však většina provozu stále využívá upgrade z HTTP/1.1.

Kdy WebSocket nepoužít

WebSocket není univerzální odpověď. Pokud data tečou pouze ze serveru ke klientovi, což je případ notifikací, sportovních výsledků nebo streamování odpovědí jazykových modelů, bývá jednodušší volbou SSE. Funguje nad běžným HTTP, takže mu rozumí stávající infrastruktura včetně autentizace, logování a komprese. Obnovení spojení řeší prohlížeč sám a nad HTTP/2 odpadá omezení šesti současných spojení na doménu, které u HTTP/1.1 dříve komplikovalo aplikace otevřené ve více záložkách.

Pro náročnější scénáře, jako jsou hry nebo přenos médií, vzniká WebTransport postavený na HTTP/3. Nabízí více nezávislých proudů v rámci jednoho spojení, takže ztráta jednoho paketu nezablokuje ostatní proudy, a také nespolehlivé datagramy pro data, u kterých je čerstvost důležitější než úplnost. Podpora v prohlížečích i na serverech se však teprve ustaluje. V prohlížečích založených na Chromiu se také experimentuje s rozhraním WebSocketStream, které řeší zpětný tlak (backpressure) pomocí Streams API.

Konečně stojí za zvážení, zda protokol implementovat sami. Knihovny jako Socket.IO přidávají obnovení spojení, místnosti, potvrzování zpráv i škálování přes Redis. Používají však vlastní protokol nad WebSocketem, takže se klient Socket.IO nepřipojí k obyčejnému WebSocketovému serveru a naopak. Existují také spravované služby, které provoz spojení převezmou úplně.

Závěr

WebSocket je zralá a všude podporovaná technologie, která webové aplikaci umožní reagovat na události v reálném čase. Otevřít spojení a poslat první zprávu zvládnete na deseti řádcích kódu. Skutečná práce ale začíná až potom: udržet spojení naživu, obnovit ho po výpadku bez ztráty dat, zabezpečit ho proti zneužití a škálovat ho na více serverů.

Než se pro WebSocket rozhodnete, položte si otázku, zda opravdu potřebujete obousměrnou komunikaci s nízkým zpožděním. Pokud ano, navrhněte si od začátku aplikační protokol, počítejte s tím, že spojení bude padat, a kontrolujte původ i oprávnění. Pokud ne, možná vám bohatě postačí Server-Sent Events. V obou případech už vaši uživatelé nebudou muset mačkat F5.

Komentáře

Odebírat
Upozornit na
guest
0 Komentářů
Nejstarší
Nejnovější Nejvíce hlasů

Zahoďte knihovny: dropdowny, tooltipy a přechody stránek jen s HTML a CSS

Rozbalovací menu, tooltip, modální okno a plynulý přechod mezi stránkami. Ještě nedávno čtyři důvody, proč do projektu přidat několik knihoven a desítky kilobajtů JavaScriptu. V roce 2026 už to všechno zvládne prohlížeč sám. Ukážeme si, jak na to s Popover API, CSS anchor positioningem a View Transitions, a také kde mají své limity.

Rok po Shai-Hulud: co se změnilo na npm a co musíte udělat do ledna

Před rokem začal registrem npm procházet samošířící červ Shai-Hulud. Od té doby se útoky na dodavatelský řetězec JavaScriptu staly pravidelnou záležitostí a npm postupně přepisuje pravidla publikování. Projdeme, co se za ten rok stalo, jak dnes bezpečně publikovat balíček, co musíte stihnout do ledna 2027 a jak se bránit jako uživatel balíčků.

Vercel zrychlil směrování v CDN. Překvapivě pomohly menší bloky

Vercel zrychlil směrování v CDN tím, že začal načítat víc dat najednou. U velkých webů mu po každém nasazení scházely v cache údaje o mnoha jednotlivých cestách, takže je musel opakovaně dohledávat. U nových bloků metadat Vercel naměřil o 91 % kratší dobu tohoto hledání v 99. percentilu. Cesta k výsledku ale vedla přes nečekané zjištění: příliš velký blok se při vyřizování požadavků nevyplatil.