Seminar Docs / Core Web Vitals

CORE WEB
VITALS

5 chỉ số Google dùng để đánh giá trải nghiệm người dùng thực tế — và là thước đo khách quan nhất để so sánh hiệu năng giữa SSG, SSR, ISR và CSR trong dự án này.

🖼️

LCP

< 2.5s

👆

FID

< 100ms

📐

CLS

< 0.1

TTFB

< 800ms

🧱

TBT

< 200ms

Lighthouse Score — Cách tính điểm

Trọng số từng metric

LCP
35%
TBT
25%
FCP
10%
Speed Index
10%
CLS
10%
TTI
10%

Nguồn: Lighthouse v11 scoring weights

Ước tính điểm theo rendering mode

SSG
95
ISR
92
SSR
78
CSR
45

* Ước tính dựa trên benchmark thực tế. Điểm thực tế đo bằng component RenderBenchmark trên mỗi trang.

Chi tiết từng chỉ số
🖼️LCP

Largest Contentful Paint

Target: < 2.5sPoor: > 4.0s

SSG

~0.8s

Good

ISR

~0.9s

Good

SSR

~1.8s

Fair

CSR

~4.5s

Poor

Đo lường gì?

Thời gian để phần tử lớn nhất trên màn hình (thường là hero image hoặc heading chính) được render hoàn toàn. Đây là chỉ số người dùng cảm nhận trực tiếp nhất — "trang này load nhanh không?"

Tại sao quan trọng?

Google coi LCP là chỉ số quan trọng nhất trong Core Web Vitals. LCP kém đồng nghĩa với bounce rate cao — người dùng bỏ trang trước khi nhìn thấy nội dung.

Tối ưu trong dự án

  • Dùng <Image> của Next.js với priority={true} cho hero image → trình duyệt biết preload sớm
  • SSG/ISR: image URL đã có trong HTML → browser bắt đầu download ngay khi parse HTML
  • Tránh lazy load image phần "above the fold" (phần nhìn thấy không cần scroll)
  • Serve ảnh từ CDN cùng region (DigitalOcean Spaces SGP1 → HCM < 15ms)

So sánh theo rendering mode

SSG
~0.8s
ISR
~0.9s
SSR
~1.8s
CSR
~4.5s
👆FID

First Input Delay

Target: < 100msPoor: > 300ms

SSG

~8ms

Good

ISR

~8ms

Good

SSR

~12ms

Good

CSR

~180ms

Fair

Đo lường gì?

Thời gian từ khi user lần đầu tương tác (click, tap, keypress) đến khi browser thực sự bắt đầu xử lý event handler. FID đo lường khả năng phản hồi của trang khi đang trong quá trình load.

Tại sao quan trọng?

FID cao xảy ra khi main thread đang bận parse/execute JS bundle — click của user bị xếp hàng chờ. Cảm giác trang "đơ" hoặc "không phản hồi" khi vừa mở. Google đã thay FID bằng INP (Interaction to Next Paint) từ 2024 nhưng nguyên tắc giống nhau.

Tối ưu trong dự án

  • Code splitting: Next.js tự động split theo route — trang nào chỉ load JS của trang đó
  • Tránh inline heavy computation trong useEffect ngay khi mount
  • Lazy load component không cần thiết với dynamic import()
  • CSR bundle lớn hơn vì inline toàn bộ component — main thread bị block lâu hơn

So sánh theo rendering mode

SSG
~8ms
ISR
~8ms
SSR
~12ms
CSR
~180ms
📐CLS

Cumulative Layout Shift

Target: < 0.1Poor: > 0.25

SSG

~0.02

Good

ISR

~0.02

Good

SSR

~0.03

Good

CSR

~0.18

Fair

Đo lường gì?

Tổng điểm dịch chuyển layout không mong muốn trong suốt vòng đời trang. Xảy ra khi element trên trang bị đẩy xung quanh sau khi đã render — ví dụ: ảnh load muộn đẩy text xuống, font swap làm text nhảy.

Tại sao quan trọng?

CLS cao là trải nghiệm tệ nhất cho người dùng — đang đọc bài thì text tự nhảy, click nhầm button vì nó dịch chuyển. Google phạt nặng trang có CLS > 0.25 trong ranking.

Tối ưu trong dự án

  • Luôn set width và height cho <Image> — Next.js Image tự reserve không gian trước khi ảnh load
  • CSR skeleton → real content swap gây CLS: dùng skeleton có đúng height với content thật
  • Font: dùng font-display: swap cẩn thận, hoặc preload font chính để tránh FOUT
  • Tránh inject content phía trên existing content sau khi render (ads, banners)

So sánh theo rendering mode

SSG
~0.02
ISR
~0.02
SSR
~0.03
CSR
~0.18
TTFB

Time to First Byte

Target: < 800msPoor: > 1800ms

SSG

~15ms

Good

ISR

~20ms

Good

SSR

~350ms

Fair

CSR

~15ms

Good

Đo lường gì?

Thời gian từ khi browser gửi HTTP request đến khi nhận byte đầu tiên của response. TTFB bao gồm: DNS lookup + TCP connection + SSL handshake + server processing time. Với SSG/ISR: server processing gần như 0. Với SSR: bao gồm cả thời gian fetch database.

Tại sao quan trọng?

TTFB là "starting gun" của mọi metric khác — nếu TTFB cao, mọi thứ sau đó đều bị delay. Đây là điểm khác biệt rõ nhất giữa SSG và SSR trong dự án này.

Tối ưu trong dự án

  • SSG/ISR TTFB thấp vì Coolify serve file tĩnh qua Traefik — không có Node.js processing
  • SSR TTFB = thời gian Express query MongoDB + Mongoose hydration + Next.js render to string
  • Đặt VPS cùng region với user (SGP1 cho VN) giảm TTFB ~150ms so với US region
  • MongoDB trong cùng Docker network → query latency < 1ms, không ảnh hưởng nhiều đến SSR TTFB

So sánh theo rendering mode

SSG
~15ms
ISR
~20ms
SSR
~350ms
CSR
~15ms
🧱TBT

Total Blocking Time

Target: < 200msPoor: > 600ms

SSG

~40ms

Good

ISR

~40ms

Good

SSR

~55ms

Good

CSR

~380ms

Poor

Đo lường gì?

Tổng thời gian main thread bị block bởi các long tasks (task > 50ms). Mỗi long task, phần vượt quá 50ms được tính vào TBT. Đây là metric phản ánh trực tiếp kích thước và độ phức tạp của JS bundle.

Tại sao quan trọng?

TBT cao = trang cảm giác "đơ" khi đang load. Lighthouse dùng TBT thay cho FID trong lab testing vì FID cần user thực tế tương tác. TBT chiếm 25% điểm Lighthouse Performance.

Tối ưu trong dự án

  • CSR bundle lớn hơn vì inline Navbar, Slider, Footer, BlogGrid trực tiếp vào file
  • SSG/ISR/SSR import components riêng → Next.js code split thành chunks nhỏ load song song
  • Tree shaking: chỉ import function cần dùng từ postUtils, không import toàn bộ
  • Dùng Chrome DevTools Performance tab để xem long tasks màu đỏ trên main thread timeline

So sánh theo rendering mode

SSG
~40ms
ISR
~40ms
SSR
~55ms
CSR
~380ms
Cách đo trong dự án này
📊

RenderBenchmark Component

Component tự build, đo real-time trực tiếp trên browser bằng PerformanceObserver API. Hiển thị FCP, LCP, TBT, TTFB, Hydration time ngay trên mỗi trang rendering.

const lcpObs = new PerformanceObserver(list => {
  const last = list.getEntries().at(-1);
  setMetrics(prev => ({
    ...prev, lcp: last.startTime
  }));
});
lcpObs.observe({
  type: 'largest-contentful-paint',
  buffered: true
});
🔬

Chrome DevTools

Lighthouse tab trong Chrome DevTools chạy audit đầy đủ. Performance tab cho phép xem timeline chi tiết: long tasks (TBT), paint events (FCP/LCP), layout shifts (CLS).

// Mở Chrome DevTools
// Tab: Lighthouse
// Device: Mobile (khắt khe hơn)
// Categories: Performance
// → Generate Report

// Tab: Performance
// Record khi load trang
// Xem: Long Tasks (đỏ)
//       Paint events
//       Layout shifts
⏱️

Navigation Timing API

Browser API built-in để đo TTFB và các timing khác. RenderBenchmark dùng API này để lấy responseStart - requestStart = TTFB thực tế của trang hiện tại.

const navEntry = performance
  .getEntriesByType('navigation')[0];

const ttfb =
  navEntry.responseStart -
  navEntry.requestStart;

// Long Tasks → TBT
const tbtObs = new PerformanceObserver(
  list => {
    list.getEntries().forEach(entry => {
      tbt += entry.duration - 50;
    });
  }
);
tbtObs.observe({ type: 'longtask' });