jvinhit//lab

Search posts

Type to search across journal entries.

navigate open esc close

Web Performance · Part 15 — Rendering & Delivery Strategies

CSR, SSR, SSG, ISR, streaming SSR và islands/partial/progressive hydration ảnh hưởng LCP, INP, CLS, TTFB ra sao — và khung quyết định chọn kiến trúc render theo mục tiêu hiệu năng.

Tối ưu vi mô (ảnh, font, bundle) có trần. Đòn bẩy lớn nhất thường là kiến trúc render — bạn dựng HTML ở đâu (server hay client) và “tưới sống” (hydrate) ra sao. Phần này so sánh các chiến lược render qua lăng kính Core Web Vitals, để bạn chọn đúng cho từng loại trang thay vì theo trào lưu framework.

Câu hỏi trung tâm: HTML có nội dung tới mắt người dùng nhanh thế nào (LCP/TTFB), và trang phản hồi tương tác nhanh thế nào (INP)? Mỗi chiến lược đánh đổi hai điều này khác nhau.


1. Bốn cách dựng HTML

CSR (Client-Side Rendering)
  Server gửi document shell + JS → JS fetch dữ liệu và dựng phần nội dung chính
  Document có thể nhỏ, nhưng LCP thường phải chờ mạng + JS; CPU client cao

SSR (Server-Side Rendering)
  Server dựng HTML đầy đủ mỗi request → client hydrate
  LCP nhanh hơn (HTML có sẵn), TTFB phụ thuộc tốc độ server

SSG (Static Site Generation)
  Dựng HTML lúc BUILD → phục vụ tĩnh qua CDN
  TTFB thường thấp; nội dung build-time cần rebuild/revalidate, phần client vẫn có thể động

ISR (Incremental Static Regeneration)
  Pattern cache của một số framework: phục vụ bản tĩnh rồi tái sinh theo thời gian/nhu cầu
  Nhanh khi cache hit, đổi lại có cửa sổ stale và bài toán invalidation
Chiến lượcTTFB thường gặpLCPDữ liệuCPU client
CSRshell có thể nhanhdễ trễ nếu nội dung chờ JSruntimecao nếu render nhiều
SSRphụ thuộc server/cachecó lợi nếu HTML + asset tới sớmtheo requestphụ thuộc hydration
SSGthấp khi ở CDNcó lợi, không tự động bảo đảmbuild-time + client fetch tùy apptừ gần 0 tới cao
ISRthấp khi cache hitgần SSG khi cache hitgần-tươi, theo policyphụ thuộc hydration

Quy tắc thô: nội dung tĩnh/ít đổi (blog, docs, marketing) → SSG hoặc cache/revalidation; cá nhân hóa cao/dữ liệu tươi → SSR hay hybrid; app sau đăng nhập, SEO không quan trọng → CSR có thể chấp nhận. Không chiến lược nào tự động cho CWV tốt: CSS, ảnh LCP, cache, data dependency và lượng JS vẫn quyết định kết quả.


2. Vấn đề hydration — “uncanny valley”

SSR/SSG có thể trả HTML nhanh. Phần HTML tĩnh không cần hydrate; chỉ UI có runtime client mới cần JS gắn listener và khôi phục state. Với hydration toàn trang, UI có thể trông sẵn sàng trước khi handler sẵn sàng; tương tác trong khoảng này có thể bị trì hoãn, replay hoặc mất tùy framework — vì vậy phải test user flow thật thay vì suy từ ảnh chụp LCP.

SSR truyền thống:
  [HTML hiện nhanh — LCP tốt] ──── [JS tải] ──── [hydrate CẢ trang] ── tương tác được
                                    người dùng bấm ở đây → không phản hồi (INP xấu)

Hydrate toàn trang nghĩa là tải + chạy phần lớn cây component dù nhiều vùng chỉ hiển thị nội dung. Nó có thể tạo long task và cạnh tranh với tương tác, là một nguyên nhân phổ biến của INP xấu trong app SSR nhiều JavaScript.


3. Các kiểu hydration giảm chi phí

① Islands (Astro, Fresh)
   Trang chủ yếu HTML tĩnh; chỉ vài "đảo" tương tác được hydrate.
   → JS tối thiểu, INP tốt cho trang nội dung.

② Partial / Selective hydration
   Hydrate component theo nhu cầu, không phải tất cả cùng lúc.

③ Progressive hydration
   Hydrate dần theo độ ưu tiên / khi cuộn tới (lazy).

④ Server Components (ví dụ React Server Components)
   Tách component chỉ chạy server khỏi ranh giới client; code server không vào
   client bundle, còn Client Components vẫn cần hydrate.

Tên gọi partial/selective/progressive không được các framework dùng hoàn toàn giống nhau. Tư duy chung vẫn là gửi ít JS hơn, hydrate ít hơn và ưu tiên đúng vùng. Islands là chiến lược hydration; Server Components là ranh giới thực thi/server-client rộng hơn, không nên đồng nhất hai khái niệm.


4. Streaming SSR — trả HTML theo dòng

Thay vì chờ server dựng toàn bộ HTML rồi mới gửi, streaming SSR có thể flush shell/<head> sớm rồi stream phần cần dữ liệu khi sẵn sàng. Nó giảm thời gian tới nội dung đầu tiên khi server thực sự flush sớm; nếu proxy buffer response hoặc shell cũng chờ dữ liệu, lợi ích biến mất.

Không streaming:  request → [chờ MỌI dữ liệu 600ms] → gửi cả HTML
Streaming:        request → gửi shell ngay (TTFB thấp)
                          → stream phần A khi query A xong
                          → stream phần B khi query B xong

React (renderToPipeableStream) + <Suspense> cho phép bao phần chậm: shell + nội dung nhanh hiện trước, phần chậm stream sau với fallback. Nếu phần tử LCP thật nằm trong boundary chậm, fallback sớm không tự làm LCP tốt; phải đo candidate và element render delay.


5. Ảnh hưởng tới từng Core Web Vital

LCP:  SSG/SSR/streaming giúp nội dung được khám phá sớm, nhưng còn phụ thuộc
      TTFB, CSS, ảnh/font và boundary chứa candidate. CSR dễ thêm chuỗi chờ JS.

INP:  hydration nặng có thể là nguồn main-thread work lớn. Ít JS / islands / RSC / progressive
      hydration → INP tốt hơn. Chia nhỏ task (Part 16).

CLS:  SSR/SSG dễ bị CLS nếu hydration thay đổi layout, hoặc nội dung
      stream chèn vào đẩy layout. Đặt chỗ trước (reserve space), tránh
      chèn nội dung phía trên (Part 7).

TTFB: SSG/CDN thường thấp; SSR phụ thuộc server/cache/data source.
      Streaming có thể đưa byte đầu/shell tới sớm nhưng không sửa backend chậm.

Bẫy CLS với streaming: nội dung stream vào sau phải có chỗ đặt trước, nếu không nó đẩy phần đã hiện → layout shift. Streaming cải thiện LCP nhưng cần kỷ luật CLS.


6. Khung quyết định

Trang nội dung tĩnh (blog/docs/landing)
  → SSG/ISR + islands. JS tối thiểu. LCP & INP đều tốt.

Trang động, discoverability quan trọng (e-commerce, tin tức)
  → SSR/cache revalidation hoặc hybrid; streaming khi dependency chậm không đều.
    RSC chỉ là lựa chọn khi stack React phù hợp, không phải yêu cầu.

App sau đăng nhập (dashboard), SEO không cần
  → CSR/SPA chấp nhận được, nhưng vẫn tối ưu bundle & INP.

Dữ liệu chậm không đồng đều
  → streaming SSR + Suspense (shell nhanh, phần chậm stream sau).

Đừng chọn kiến trúc theo framework “hot”; chọn theo đặc tính trangmục tiêu metric. Một site thường trộn nhiều chiến lược (marketing = SSG, app = CSR, checkout = SSR).


7. Tóm tắt

  • CSR dễ thêm chuỗi chờ JS cho LCP; SSR đưa HTML nội dung tới sớm nhưng TTFB theo server; SSG/cache revalidation thường có lợi khi CDN hit. Không lựa chọn nào tự bảo đảm CWV.
  • Hydration toàn trang có thể tạo long task và khoảng UI trông sẵn sàng trước handler; HTML tĩnh thì không cần hydrate.
  • Giảm chi phí bằng islands, partial/progressive hydration, Server Components (RSC) — gửi ít JS, hydrate ít.
  • Streaming SSR + Suspense có thể trả shell sớm rồi stream phần chậm; lợi ích LCP phụ thuộc candidate, và cần đặt chỗ trước để tránh CLS.
  • Chọn kiến trúc theo đặc tính trang + mục tiêu metric, không theo trào lưu; trộn nhiều chiến lược trong một site là bình thường.

Phần tiếp theo: INP deep & main-thread scheduling — Long Animation Frames (LoAF) API, scheduler.postTask/yield, isInputPending, và chia nhỏ tác vụ dài để giữ INP “Good”.