Web Performance

Main Thread Work

Khối lượng công việc (JS execution, layout, paint) diễn ra trên luồng chính của trình duyệt, ảnh hưởng trực tiếp đến độ mượt và phản hồi.

3 lượt xem Cập nhật: 26/05/2026

Main Thread Work là gì?

"Main Thread Work" (công việc trên luồng chính) là toàn bộ khối lượng xử lý do JavaScript, layout (bố cục), paint (vẽ) và composite (kết hợp lớp) thực hiện trên luồng chính (main thread) của trình duyệt. Đây là luồng duy nhất chịu trách nhiệm xử lý sự kiện người dùng, chạy script, tính toán vị trí và kích thước các phần tử (layout), vẽ pixel lên màn hình (paint), và phối hợp các lớp hiển thị (composite). Khi luồng chính bận rộn quá mức hoặc bị chặn, trang web trở nên chậm, giật, không phản hồi — đặc biệt rõ khi cuộn, nhấn nút hay nhập liệu.

Tại sao quan trọng trong SEO?

Main Thread Work ảnh hưởng trực tiếp đến các chỉ số trải nghiệm người dùng (Core Web Vitals), vốn là yếu tố xếp hạng chính thức của Google từ năm 2021. Cụ thể:

  • First Input Delay (FID)Interaction to Next Paint (INP) đo độ trễ phản hồi — cả hai đều phụ thuộc vào thời gian luồng chính rảnh để xử lý sự kiện.
  • Largest Contentful Paint (LCP) có thể bị kéo dài nếu script nặng chặn layout/paint.
  • Trang chậm → tỷ lệ thoát cao → tín hiệu tiêu cực gửi về Google → giảm khả năng xuất hiện trong kết quả tìm kiếm.

Theo báo cáo của HTTP Archive (2024), hơn 42% trang desktop và 63% trang mobile có thời gian Main Thread Work trên 50ms mỗi khung hình — vượt ngưỡng mượt (16ms/khung cho 60fps). Điều này làm tăng xác suất vi phạm INP & FID.

Cách hoạt động

Trình duyệt chạy trên một luồng chính duy nhất (trừ khi dùng Web Workers). Mỗi khung hình (frame) cần hoàn tất 4 giai đoạn: JavaScript → Style → Layout → Paint → Composite. Nếu bất kỳ bước nào kéo dài >16ms, khung hình bị bỏ lỡ (jank), gây hiện tượng giật lag. Các tác vụ như setTimeout, event listener, hoặc render-triggering property (ví dụ: offsetTop, getBoundingClientRect()) đều chạy trên luồng chính và có thể gây chặn.

Web Workers và Service Workers chạy ngoài luồng chính, nhưng không thể truy cập DOM — nên không thay thế được việc tối ưu Main Thread Work mà chỉ hỗ trợ xử lý nền.

Hướng dẫn thực hiện

Dưới đây là các bước kiểm tra và tối ưu Main Thread Work theo thứ tự ưu tiên:

  1. Đo lường bằng công cụ chính xác: Dùng Chrome DevTools > tab Performance, ghi lại tương tác (cuộn, click), sau đó xem phần "Main" trong timeline. Tập trung vào các khối màu xanh (JS), tím (layout), cam (paint).
  2. Phân tách và hoãn tải script: Dùng async hoặc defer cho script không cần thiết ngay lập tức. Tránh document.write() — nó buộc trình duyệt phải dừng parsing và reset layout.
  3. Giảm khối lượng JS thực thi: Loại bỏ code chết (dead code), dùng tree-shaking, chuyển logic nặng sang Web Worker (ví dụ: xử lý dữ liệu lớn, mã hóa).
  4. Tối ưu layout & paint: Tránh trigger layout thrashing (gọi liên tiếp các thuộc tính buộc tính lại bố cục). Gộp các truy vấn DOM, dùng transformopacity thay vì top/left/width/height để tận dụng GPU compositing.
  5. Giới hạn tần suất cập nhật UI: Dùng requestIdleCallback() cho tác vụ không khẩn cấp; dùng throttle hoặc debounce cho event như resize, scroll.

Lỗi thường gặp

Lỗi Dấu hiệu trong DevTools Cách khắc phục
Layout thrashing Nhiều khối layout (tím) xuất hiện liên tiếp trong một frame Gộp đọc/ghi DOM: đọc hết thuộc tính trước, sau đó ghi toàn bộ cùng lúc.
Long tasks (>50ms) Khối JS (xanh) rộng >50ms, chiếm gần hết khung hình Chia nhỏ task bằng setTimeout hoặc queueMicrotask; chuyển sang Web Worker nếu có thể.
Forced synchronous layout Thông báo "Layout was forced" trong console hoặc timeline Loại bỏ các thuộc tính đọc layout (như offsetHeight) ngay sau thay đổi style.

Ví dụ thực tế

Một trang tin tức sử dụng thư viện carousel tự động cuộn. Mỗi lần đổi slide, script gọi element.offsetHeight rồi cập nhật marginLeft — gây layout thrashing. Sau khi tối ưu: nhóm toàn bộ phép đọc vào một lần, dùng transform: translateX() thay vì marginLeft, và hoãn cập nhật trạng thái bằng requestIdleCallback. Kết quả: thời gian Main Thread Work trung bình mỗi frame giảm từ 48ms xuống còn 9ms, INP cải thiện từ 320ms → 42ms — đạt mức "tốt" theo tiêu chuẩn Google.

Một ví dụ khác: trang sản phẩm có form lọc với 200 checkbox. Khi người dùng tích chọn, script chạy vòng lặp qua toàn bộ và cập nhật DOM — gây long task 210ms. Giải pháp: dùng virtualization (chỉ render checkbox đang trong viewport), cập nhật state bằng setState batch (React 18+), và dùng IntersectionObserver để tải lazy.

Câu hỏi thường gặp

Main Thread Work có giống JavaScript execution không?

Không hoàn toàn. Main Thread Work bao gồm cả JavaScript execution, nhưng còn gồm layout, paint và composite. Một trang có thể có ít JS nhưng vẫn chậm nếu layout/paint quá nặng — ví dụ do CSS phức tạp hoặc nhiều element được repaint liên tục.

Có thể loại bỏ hoàn toàn Main Thread Work không?

Không. Luồng chính là bắt buộc để tương tác với DOM và xử lý sự kiện. Tuy nhiên, có thể giảm thiểu đến mức không gây ảnh hưởng tới trải nghiệm — mục tiêu là giữ dưới 50ms mỗi task và dưới 16ms cho các tác vụ tương tác nhạy cảm.

Web Worker giúp giảm Main Thread Work không?

Có, nhưng chỉ với tác vụ không cần DOM. Ví dụ: xử lý JSON lớn, tính toán ma trận, nén ảnh — đều có thể dời sang Web Worker. Tuy nhiên, việc gửi dữ liệu qua postMessage có chi phí, nên không phù hợp với tác vụ nhỏ hoặc cần phản hồi tức thì.