11 ngày sửa nhầm chỗ vì tin một cái nhãn sai

Đồ hoạ: Lighthouse gán nhãn LCP là TEXT trong khi thật ra là ảnh nền CSS của div hero

Bài viết do Nguyễn Nhật Ánh Dương thực hiện, biên soạn theo Chính sách nội dung của Marketing365. Cập nhật lần cuối .

Nội dung
  1. Cái nhãn nói gì và tôi hiểu ra sao
  2. Cái làm tôi phải đo lại
  3. Chuỗi ứng viên nói ra chuyện khác hẳn
  4. Sửa lại: bảo trước cho trình duyệt biết
  5. Nghiệm thu bằng thứ không bị nhiễu
  6. Thứ tôi mang đi khỏi vụ này

Ngày 20/07/2026 tôi chạy đo tốc độ trang chủ ở chế độ điện thoại. Công cụ chỉ đúng phần tử làm trang chậm nhất, kèm một cái nhãn: TEXT. Tôi đọc nhãn đó, kết luận rằng phần tử chậm nhất là chữ, và gạch luôn khỏi danh sách mọi việc liên quan đến ảnh — nén ảnh, đổi định dạng, tải ảnh sớm. Ảnh không liên quan mà, công cụ vừa nói vậy.

Mười một ngày sau tôi đo lại bằng tay. Phần tử đó được tính vào LCP không phải vì chữ, mà vì một tấm ảnh nền khai trong CSS. Cái tôi gạch đi ngay từ đầu chính là cách sửa hiệu quả nhất.

Cái nhãn nói gì và tôi hiểu ra sao

LCP là chỉ số đo xem phần tử lớn nhất trong màn hình đầu tiên mất bao lâu mới hiện ra. Công cụ đo chỉ ra phần tử ấy là <div class="mna-hero"> và gán nhãn loại phần tử là TEXT.

Suy luận của tôi lúc đó, viết ra thì thấy ngay chỗ hở: phần tử này là chữ, nên muốn nó hiện sớm thì phải làm font tải nhanh hơn và bỏ bớt CSS chặn đường vẽ. Ảnh hero nằm ngay sau lưng phần tử đó — nhưng công cụ bảo LCP là TEXT, nên tối ưu ảnh hero là phí công.

Tôi ghi hẳn kết luận ấy vào sổ tay kỹ thuật: “preload ảnh hero, bỏ lazy, đổi sang AVIF — vô dụng cho LCP”. Câu đó nằm đó 11 ngày và định hướng toàn bộ việc tôi làm sau đó.

Cái làm tôi phải đo lại

Điều không khớp là bảng phân rã 7,8 giây LCP. Nó tách thành bốn khúc, và ba khúc đầu đều nhỏ:

  • Chờ máy chủ trả byte đầu tiên: 624 ms
  • Chờ trình duyệt tìm ra cần tải cái gì: 692 ms
  • Tải cái đó về: 30 ms
  • Chờ vẽ ra màn hình: 6.496 ms

Khúc cuối chiếm 83% toàn bộ. Nếu phần tử này thật sự là chữ thì “chờ vẽ” 6,5 giây phải giải thích được bằng font hoặc bằng CSS chặn đường. Tôi bỏ bớt CSS chặn đường thật, và con số không nhúc nhích. Có một khúc 692 mili-giây mang tên “chờ tìm ra cần tải cái gì” — mà một khối chữ thì đâu cần tải gì.

Đáng lẽ tôi phải nghi từ sớm hơn. Trong 11 ngày đó tôi làm đúng những việc mà một phần tử chữ chậm sẽ cần: cắt bớt CSS của theme cho nhẹ đường vẽ, dựng lại phần CSS nạp sớm. Mỗi lần xong tôi đo lại và LCP đứng im. Tôi kết luận là mình cắt chưa đủ sâu, rồi quay lại cắt tiếp. Không lần nào tôi quay lại hỏi liệu cái nhãn ban đầu có đúng không.

Nên tôi bỏ công cụ chấm điểm, viết một đoạn script nhỏ chạy ngay trong trình duyệt để ghi lại toàn bộ chuỗi ứng viên LCP — không chỉ cái cuối cùng — kèm tên thẻ và kích thước từng cái.

Chuỗi ứng viên nói ra chuyện khác hẳn

Biểu đồ: h1 48.576 px² so với div hero 269.952 px², và LCP 7,8 giây với 6.496 ms là thời gian chờ vẽ
Chuỗi ứng viên LCP và phân rã 7,8 giây của trang chủ marketing365.vn ở chế độ điện thoại. Số ở phần trên đo lại bằng tay ngày 31/07/2026; phân rã LCP lấy từ lần đo 20/07/2026.

Có hai ứng viên, không phải một:

  • Ứng viên số 1: thẻ <h1>, diện tích 48.576 px²
  • Ứng viên số 2, và là cái được tính: div.mna-hero, diện tích 269.952 px² — gấp 5,5 lần

Cái <h1> lên ngôi trước, rồi bị khối hero soán chỗ. Công cụ chỉ báo cho tôi kẻ chiến thắng cuối cùng, và cái nhãn TEXT nó gán cho kẻ đó thì sai.

Sai ở đâu: div.mna-hero là một thẻ div, và nó được tính là “có nội dung” không phải vì chữ bên trong, mà vì có ảnh nền khai trong CSS bằng background: image-set(url(...)). Với trình duyệt thì đó là ảnh, đủ tư cách làm LCP. Với cái nhãn của công cụ thì nó là một thẻ div chứa chữ.

Đọc thêm: Mỗi ngày thêm 27 trang mới, Googlebot đọc 8

Và 692 mili-giây “chờ tìm ra” là dấu vết của chính tấm ảnh đó. Ảnh khai trong CSS thì trình duyệt không thấy nó lúc quét HTML. Trình tự bắt buộc là: đọc HTML, tải file CSS về, phân tích file CSS, khớp selector, rồi mới biết cần tải ảnh nào. Vài vòng đi lại trước khi request ảnh được gửi đi.

Sửa lại: bảo trước cho trình duyệt biết

Cách xử lý là khai báo tải sớm tấm ảnh nền ngay trong HTML, để trình duyệt gửi request đi mà không cần chờ đọc xong CSS. Trên trang chủ hiện tại có đúng hai dòng này:

<link rel="preload" as="image" type="image/webp"
      href=".../assets/hero-bg-mobile.webp"
      media="(max-width: 768px)" fetchpriority="high">
<link rel="preload" as="image" type="image/webp"
      href=".../assets/hero-bg.webp"
      media="(min-width: 769px)" fetchpriority="high">

Hai chi tiết trong đó tôi phải trả giá mới biết:

  1. Bắt buộc có media. Thiếu nó thì máy nào cũng tải cả hai tấm ảnh, và trang chậm hơn lúc chưa sửa. Khai báo tải sớm mà tải sớm nhầm thứ thì tệ hơn là không khai.
  2. Đường dẫn phải trùng từng ký tự với đường dẫn trong CSS. Thêm một cái ?ver= vào cuối là trình duyệt coi đó là tấm ảnh khác và tải lần thứ hai.

Nhân tiện, tôi cắt luôn một tấm ảnh riêng cho điện thoại. Bản đang dùng là 1536×1024, trong khi khung thật trên máy chỉ 721 điểm ảnh — to gấp khoảng 4,5 lần chỗ cần. Bản mới 800×533 nặng 18.004 byte thay cho 108.714 byte, nhẹ hơn 83%.

Nghiệm thu bằng thứ không bị nhiễu

Máy chủ này chạy nhiều việc cùng lúc nên số mili-giây tuyệt đối đo trên đó nhảy loạn giữa các lần. Vì vậy tôi không lấy “LCP giảm bao nhiêu giây” làm bằng chứng, mà lấy hai thứ mang tính quan hệ:

  • Chuỗi ứng viên LCP: 2 còn 1. Khối hero giờ hiện ra cùng lúc với chữ, không còn cảnh chữ lên trước rồi ảnh nhảy vào sau.
  • Khoảng cách LCP − FCP: từ +720 ms về 0 ms. Tức phần tử lớn nhất hiện ra ngay tại thời điểm trang vẽ nét đầu tiên. Máy đo khoẻ hay yếu thì con số này vẫn là 0.

Tôi cũng phải chắc là không đánh đổi giao diện lấy tốc độ. Chụp lại trang trước và sau rồi so từng điểm ảnh: lệch 0,019% số điểm ảnh, chỗ lệch nhiều nhất chênh 12 trên thang 255 — mắt thường không thấy. Và log lại request ở cả hai kích thước màn hình để chắc rằng điện thoại chỉ tải bản điện thoại, máy tính chỉ tải bản máy tính, không máy nào tải trùng.

Thứ tôi mang đi khỏi vụ này

Nhãn loại phần tử mà công cụ đo đưa ra là một phỏng đoán, không phải kết luận. Với thẻ <img> thì nó chắc chắn đúng. Với một thẻ <div> thì phải hỏi thêm một câu: thẻ này được tính là có nội dung nhờ chữ bên trong, hay nhờ ảnh nền khai trong CSS? Hai trường hợp đó cần hai cách sửa hoàn toàn khác nhau.

Và đừng chỉ nhìn phần tử LCP cuối cùng. Ghi lại cả chuỗi ứng viên, kèm kích thước. Nếu tôi làm điều đó ngay từ ngày đầu, tôi đã thấy có hai ứng viên và cái lớn hơn gấp 5,5 lần là một khối hình — hết 11 ngày.

Còn một dấu hiệu nữa mà giờ nhìn lại thì rõ mồn một: khúc “chờ tìm ra cần tải cái gì” dài 692 mili-giây. Khúc đó chỉ tồn tại khi có một tài nguyên phải đi tải về. Một khối chữ thuần thì khúc đó bằng 0. Bảng phân rã đã nói thẳng rằng phần tử này cần tải một tấm ảnh, ngay bên dưới cái nhãn ghi là chữ. Tôi đọc cái nhãn, và bỏ qua bảng số.

Chuyện tin nhầm chỉ số rồi đi sai đường không phải lần duy nhất ở site này. Có lần tôi đọc sai theo hướng ngược lại: tối ưu CSS làm điểm tốc độ tụt từ 38 xuống 14 — công cụ trả lời rất chính xác cho một câu hỏi hơi khác câu tôi đang cần hỏi.

Có thể bạn thích

Để lại bình luận