Skip to content

Hero-ul primei pagini se vopsește din HTML-ul servit

Vitalie requested to merge 44_paint-hero-from-html into main

Ține de #44 (closed) și de #10 (closed). Nu le închide — se închid când cifrele de pe producție confirmă.

Problema, măsurată

curl https://hyperice.md/ dă 9.417 octeți, din care capul ~8.500 — corpul are sub o mie. Nimic nu se vopsește până nu ajunge și pornește JavaScript-ul. Măsurat azi (Lighthouse 13.4.1, mobil, mediana a 5 rulări, mașină liniștită): FCP 2,93 s, LCP 4,21 s, față de pragul de 2,5 s.

LCP nu poate fi niciodată mai devreme decât FCP. Livrarea de azi a dovedit-o din partea cealaltă: indiciile din cap (!48 (merged)) au funcționat — capul conține acum preload-ul pozei și nouă modulepreload-uri, verificate pe producție — și LCP-ul s-a mișcat cu −0,26 s, adică sub zgomot. Nimic de deasupra podelei nu poate atinge pragul.

Ce face acest MR

Rendererul scrie stratul de imagine al hero-ului direct în <div id="root">, ca browserul să-l vopsească imediat ce are documentul, fișierul de stiluri și poza — fără JavaScript. React preia după aceea.

Verificat empiric pe React 19.2.7, versiunea din proiect: createRoot peste markup pus de server în #root îl înlocuiește curat la prima randare, cu zero mesaje în consolă. Nu e nevoie de hidratare.

Corpul emis are 629 de octeți.

Scop restrâns deliberat

Doar prima pagină. Pe pagina de produs elementul LCP începe la y=198 — are conținut deasupra — deci markup-ul serverului l-ar așeza la y=0 și ar sări când vine React. Pe prima pagină antetul stă peste hero: elementul LCP măsurat are boundingRect.top = 2.

Doar stratul de imagine, nu și textul. Copia hero-ului (h1 cu cn(), clase condiționate, --hl-size din numărul setat de admin, BannerButton) e prea intricată ca să fie dublată într-un șir fără să divergă în tăcere la prima editare din panou. Elementul LCP măsurat este poza (412×695), nu textul. Textul vine cu React și nu mută nimic: cutia are min-h-[695px], poza e absolute inset-0, iar copia intră într-un flex items-end mult sub înălțimea aia.

Săgețile, punctele și butonul de pauză sunt absolute — React le adaugă fără să miște nimic.

Geometria, măsurată

Fereastră Markup server, fără JS Producție după React Diferență
1440×900 x0 y0 1440×873 x0 y0 1440×873 zero
412×823 x0 y0 412×695 x0 y2 412×695 2 px

Cei 2 px vin din antetul mobil de 106 px față de marginea negativă de −104. Contribuția la CLS: ~0,002, față de 0,011 azi și un prag de 0,1.

Confirmat separat, cu foaia de stil reală de producție: picture { display: contents } se aplică, secțiunea iese 375×695 la y=0, iar currentSrc al pozei coincide cu fișierul numit de preload — deci o singură descărcare, nu două.

Testul de sincronizare

backend/tests/hero-markup.test.ts citește HeroBanner.tsx, extrage cele patru liste de clase și verifică că exact acele șiruri apar în documentul randat. Prinde orice clasă adăugată, scoasă sau reordonată, și orice listă mutată în cn() (ancora nu se mai găsește și testul pică zgomotos). Nu prinde structura: un element nou între cutie și poză, altă imbricare, sau o modificare în index.css. Scris explicit în comentariul testului.

Verificat

Comandă Rezultat
backend/ npm run build exit 0
backend/ npm test 543 de teste, toate trec
frontend/ npx tsc --noEmit -p tsconfig.app.json exit 0
frontend/ npx tsc --noEmit -p tsconfig.test.json exit 0
frontend/ npm run build exit 0
frontend/ npm test 226 de teste, toate trec

Ce nu s-a verificat: câștigul de LCP. Se măsoară după deploy, mediana a 5 rulări pe mașină liniștită, ca restul cifrelor din #10 (closed).

Edited by Vitalie

Merge request reports