Minulú zimu sme jeden SaaS dashboard zmigrovali z Next.js Pages Routera na App Router a React Server Components. Migrácia samotná bola prekvapivo hladká — dva inžinieri, deväť dní, žiaden drama. Nasadili sme v piatok večer, dali si pivo a mysleli sme, že máme hotovo. Pondelok nás vyviedol z omylu. Toto sú tri veci, ktoré v tutoriáloch a konferenčných demách nikto nehovorí — lebo v deme s jedným fetchom sa neprejavia.
Píšeme to ako poznámku z praxe, nie ako návod. Server Components v produkcii používame ďalej a radi — sú jadrom toho, ako dnes staviame webové aplikácie. Len sme sa popri tom naučili tri veci po zlom.
Vec č. 1: vodopády, ktoré nevidíš v komponentovom strome
Každý async server component si počká na svoje dáta. To je pekné, kým ich nemáš zanorené. Náš dashboard mal stránku, ktorá renderovala layout, v ňom panel, v ňom widget — a každá úroveň si fetchovala vlastné dáta. V deme to boli tri rýchle fetchy. V produkcii to bol sekvenčný vodopád: layout počkal na svoje query, až potom sa začal renderovať panel, ktorý počkal na svoje, až potom widget. Time-to-first-byte vyskočil z 200 ms na 1,4 sekundy a nikto nechápal prečo — v dev móde s lokálnou DB to bolo neviditeľné.
Fix bol nudný: dáta, ktoré od seba nezávisia, fetchujeme paralelne cez `Promise.all` čo najvyššie v strome, a pomalé časti zabalíme do `Suspense`, aby sa stránka začala streamovať skôr, než dorazí to pomalé. Po zmene TTFB spadol späť pod 300 ms. Lekcia: zanorenie async komponentov je zanorenie latencií, a v deve to neuvidíš.
Vec č. 2: cache, ktorá je agresívna a neviditeľná
Toto nás stálo najviac. App Router defaultne cachuje `fetch()` volania aj celé routy. Demo to oslavuje ako fíčru — a aj je. Lenže náš dashboard ukazoval dáta, ktoré sa menili každú minútu, a my sme používateľom servírovali stav spred hodín. Žiadna chyba v logu, žiaden alert. Len klient, ktorý napísal „prečo tu vidím včerajšie čísla”.
Default je „cache, kým ti nepoviem inak”, nie „čerstvé, kým si nepýtaš cache”. Museli sme prejsť každý fetch a vedome rozhodnúť: toto je `no-store`, toto má `revalidate: 60`, toto môže žiť hodinu. Až keď sme cache nastavovali zámerne, dashboard sa správal predvídateľne. Odvtedy je naše pravidlo, že na akýkoľvek dynamický dáta-fetch sa stratégia cache píše hneď pri prvom riadku — nie keď sa klient ozve.
Vec č. 3: hranica „use client” presakuje nahor
Keď komponent označíš `"use client"`, klientom sa stane celý jeho podstrom. Vieš to, kým robíš listy. Zabudneš to, keď tlačíš `"use client"` vyššie, len aby si použil jeden `useState`. My sme to spravili na komponente blízko vrcholu stránky kvôli jednej knižnici na grafy, ktorá to vyžadovala — a ticho sme tým z polovice „server” komponentov spravili klientske. JS bundle stránky narástol zhruba o 40 % a my sme nechápali, kam sa podelo to, čo malo zostať na serveri.
Riešenie nie je technické, je disciplinárne: `"use client"` patrí čo najnižšie v strome, na list, nie na vetvu. Tú knižnicu na grafy sme obalili do malého klientského komponentu a zvyšok stránky nechali na serveri. Bundle spadol späť. Toto je presne ten istý vzor, ktorý opisujeme pri Redise — pridáš niečo „len dočasne, vysoko” a o mesiac to ťahá náklady, o ktorých nikto nerozhodol nahlas.
Server Components nie sú ťažké. Ťažké je, že tri veci, ktoré ťa potrápia, sa v deme s jedným fetchom nikdy neukážu. Ukážu sa v piatok večer po nasadení.
Čo sme si z toho prepísali do pravidiel
- Nezávislé dáta fetchuj paralelne a čo najvyššie; pomalé časti daj do Suspense. Zanorené awaity sú zanorené latencie.
- Stratégiu cache píš zámerne pri každom fetchi. Default je cache — to je dobré pre blog, zlé pre dashboard.
- `use client` daj na list, nie na vetvu. Každý posun nahor pridáva do bundlu celý podstrom.
Nič z toho neznamená, že je App Router zlý. Pre väčšinu webových aplikácií, ktoré dnes staviame, je to default — rovnako ako bol pri Helply. Len sme sa naučili, že produkcia preverí presne tie tri miesta, ktoré demo preskočí. Ak migrujete väčšiu appku na Server Components a chcete sa vyhnúť nášmu pondelku, ozvite sa — radi sa pozrieme na váš dáta-flow skôr, než nasadíte.