Cách tối ưu hiệu năng ứng dụng web: Performance Optimization toàn diện
Tối ưu hiệu năng ứng dụng web là quá trình đo xem người dùng đang chờ ở đâu rồi cắt giảm đúng chỗ đó: thời gian máy chủ trả phản hồi, dung lượng tài nguyên phải tải, lượng JavaScript trình duyệt phải chạy và số lần tính toán lại giao diện. Nguyên tắc quan trọng nhất là đo trước, sửa sau; tối ưu theo cảm tính thường tốn công vào chỗ không đáng. Bài viết đi theo đúng đường đi của một request, từ trình duyệt về tới database.
Đo lường: Core Web Vitals và các chỉ số máy chủ
Google đưa ra bộ Core Web Vitals để đo trải nghiệm thực tế:
| Chỉ số | Đo cái gì | Ngưỡng "tốt" theo Google |
|---|---|---|
| LCP (Largest Contentful Paint) | Thời gian phần nội dung lớn nhất hiện ra | Từ 2,5 giây trở xuống |
| INP (Interaction to Next Paint) | Độ trễ từ khi người dùng tương tác đến khi giao diện phản hồi | Từ 200 ms trở xuống |
| CLS (Cumulative Layout Shift) | Mức độ bố cục bị xô lệch khi tải | Từ 0,1 trở xuống |
INP đã thay thế FID trong bộ chỉ số từ tháng 3 năm 2024. Ngoài ra nên theo dõi TTFB (thời gian đến byte đầu tiên) để tách vấn đề máy chủ khỏi vấn đề frontend.
Có hai loại dữ liệu cần kết hợp:
- Dữ liệu phòng thí nghiệm: Lighthouse, PageSpeed Insights, WebPageTest. Dễ tái hiện, hợp để so sánh trước và sau khi sửa.
- Dữ liệu người dùng thật (RUM): thư viện web-vitals gửi số đo về hệ thống phân tích, hoặc báo cáo Chrome UX Report. Phản ánh đúng thiết bị và mạng của khách hàng, đặc biệt điện thoại tầm trung với mạng di động.
Ở phía backend, đo phân vị độ trễ (p50, p95, p99) của từng endpoint chứ không chỉ trung bình; hệ thống monitoring giám sát ứng dụng production là nơi tập hợp các số đo này.
Giảm thời gian tải trang đầu tiên
Hình ảnh
Ảnh thường chiếm phần lớn dung lượng trang. Các bước cơ bản:
- Dùng định dạng WebP hoặc AVIF, giữ JPEG/PNG làm dự phòng nếu cần.
- Phục vụ nhiều kích thước bằng thuộc tính srcset và sizes để điện thoại không tải ảnh dành cho màn hình lớn.
- Khai báo width và height (hoặc aspect-ratio) để tránh xô lệch bố cục, giảm CLS.
- Lazy load ảnh dưới màn hình đầu tiên bằng loading="lazy", nhưng không lazy load ảnh LCP; ảnh đó nên được ưu tiên bằng fetchpriority="high".
JavaScript và CSS
- Chia nhỏ bundle theo route (code splitting), chỉ tải code của trang đang xem.
- Rà soát thư viện nặng bằng công cụ phân tích bundle; thay thư viện lớn bằng hàm nhỏ khi chỉ dùng một tính năng.
- Dùng defer hoặc type="module" cho script, tránh chặn hiển thị.
- Tách CSS quan trọng cho màn hình đầu, tải phần còn lại sau.
- Kiểm soát script bên thứ ba (chat, quảng cáo, theo dõi); chúng thường là nguyên nhân chính làm INP kém.
Lựa chọn framework và cách render (SSR, SSG, hydrate từng phần) ảnh hưởng lớn đến lượng JavaScript phải tải. Với website nội dung, kiến trúc headless kết hợp trang tĩnh, như phân tích trong bài so sánh nền tảng CMS cho doanh nghiệp, thường cho tốc độ tốt mà không cần nhiều công tối ưu.
Phông chữ
Tự host font, chỉ tải các trọng lượng dùng thật, dùng font-display: swap và cân nhắc tách bộ ký tự. Font tiếng Việt cần đủ dấu nên đừng cắt subset quá tay.
Cache và CDN
Không request nào nhanh bằng request không phải gửi. Chiến lược cache theo lớp:
- Trình duyệt: tài nguyên tĩnh có tên chứa hash (app.3f9a.js) đặt Cache-Control dài hạn với immutable; HTML đặt thời gian ngắn hoặc no-cache kèm ETag.
- CDN: phân phối tài nguyên tĩnh và có thể cache cả HTML công khai tại điểm gần người dùng. Với khách tại Việt Nam, chọn CDN có điểm hiện diện trong nước hoặc gần khu vực giúp giảm độ trễ rõ rệt.
- Ứng dụng: cache kết quả tính toán tốn kém, dữ liệu ít đổi (danh mục, cấu hình) trong Redis hoặc bộ nhớ, có chiến lược vô hiệu hóa rõ ràng.
- Database: buffer cache của chính database; đảm bảo RAM đủ chứa phần dữ liệu "nóng".
Bật nén Brotli hoặc gzip cho văn bản và dùng HTTP/2 hoặc HTTP/3 để tải song song hiệu quả hơn.
Tăng tốc API và backend
Khi TTFB cao hoặc màn hình phải chờ API lâu, hãy dùng distributed tracing để biết thời gian đi đâu. Các nguyên nhân hay gặp:
- Truy vấn N+1: lấy danh sách 50 đơn hàng rồi truy vấn khách hàng cho từng đơn. Sửa bằng join hoặc nạp theo lô.
- Thiếu chỉ mục: kiểm tra truy vấn chậm bằng EXPLAIN; nguyên tắc chọn chỉ mục được trình bày trong bài thiết kế database schema chuẩn.
- Gọi tuần tự nhiều service: gọi song song những lời gọi không phụ thuộc nhau, đặt timeout hợp lý.
- Trả về quá nhiều dữ liệu: phân trang, chỉ chọn trường cần thiết, tránh SELECT *.
- Làm việc chậm ngay trong request: gửi email, tạo báo cáo, gọi đối tác nên đẩy vào hàng đợi xử lý nền.
- Connection pool quá nhỏ hoặc quá lớn: pool nhỏ khiến request xếp hàng, pool lớn làm quá tải database.
Hiệu năng khi tương tác
Trang tải nhanh nhưng bấm nút phải chờ vẫn là trải nghiệm kém. Để cải thiện INP:
- Chia nhỏ tác vụ JavaScript dài, nhường luồng chính cho trình duyệt xử lý input.
- Giảm số lần render lại không cần thiết trong React, Vue (memo hóa, tách state).
- Ảo hóa danh sách dài (virtual list) thay vì render hàng nghìn dòng.
- Hiển thị phản hồi tức thì (trạng thái đang xử lý, optimistic update) trong khi chờ API.
Giữ hiệu năng không bị thoái lui
Tối ưu một lần rồi để đó thì vài tháng sau trang sẽ chậm lại vì thêm thư viện, thêm script marketing. Vài cơ chế phòng ngừa:
- Đặt ngân sách hiệu năng (performance budget): kích thước JavaScript tối đa mỗi trang, LCP mục tiêu.
- Chạy Lighthouse CI hoặc kiểm tra kích thước bundle trong pipeline, cảnh báo khi vượt ngân sách.
- Theo dõi dữ liệu RUM theo từng bản release để phát hiện bản làm chậm.
- Chạy kiểm thử tải định kỳ cho API quan trọng trước các đợt khuyến mãi lớn.
Thứ tự ưu tiên khi thời gian có hạn
Nếu chỉ có một tuần, hãy làm theo thứ tự thường mang lại hiệu quả lớn nhất: tối ưu ảnh và ảnh LCP; bật nén, cache dài hạn và CDN cho tài nguyên tĩnh; loại bỏ hoặc trì hoãn script bên thứ ba không thiết yếu; sửa ba endpoint chậm nhất theo dữ liệu tracing. Sau đó mới đến các tối ưu tinh vi hơn như tái cấu trúc bundle hay đổi chiến lược render.
Câu hỏi thường gặp
Điểm Lighthouse 100 có nghĩa là trang đã nhanh với mọi người dùng không?
Không. Lighthouse chạy trong điều kiện giả lập cố định. Người dùng thật có thiết bị, mạng và hành vi khác nhau, nên cần dữ liệu RUM để biết trải nghiệm thực tế.
Có nên dùng service worker để tăng tốc không?
Service worker giúp cache tài nguyên và cho phép dùng ngoại tuyến, hữu ích cho ứng dụng dùng lặp lại nhiều lần. Tuy nhiên chiến lược cache sai có thể khiến người dùng kẹt ở phiên bản cũ, nên cần cơ chế cập nhật rõ ràng.
Hiệu năng có ảnh hưởng tới SEO không?
Core Web Vitals là một trong các tín hiệu trải nghiệm trang mà Google sử dụng, nhưng nội dung phù hợp vẫn quan trọng hơn. Trang nhanh chủ yếu giúp giảm tỷ lệ thoát và tăng chuyển đổi.
Nên tối ưu frontend hay backend trước?
Xem TTFB trước. Nếu TTFB chiếm phần lớn thời gian tải thì ưu tiên backend; nếu TTFB thấp mà LCP vẫn cao thì vấn đề nằm ở tài nguyên và render phía trình duyệt.
Bài liên quan
Hướng dẫn viết Unit Test và Integration Test đảm bảo chất lượng code
Cách viết unit test và integration test có giá trị: chọn phạm vi, đặt tên, dùng mock đúng chỗ, dựng database thử, đưa test vào CI và tránh test giòn.

So sánh Agile, Scrum và Kanban: chọn cách làm nào cho đội phần mềm
Agile là triết lý, Scrum và Kanban là hai cách thực hành. Bài so sánh vai trò, nhịp làm việc, chỉ số đo lường và gợi ý chọn mô hình theo loại dự án.

So sánh message queue: RabbitMQ, Kafka và Redis Pub/Sub
RabbitMQ, Kafka hay Redis Pub/Sub? So sánh mô hình lưu trữ, đảm bảo giao nhận, thứ tự tin nhắn, vận hành và tình huống nên dùng từng giải pháp.
So sánh nền tảng CMS WordPress, Strapi, Directus cho doanh nghiệp
WordPress, Strapi hay Directus? So sánh kiến trúc, đối tượng biên tập, khả năng mở rộng, bảo mật, chi phí vận hành để doanh nghiệp chọn CMS phù hợp.