Tối ưu performance web 2026: Giảm load time từ 3s xuống 0.5s với CDN, caching và compression

2 tháng trước · 8 phút đọc
Bài toán thực tế: website 3 giây load time
Mình từng nhận một task tưởng chừng đơn giản: "tối ưu performance cho site bán hàng". Mở DevTools ra, Lighthouse chạy - điểm Performance: 34/100. Load time trên mobile: 3.2 giây. Bounce rate? Từ 1 giây lên 3 giây, bounce rate tăng 32%; lên 5 giây tăng 90%; lên 10 giây tăng 123% (Google / SOASTA (Google Think Insights)).
Thực ra con số đó không quá bất ngờ. Vấn đề là site này dùng shared hosting, ảnh không nén, JS bundle 2.4MB chưa split, không có CDN. Mọi thứ sai từ nền tảng.
Bài này mình sẽ walk-through qua đúng những gì mình đã làm - từ audit ban đầu đến khi load time còn 0.5s. Không lý thuyết suông, có số liệu thật và code cụ thể.
Lighthouse 34 → 96 sau khi áp dụng đủ stack - không phải phép màu, chỉ là làm đúng thứ tự
Tools cần có trước khi làm gì
Trước khi optimize, phải biết mình đang ở đâu. Ba tools mình dùng thường xuyên:
- Lighthouse (Chrome DevTools): baseline nhanh, đủ cho local audit
- WebPageTest (WebPageTest): test từ nhiều location, nhiều device thật
- GTmetrix (GTmetrix): waterfall chart đẹp, dễ đọc cho client
Chạy cả ba trên cùng một URL, lấy trung bình. Đừng chạy một lần rồi kết luận vì network noise ảnh hưởng kết quả đáng kể.
Compression: Brotli thay vì Gzip
Nếu server của bạn vẫn đang dùng Gzip, đây là thứ nên đổi ngay hôm nay.
Brotli (do Google phát triển) nén tốt hơn Gzip Brotli is typically 10-20% smaller than Gzip for web text assets, with some benchmarks showing 5-25% reduction depending on content and compression level (Web Performance Benchmarks 2024) với text-based assets (HTML, CSS, JS). Trên thực tế, file JS 2.4MB của mình sau Brotli còn 480KB - so với 620KB khi Gzip.
Brotli thường cho kết quả nhỏ hơn Gzip rõ rệt với file JS/CSS - đáng để đổi nếu server hỗ trợ
Config Nginx:
Kiểm tra server đã bật Brotli chưa:
Lưu ý: nếu đang dùng Cloudflare free plan, Brotli được bật mặc định cho static assets - không cần config thêm.
CDN: thứ tạo ra sự khác biệt lớn nhất
Mình đã từng nghĩ CDN chỉ dành cho big tech. Sai hoàn toàn.
Khi user ở Đà Nẵng truy cập site host tại Singapore, mỗi request phải đi vài trăm km. CDN giải quyết bằng cách cache và serve content từ edge server gần người dùng nhất. Kết quả? Latency giảm, server gốc ít tải hơn.
Không có CDN: mọi request đi thẳng về origin server. Có CDN: static assets serve từ node gần nhất
Với Cloudflare (free tier đủ dùng cho hầu hết project):
Một điểm mình hay quên: cache invalidation. Khi deploy code mới, assets cũ vẫn nằm trên CDN. Giải pháp chuẩn là content hashing:
Cloudflare Workers (Cloudflare Workers) là bước tiếp theo nếu bạn muốn custom logic tại edge - ví dụ A/B testing, geo-routing, hay transform response on-the-fly.
Browser caching và service workers
CDN cache ở edge. Browser cache ở máy người dùng. Hai lớp này hoạt động độc lập - tối ưu được cả hai mới đúng.
Mình phân loại cache theo loại content:
| Loại content | Cache strategy | Ví dụ |
|---|---|---|
| JS/CSS với hash | max-age=31536000, immutable |
main.a3f7.js |
| Ảnh tĩnh | max-age=604800 (7 ngày) |
logo.png |
| Font | max-age=31536000, immutable |
inter.woff2 |
| HTML | no-cache |
index.html |
| API responses | max-age=60 hoặc no-store |
/api/products |
Service Worker là lớp cache thứ ba - chạy trong browser, intercept network requests, và quyết định serve từ cache hay fetch từ network.
Service Worker đứng giữa browser và network - cache hit = 0ms latency
Workbox (Workbox) từ Google làm việc này tốt hơn nhiều so với viết tay - có sẵn các strategies như StaleWhileRevalidate, NetworkFirst, và tự handle versioning.
Lazy loading và code splitting
Hai kỹ thuật này tấn công cùng một vấn đề: đừng tải thứ người dùng chưa cần.
Lazy loading ảnh
Native lazy loading đã supported 95-96% global browser support for native lazy loading img attribute in 2024-2025 (Can I Use) browser hiện đại - không cần thư viện:
Với ảnh hero (above the fold), đừng lazy load - dùng loading="eager" hoặc bỏ attribute đó để browser ưu tiên tải trước.
Code splitting trong React
Code splitting chia bundle thành nhiều chunk nhỏ - initial load chỉ tải chunk cần thiết
Sau khi áp dụng code splitting cho site mình đề cập ở đầu bài: initial JS bundle từ 2.4MB xuống 340KB. Đây là improvement lớn nhất trong cả pipeline.
Nếu bạn đang học React và muốn hiểu rõ hơn về cách React hoạt động bên dưới, khóa Xây Dựng Website với ReactJS cover khá chi tiết các patterns như lazy loading, Suspense, và tối ưu re-render.
Image optimization: thứ mọi người hay bỏ qua nhất
JS bundle 2.4MB nghe to, nhưng ảnh chưa nén trên nhiều site còn tệ hơn. Một ảnh JPEG chụp từ điện thoại dễ đạt 5-8MB nếu không xử lý.
WebP và AVIF là hai format mình recommend:
- WebP: nhỏ hơn JPEG 25%-34% smaller than JPEG at the same SSIM visual similarity level (Google WebP Compression Study), support rộng hơn AVIF
- AVIF: nhỏ hơn WebP thêm 20-30% nữa, nhưng encoding chậm hơn
AVIF > WebP > JPEG về kích thước - dùng picture element để không bỏ user cũ
Tự động hóa trong build pipeline với Sharp (Node.js):
Next.js có next/image component làm tất cả việc này tự động - resize, convert sang WebP/AVIF, lazy load, và generate srcset cho responsive. Nếu bạn đang dùng Next.js, không có lý do gì để không dùng nó.
