Tối ưu CSS làm điểm tốc độ tụt từ 38 xuống 14

Đồ hoạ: inline 325.197 B CSS khiến điểm Lighthouse mobile tụt từ 38 xuống 14

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. Con số làm tôi tưởng mình đọc nhầm
  2. Vì sao tôi tin là mình đang làm đúng
  3. Chỗ sai: “có dùng” không đồng nghĩa với “cần để vẽ”
  4. Cái giá phải trả, tính bằng số
  5. Làm lại bằng công cụ đo đúng thứ cần đo
  6. Nếu bạn định làm việc này, kiểm ba thứ

Tối 19/07/2026 tôi làm một việc mà mọi bài hướng dẫn tối ưu tốc độ đều khuyên: gom CSS cần thiết rồi nhét thẳng vào thẻ <head>, để trình duyệt khỏi phải chờ tải file CSS rồi mới vẽ được gì lên màn hình. Kỹ thuật này có tên, có công cụ, có hàng trăm bài viết đứng sau. Tôi làm xong, chạy đo lại, và điểm tốc độ trên điện thoại tụt từ 38 xuống 14.

Bài này kể lại chỗ tôi sai, kèm số đo hai chiều: lúc hỏng và lúc làm lại cho đúng. Tất cả số trong bài đều lấy từ nhật ký đo của chính trang marketing365.vn, và phần lớn vẫn kiểm tra lại được ngay hôm nay vì file thí nghiệm hỏng còn nằm nguyên trên máy chủ.

Con số làm tôi tưởng mình đọc nhầm

Trước khi đụng vào, trang chủ chấm 38 điểm ở chế độ điện thoại. Chậm, nhưng chậm một cách ổn định: CLS bằng 0, tức bố cục không nhảy lung tung khi trang tải xong. Thủ phạm rõ ràng — có 1.847 mili-giây trình duyệt ngồi chờ file CSS về mới dám vẽ.

Sau khi tôi nhét CSS vào thẳng trang, đúng cái 1.847 mili-giây kia biến mất thật. Không còn file CSS nào chặn đường vẽ nữa. Nhưng ba con số khác đi theo hướng ngược lại:

  • Điểm tổng: 38 xuống 14
  • TBT, tức tổng thời gian trình duyệt bận đến mức không phản hồi khi bạn chạm màn hình: 1.582 lên 4.182 mili-giây — gấp 2,6 lần
  • CLS, tức mức bố cục xê dịch: 0 lên 0,724 — từ đứng yên tuyệt đối thành nhảy loạn

Tôi bỏ đi một chỗ nghẽn và tạo ra ba chỗ nghẽn to hơn. Ngưỡng “tốt” của CLS là dưới 0,1; 0,724 nghĩa là nội dung nhảy đi gần ba phần tư chiều cao khung nhìn trong lúc tải.

Vì sao tôi tin là mình đang làm đúng

Theme của site là Soledad, và file CSS chính của nó nặng 1.208.930 byte. Nén gzip xong vẫn còn 166.183 byte. Đây là một file rất lớn, và nó chặn đường vẽ — không tải xong thì trình duyệt không hiển thị gì cả. Chuyện cắt gọn chính file này tôi kể riêng ở bài cắt 80% file CSS của theme mà LCP không nhúc nhích, và kết quả ở đó cũng không như tôi tưởng.

Cách tôi chọn để rút gọn nghe rất hợp lý: mở trang bằng Chromium ở chế độ điều khiển tự động, bật tính năng đo độ phủ CSS, và hỏi trình duyệt “trang này thật ra dùng những rule nào”. Chromium trả về một tập rule. Tôi gom tập đó thành file used.min.css rồi nhét vào <head>.

Lập luận: nếu chỉ giữ phần CSS trang thật sự dùng thì phần bỏ đi là phần thừa, mà bỏ phần thừa thì chỉ có nhanh lên. Lập luận này sai ở một chỗ tôi không nhìn ra lúc đó.

Chỗ sai: “có dùng” không đồng nghĩa với “cần để vẽ”

Tính năng đo độ phủ của Chromium đánh dấu mọi rule CSS khớp với bất kỳ phần tử nào có trong trang. Bất kỳ phần tử nào — kể cả phần tử nằm ở cuối trang, kể cả footer, kể cả menu mobile đang ẩn, kể cả popup chưa ai bấm.

Còn thứ tôi cần lại hẹp hơn nhiều: chỉ những rule để vẽ đúng phần màn hình đầu tiên người dùng nhìn thấy. Phần còn lại nên để trình duyệt tải thong thả phía sau, không cần chặn đường.

Hai tập hợp đó không xấp xỉ nhau. Chúng lệch nhau gần 30 lần, và tôi có con số cụ thể ở phần sau.

Khi nhét 325.197 byte CSS vào thẳng trang, trình duyệt phải đọc và dựng toàn bộ chỗ đó trước khi vẽ được nét đầu tiên. Nó không còn phải chờ mạng, nhưng lại phải tính toán nhiều gấp bội — đó là 4.182 mili-giây TBT. Và vì mớ CSS đó chứa cả rule của những khối chưa tồn tại lúc tải, trang vẽ ra một trạng thái rồi tự sắp xếp lại khi nội dung thật đến — đó là CLS 0,724.

Cái giá phải trả, tính bằng số

Bảng so sánh ba trạng thái: trước khi sửa 38 điểm, inline 325.197 B còn 14 điểm, inline 11.327 B giảm FCP 440ms
Ba trạng thái của cùng một trang chủ. Cột 1 và 2 đo bằng Lighthouse chế độ điện thoại chạy trên máy chủ, ngày 19–20/07/2026. Cột 3 đo bằng cách bật–tắt trên cùng đường tải chưa cache, ngày 31/07/2026 — khác cách đo nên không đem so điểm với hai cột kia.

Đọc thêm: SEO là gì? Tối ưu công cụ tìm kiếm A-Z

Đây là chỗ tôi phải nói rõ một giới hạn của phép đo. Máy chủ này chạy nhiều thứ cùng lúc, nên chạy Lighthouse ngay trên đó cho ra số mili-giây nhiễu — hai lần đo liên tiếp có thể lệch nhau vài trăm mili-giây mà chẳng vì lý do gì. Ba con số ở cột 1 và 2 vẫn dùng được vì độ lệch giữa chúng quá lớn để đổ cho nhiễu: TBT gấp 2,6 lần, CLS từ 0 lên 0,724. Nhưng đến lúc làm lại cho đúng, tôi bỏ hẳn cách chấm điểm này và chuyển sang so sánh bật–tắt trên cùng một đường tải.

Hai con số làm nên toàn bộ câu chuyện, kiểm tra lại được ngay bây giờ trên máy chủ:

# file thí nghiệm hỏng, vẫn còn nằm lại trên đĩa
$ wc -c wp-content/mna-critical/used.min.css
325197

# CSS đang thật sự được nhét vào trang chủ hôm nay
$ wc -c wp-content/mna-css/critical/home.css
11327
$ gzip -c wp-content/mna-css/critical/home.css | wc -c
2912

325.197 so với 11.327. Nhỏ hơn 28,7 lần. Cùng một kỹ thuật, cùng một trang, khác nhau ở chỗ lấy cái gì đem nhét vào.

Làm lại bằng công cụ đo đúng thứ cần đo

Lần thứ hai tôi bỏ Chromium coverage, đổi sang penthouse — công cụ dựng trang ở một kích thước màn hình cụ thể rồi chỉ lấy những rule tham gia vẽ phần nhìn thấy. Đúng định nghĩa của thứ tôi cần ngay từ đầu.

Chạy cho ba loại trang, gộp hai kích thước màn hình điện thoại 412×823 và máy tính 1440×900, nén lại:

  • Trang chủ: 11.327 byte (gzip còn 2.912)
  • Trang bài viết: 18.322 byte (gzip 4.315)
  • Trang chuyên mục: 18.702 byte (gzip 4.306)

File CSS lớn của theme không bị vứt đi, nó chỉ bị đẩy xuống hàng chờ: trang vẫn tải nó, nhưng tải kiểu không chặn đường vẽ. Trên trang chủ đang chạy, file đó là bản đã cắt bớt còn 242.993 byte, gzip 32.022.

Lần này tôi đo bằng cách bật rồi tắt tính năng, lặp ba lần mỗi bên, trên cùng một đường tải chưa cache. Khi tắt, thời điểm vẽ nét đầu tiên rơi vào 3.296–3.448 mili-giây và luôn đến sau lúc file CSS cuối cùng cập bến. Khi bật, con số là 2.864–2.972 mili-giây, và trang vẽ xong trước file CSS cuối cùng khoảng 1,1 giây.

Chênh lệch khoảng 440 mili-giây. Nhưng tôi tin vào tín hiệu kia hơn — chuyện trang vẽ xong trước hay sau khi CSS về là một quan hệ trước–sau, không phụ thuộc máy chủ hôm đó bận hay rảnh.

Còn bố cục: tôi đo toạ độ và kích thước của 7–8 khối trên ba loại trang, hai kích thước màn hình, so lúc bật với lúc tắt. Kết quả 0 sai khác. Có một lần CLS nhích lên 0,018 ở trang bài viết trên điện thoại, nhưng đo 6 lần lúc bật thấy 1 lần, đo 4 lần lúc tắt cũng thấy 1 lần — nó có sẵn từ trước, không phải do phần CSS mới.

Nếu bạn định làm việc này, kiểm ba thứ

  1. Cân file trước khi nhét. CSS nhét thẳng vào trang mà quá 20.000 byte thì gần như chắc chắn bạn đang lấy nhầm tập hợp. Con số 325.197 của tôi lẽ ra phải là một tiếng chuông báo động ngay từ lúc tạo file, trước cả khi đo.
  2. Nhìn CLS, đừng chỉ nhìn “CSS chặn hiển thị”. Chỉ số chặn hiển thị về 0 làm tôi tưởng đã xong việc. Nó về 0 thật, trong khi hai chỉ số quan trọng hơn thì hỏng nặng. Tôi từng dính đúng kiểu này một lần nữa khi thấy cache báo trúng và tưởng đã tối ưu xong, hoá ra cache bật rồi mà web vẫn chậm 1,3 giây vì một lỗi quyền thư mục.
  3. Giữ đường lùi. Bản đang chạy được cài thành hai file: một file chứa code, một file bật công tắc. Xoá file công tắc là mọi thứ trở lại như cũ trong một giây, không cần sửa gì. Nhờ vậy tôi mới dám bật lên đo trên site thật.

Điều tôi rút ra không phải “đừng inline CSS”. Kỹ thuật ấy đúng, và bản làm lại đang chạy trên site này. Điều tôi rút ra là một công cụ đo có thể trả lời rất chính xác cho một câu hỏi hơi khác với câu tôi đang cần hỏi — và nếu tôi không đọc kỹ nó đang đo cái gì, tôi sẽ tự tin làm hỏng đúng thứ mình định sửa.

File used.min.css 325.197 byte đó tôi vẫn giữ trên máy chủ. Không dùng nữa, chỉ để nhắc.

Có thể bạn thích

Để lại bình luận