Powrót SSR: serwer znowu renderuje HTML
- data
- kategoria
- Frontend
- czytanie
- 3 min / 580 słów
CSR dał frontendowi pełną kontrolę nad aplikacją, ale pierwsze załadowanie zaczęło mieć coraz dłuższy critical path.
Browser musiał pobrać JavaScript, uruchomić runtime, odkryć zależności danych i dopiero potem wygenerować UI.
Naturalna odpowiedź była prosta:
wyrenderuj pierwszy widok wcześniej, na serwerze.
SSR wraca, ale w innej formie
Klasyczny server-rendered web działał tak:
request
|
v
server
|
v
HTML
|
v
browser
Nowoczesny SSR dodaje jeszcze jeden etap:
request
|
v
server render
|
v
HTML
|
v
browser shows content
|
v
JavaScript loads
|
v
application takes over
To ważna różnica.
Serwer nie renderuje strony po to, żeby frontend przestał istnieć.
Renderuje ją po to, żeby pierwszy widok powstał wcześniej.
Potem browser nadal może działać jak SPA.
Po co robić tę samą pracę na serwerze
Załóżmy, że użytkownik otwiera:
/products/42
W czystym CSR może być tak:
HTML shell
|
v
JS
|
v
router
|
v
fetch product
|
v
render
W SSR:
request /products/42
|
v
server fetches product
|
v
server renders HTML
|
v
browser receives content
Użytkownik może zobaczyć nazwę, cenę i opis produktu zanim cały runtime aplikacji zdąży się uruchomić.
To skraca ścieżkę do pierwszej treści.
Ale nie oznacza, że koszt klienta znika.
SSR nie usuwa JavaScriptu
To jeden z najważniejszych punktów.
Jeśli aplikacja ma być później interaktywna, browser nadal często musi pobrać:
framework
komponenty
routing
event handling
application state
kod odpowiedzialny za kolejne interakcje
Dlatego SSR nie jest:
server zamiast browsera
Tylko raczej:
server najpierw
browser później
W praktyce część pracy może zostać wykonana dwukrotnie.
Serwer generuje HTML, a klient później rekonstruuje komponentowy runtime potrzebny do dalszego działania aplikacji.
Ten proces stanie się osobnym problemem, który później nazwiemy hydration.
TTFB zaczyna znowu mieć znaczenie
W CSR serwer może zwrócić niemal natychmiast:
<div id="app"></div>
To daje świetny TTFB, ale niewiele mówi o tym, kiedy pojawi się właściwy content.
SSR przesuwa pracę przed odpowiedź.
request
|
v
database
|
v
business logic
|
v
render
|
v
response
To może zwiększyć Time to First Byte.
I tu pojawia się pierwszy realny trade-off:
CSR
-> szybki pusty response
-> więcej pracy później
SSR
-> więcej pracy przed response
-> więcej gotowej treści wcześniej
Dlatego stwierdzenie:
SSR jest szybszy
jest zbyt proste.
Szybszy w którym miejscu ścieżki?
Komercyjny przykład: product detail page
Sklep internetowy jest dobrym przypadkiem dla SSR.
Użytkownik wchodzi bezpośrednio z wyszukiwarki na stronę produktu.
Najważniejsze dane są znane już podczas requestu:
nazwa
cena
zdjęcie
dostępność
opis
Serwer może pobrać je i od razu wygenerować:
<h1>Mechanical Keyboard</h1>
<p>129 zł</p>
<p>In stock</p>
Browser nie musi czekać, aż framework wykona fetch i pierwszy render.
Jednocześnie później można uruchomić interaktywny koszyk, warianty produktu czy rekomendacje.
Współczesne frameworki takie jak Next.js, Nuxt czy SvelteKit wykorzystują ten model jako jedną z dostępnych strategii.
Ciekawostka
SSR może stać się problemem CPU nawet wtedy, gdy pojedynczy render jest szybki.
Załóżmy:
render = 20 ms CPU
Przy jednym użytkowniku to praktycznie nic.
Przy tysiącach requestów na sekundę sytuacja wygląda inaczej.
Jeżeli wszystkie requesty wymagają renderowania:
request
request
request
request
request
|
v
limited CPU cores
zaczyna pojawiać się kolejka.
Latency użytkownika nie jest wtedy tylko:
render time
ale:
queue time + render time
Dlatego przy dużym ruchu optymalizacja SSR staje się problemem nie tylko frameworka, ale też concurrency, cache i capacity planning.
Personalizacja komplikuje cache
SSR świetnie pasuje do dynamicznych danych.
Ale im bardziej odpowiedź zależy od requestu, tym trudniej ją cache'ować.
Przykład:
/products/42
Jeśli każdy użytkownik widzi ten sam HTML, odpowiedź można łatwo trzymać na CDN.
Ale jeśli strona zależy od:
user
country
currency
experiment
session
pricing tier
liczba możliwych wariantów rośnie.
W praktyce trzeba zdecydować, co naprawdę musi być dynamiczne.
Często tylko mały fragment strony jest spersonalizowany, podczas gdy reszta mogłaby być współdzielona przez wszystkich użytkowników.
To pytanie później doprowadzi do bardziej granularnych modeli renderowania.
SSR może też tworzyć waterfall
Przeniesienie renderu na serwer nie usuwa zależności danych.
Może jedynie przenieść je w inne miejsce.
Źle napisany SSR:
fetch user
|
v
fetch product
|
v
fetch reviews
|
v
fetch recommendations
|
v
render
oznacza, że użytkownik czeka na cały sekwencyjny chain zanim dostanie pierwszy bajt HTML.
Serwer może więc stworzyć dokładnie taki sam waterfall, jaki wcześniej mieliśmy w browserze.
Tyle że teraz waterfall blokuje całą odpowiedź.
SSR rozwiązuje jeden problem i tworzy następny
Powrót renderowania na serwer poprawił pierwsze wyświetlenie treści, ale pojawiło się pytanie:
co dalej?
Mamy gotowy HTML.
Ale browser nadal potrzebuje JavaScriptu, żeby buttony działały, state był aktualny, a navigation pozostała client-side.
Czyli trzeba połączyć:
HTML generated on server
z:
application runtime in browser
To połączenie nie jest darmowe ani trywialne.
Właśnie dlatego kolejnym dużym elementem współczesnej architektury renderowania stała się hydration.