DOMContentLoaded (DCL)
Sự kiện xảy ra khi cây DOM đã được phân tích xong và sẵn sàng thao tác, không chờ CSS, hình ảnh hoặc script bên ngoài.
DOMContentLoaded (DCL) là gì?
DOMContentLoaded (DCL) là sự kiện JavaScript được kích hoạt ngay khi trình duyệt đã hoàn tất việc phân tích HTML và xây dựng cây DOM đầy đủ — nghĩa là toàn bộ mã HTML trên trang đã được đọc, xử lý và chuyển thành cấu trúc cây có thể thao tác bằng JavaScript. Sự kiện này không chờ các tài nguyên bên ngoài như CSS, hình ảnh, font, script không chặn hiển thị (async/defer), hay iframe.
Đây là thời điểm đầu tiên mà JavaScript có thể an toàn truy cập và thay đổi phần tử trong DOM — ví dụ: chọn thẻ <div id="header">, thêm lớp CSS, hoặc gắn sự kiện click — mà không gặp lỗi Cannot read property of null.
Tại sao quan trọng trong SEO?
Google và các công cụ tìm kiếm hiện đại đánh giá trải nghiệm người dùng (UX) như một yếu tố xếp hạng trực tiếp. Một trong những chỉ số UX then chốt là thời gian tương tác đầu tiên (First Input Delay – FID) và thời gian tải nội dung chính (Largest Contentful Paint – LCP). DCL liên quan mật thiết đến cả hai:
- Khi DCL xảy ra sớm, JavaScript có thể bắt đầu khởi tạo giao diện, xử lý form, hoặc bật tính năng tương tác — giúp giảm độ trễ phản hồi với người dùng.
- Nếu DCL bị trì hoãn do script chặn (render-blocking scripts), trang sẽ chậm tương tác, làm tăng tỷ lệ thoát và giảm thời gian ở lại — cả hai đều là tín hiệu tiêu cực với thuật toán xếp hạng.
- Googlebot cũng sử dụng cơ chế render gần giống trình duyệt thật. Nếu DCL quá muộn, bot có thể không kịp chạy hết logic JavaScript cần để hiển thị nội dung động (ví dụ: nội dung tải qua AJAX hoặc component React/Vue), dẫn đến nội dung bị thiếu trong lập chỉ mục.
Cách hoạt động
Quá trình diễn ra theo thứ tự sau:
- Trình duyệt tải file HTML từ máy chủ.
- Nó phân tích tuần tự từng ký tự HTML, tạo DOM node và nối chúng thành cây DOM.
- Khi gặp thẻ
<script>không có thuộc tínhasynchoặcdefer, trình duyệt tạm dừng phân tích HTML để tải, biên dịch và thực thi script — gây trì hoãn DCL. - Khi toàn bộ HTML được phân tích xong và tất cả script chặn đã thực thi xong, sự kiện
DOMContentLoadedđược phát đi. - Lưu ý: CSS không chặn DCL về mặt kỹ thuật, nhưng nếu có
<link rel="stylesheet">trước<script>, trình duyệt thường đợi CSSOM sẵn sàng trước khi thực thi script — vì script có thể hỏi computed styles — nên CSS gián tiếp làm chậm DCL.
Hướng dẫn thực hiện
Để tối ưu DCL, bạn cần đảm bảo DOM được xây dựng nhanh nhất có thể và tránh mọi thứ làm chậm quá trình đó:
- Loại bỏ script chặn hiển thị: Di chuyển script vào cuối
<body>, hoặc thêm thuộc tínhdefercho script cần chạy sau khi DOM sẵn sàng, hoặcasynccho script độc lập (như analytics). - Tối ưu CSS: Tránh CSS quá lớn trong
<head>. Đưa CSS quan trọng (critical CSS) vào<style>nội tuyến; phần còn lại tải bất đồng bộ bằngmedia="print"+ đổimedia="all"sau khi DCL. - Giảm HTML thừa: Loại bỏ comment, khoảng trắng không cần thiết, thẻ lồng không hợp lý — giúp trình duyệt phân tích nhanh hơn.
- Sử dụng
document.readyState: Kiểm tra trạng thái trước khi gắn sự kiện:if (document.readyState === 'loading') { document.addEventListener('DOMContentLoaded', fn); } else { fn(); }. - Không dùng
window.onloadthay thế: Vìonloadchờ tất cả tài nguyên (hình ảnh, CSS, iframe), nên luôn xảy ra muộn hơn DCL — không phù hợp cho khởi tạo giao diện.
Lỗi thường gặp
| Lỗi | Dấu hiệu | Cách khắc phục |
|---|---|---|
| Thực thi script trước khi DOM sẵn sàng | Lỗi querySelector(...) is null, giao diện không hiển thị đúng |
Dùng addEventListener('DOMContentLoaded', ...) hoặc đặt script dưới phần HTML cần thao tác |
| Script chặn trong <head> không cần thiết | DCL chậm > 1s trên mạng 3G, LCP bị ảnh hưởng | Chuyển sang defer; kiểm tra bằng LightHouse mục "Eliminate render-blocking resources" |
| Gắn nhiều lần sự kiện DCL | Script chạy 2–3 lần, gây lỗi trùng lặp hoặc hiệu ứng nhảy | Dùng once: true trong addEventListener, hoặc kiểm tra biến cờ (flag) trước khi chạy |
Ví dụ thực tế
Dưới đây là cách viết đúng và sai khi khởi tạo menu điều hướng:
Sai:<script>document.getElementById('menu').addEventListener('click', toggleMenu);</script>
(Đặt trong <head> → DOM chưa tồn tại → lỗi)
Đúng — cách 1 (gắn sự kiện DCL):
document.addEventListener('DOMContentLoaded', function() {
const menu = document.getElementById('menu');
if (menu) menu.addEventListener('click', toggleMenu);
});
Đúng — cách 2 (dùng defer):
<script src="menu.js" defer></script>
Trong menu.js, không cần bọc DCL — vì defer đảm bảo script chỉ chạy sau khi DOM sẵn sàng.
Câu hỏi thường gặp
DCL có khác gì với window.onload?
Có. DOMContentLoaded chỉ chờ DOM xong; window.onload chờ tất cả tài nguyên (hình ảnh, CSS, iframe, script). Vì vậy, DCL luôn xảy ra sớm hơn — thường nhanh gấp 2–5 lần so với onload trên trang có nhiều ảnh.
Có nên dùng jQuery.ready() thay cho DCL?
jQuery $(document).ready() bản chất là bao bọc DOMContentLoaded với khả năng tương thích ngược. Trên dự án hiện đại (không hỗ trợ IE8 trở xuống), nên dùng DCL thuần để giảm phụ thuộc và tăng tốc độ khởi chạy. jQuery.ready() vẫn hoạt động nhưng không còn cần thiết.
DCL có ảnh hưởng đến Core Web Vitals không?
DCL không phải chỉ số Core Web Vitals, nhưng nó ảnh hưởng gián tiếp: DCL muộn thường kéo theo FID cao và LCP chậm — do script chặn làm chậm render và tương tác. Tối ưu DCL là bước nền tảng để đạt điểm CWV tốt. Thời gian DCL mục tiêu nên dưới 500ms trên mạng 3G (tùy trường hợp).