Nội dung
- Đo cái gì trước khi cắt: coverage của trình duyệt, không phải cảm giác
- Trên đường truyền, phần tiết kiệm nhỏ hơn nhiều so với con số 80%
- Điểm lên 9, TBT giảm 65%, còn LCP thì không
- Lưới an toàn nạp bù ăn sạch phần vừa tiết kiệm
- Cách chúng tôi giữ đường lùi: một file, xoá là xong
- Nếu bạn định làm việc này trên site của mình
Theme Soledad mà marketing365.vn đang dùng nạp một file CSS duy nhất tên main.min.css, nặng 1.208.930 byte. Đó là con số đo trên đĩa sáng nay, 14/08/2026. Một file CSS hơn một megabyte cho một trang tin — nó chặn render, nó nằm trong đường tới hạn, và PageSpeed báo hơn 1,2 MB trong đó chưa bao giờ được dùng tới.
Ngày 20/07/2026 chúng tôi cắt nó xuống còn 242.993 byte và đẩy lên chạy thật. Bài này ghi lại cách cắt, số đo trước sau, và phần khó nói hơn: chỉ số quan trọng nhất — LCP — không hề tốt lên, thậm chí còn nhích xấu đi. Cùng với một sai lầm khiến toàn bộ phần lợi bị nuốt mất trong hai ngày mà chúng tôi không nhận ra.

Đo cái gì trước khi cắt: coverage của trình duyệt, không phải cảm giác
Cách duy nhất biết dòng CSS nào thật sự được dùng là để trình duyệt tự nói. Chromium có tính năng coverage: mở một trang, nó ghi lại từng byte CSS có được áp lên phần tử nào không. Chúng tôi chạy coverage qua một danh sách URL đại diện — trang chủ, trang chuyên mục, bài đơn, trang tìm kiếm, trang 404, bản mobile và bản desktop — rồi hợp nhất các selector còn sống lại.
Chỗ dễ hỏng nhất không phải bước quét, mà bước dựng lại file. CSS không phải danh sách phẳng: một selector nằm trong @media (max-width: 767px) có ý nghĩa khác hẳn khi nó bị bê ra ngoài. Nếu công cụ cắt chỉ giữ lại các dòng rời rạc rồi ghép, giao diện mobile sẽ vỡ trong im lặng. Vì vậy script dựng lại đi theo cây AST của PostCSS và giữ nguyên cấu trúc @media bao ngoài.
Kết quả của bước này đo được bằng ba con số, chạy trên chính máy chủ sáng nay:
$ ls -l main.min.css main.pruned.css
-rw------- 1 www-data www-data 1208930 main.min.css
-rw-r--r-- 1 www-data www-data 242993 main.pruned.css
$ grep -o "{" main.min.css | wc -l → 9983 khối rule
$ grep -o "{" main.pruned.css | wc -l → 2474 khối rule
$ grep -o "@media" main.min.css | wc -l → 260
$ grep -o "@media" main.pruned.css | wc -l → 39
7.509 khối rule bị bỏ, tương đương 75,2% số rule. Phần @media giảm từ 260 xuống 39 — đây là chỗ chứng minh script không hề bê selector ra khỏi ngữ cảnh, nó bỏ hẳn những khối media không có selector nào sống sót.
Trên đường truyền, phần tiết kiệm nhỏ hơn nhiều so với con số 80%
Đây là chỗ dễ tự lừa mình nhất. Cắt 1.208.930 byte xuống 242.993 byte nghe như tiết kiệm 966 KB. Nhưng CSS đi qua đường truyền có nén gzip, và CSS là loại dữ liệu nén cực tốt vì lặp lại nhiều. Đo lại đúng cái mà khách thật sự tải:
$ gzip -9 -c main.min.css | wc -c → 164160
$ gzip -9 -c main.pruned.css | wc -c → 31664
Trên dây, phần tiết kiệm thật là 132.496 byte, tức khoảng 129 KB chứ không phải 966 KB. Vẫn là −80,7%, tỉ lệ gần y hệt, nhưng giá trị tuyệt đối nhỏ hơn bảy lần so với con số nhìn trên đĩa. Với một khách dùng 4G khoảng 5 MB/giây, 129 KB là chừng 26 mili giây. Ai kỳ vọng cắt CSS sẽ đổi đời tốc độ tải thì nên biết trước quy mô này.
Toàn bộ CSS chặn render của trang chủ hôm nay là 7 file, tổng 406.995 byte thô, 66.657 byte sau nén. File đã cắt chiếm 242.779 byte thô — vẫn là file lớn nhất, nhưng giờ nó nằm cùng hạng với phần còn lại thay vì gấp bốn lần tất cả cộng lại.
Điểm lên 9, TBT giảm 65%, còn LCP thì không
Hai lần chạy PageSpeed mobile tối 20/07, cách nhau khoảng 15 phút, cùng URL trang chủ:
- Điểm tổng: 57 → 66
- TBT: 370 ms → 130 ms, giảm 65%
- CLS: 0,018 → 0
- FCP: 3,3 s → 2,9 s
- CSS chưa dùng: khoảng 1,2 MB → 191 KiB
- LCP: 7,1 s → 7,4 s
Năm dòng đầu là thứ ai cũng muốn khoe. Dòng cuối mới là dòng đáng nói. LCP là chỉ số Google dùng để chấm trải nghiệm tải, và nó không những không giảm mà còn nhích lên 0,3 giây — nằm trong biên nhiễu giữa hai lần chạy, nên cách đọc trung thực là: cắt 80% CSS không cải thiện LCP của trang này chút nào.
Điều đó hợp lý khi nhìn kỹ. LCP của trang chủ bị quyết định bởi phần tử ảnh lớn nhất và bởi thời gian máy chủ trả byte đầu tiên, không phải bởi kích thước file CSS. CSS nặng làm nghẽn luồng chính — và đúng như vậy, TBT giảm 65%. Nó không làm ảnh hero tải nhanh hơn. Hai chuyện khác nhau, và chúng tôi đã gộp chúng làm một trước khi có số đo.
Đọc thêm: Tối ưu CSS làm điểm tốc độ tụt từ 38 xuống 14
Lưới an toàn nạp bù ăn sạch phần vừa tiết kiệm
Khi đẩy file cắt lên chạy thật, chúng tôi để lại một lưới an toàn: file CSS gốc 1,2 MB vẫn được nạp phía sau theo kiểu bất đồng bộ, để phòng trường hợp coverage bỏ sót một selector nào đó và có chỗ nào trên site bị vỡ. Nghe rất hợp lý. Nó chạy như vậy trong hai ngày.
Ngày 22/07, khi mở bảng mainthread-work-breakdown của PageSpeed để xem tại sao điểm không nhích thêm, mới thấy chuyện gì đang xảy ra. So bản có lưới an toàn với bản đã tắt nó:
Style & Layout + 703 ms (do lưới an toàn gây ra)
Other +1082 ms
Total Blocking Time + 656 ms
Script Evaluation - 529 ms
Script Parse/Compile - 640 ms
Trình duyệt vẫn phải tải, phân tích và tính lại toàn bộ 1,2 MB CSS đó, chỉ là muộn hơn một nhịp. Nạp bất đồng bộ không làm file biến mất — nó chỉ dời chi phí sang chỗ khác trong luồng chính. Phần “tiết kiệm” chỉ tồn tại trên giấy suốt hai ngày.
Có một bài học phụ nằm trong chính bảng số trên. TBT tăng 656 ms khiến phản xạ đầu tiên là đổ cho JavaScript, vì TBT gần như luôn được nói tới cùng JavaScript. Nhưng hai dòng cuối cho thấy JavaScript hôm đó còn nhẹ đi. Thủ phạm là CSS. Kể từ lần đó, nguyên tắc trong đội là thấy TBT xấu thì mở bảng phân rã luồng chính trước, đừng đoán.
Tắt lưới an toàn xong, đo lại lúc 15:11 ngày 22/07: 60 điểm, FCP 2,9 s, LCP 7,2 s, TBT 330 ms, CLS 0. Điểm thấp hơn lần đo tối 20/07 vì PageSpeed dao động giữa các lần chạy — thêm một lý do để đừng bao giờ kết luận từ một lần đo duy nhất.
Cách chúng tôi giữ đường lùi: một file, xoá là xong
Việc thay CSS của theme là loại thay đổi mà nếu sai thì cả site trông như vỡ. Nên phần hạ tầng được dựng sao cho quay lại trạng thái cũ chỉ mất vài giây, không cần nhớ gì.
Việc đổi đường dẫn CSS nằm gọn trong một mu-plugin duy nhất. Muốn tắt: xoá đúng file đó, site quay về nạp main.min.css gốc ngay lập tức. Không sửa theme, không sửa functions.php, không có bước gỡ nào phải nhớ. Tên file cũng được đặt có chủ ý để nó nạp trước các mu-plugin khác, tránh trường hợp một plugin khác đã kịp đăng ký handle CSS trước.
Ngoài ra có một script kiểm tra bố cục: chụp lại chiều cao và vị trí của các khối chính trên nhiều URL, so bản trước và bản sau. Cần biết trước một điều khi dùng loại script này — nó có nhiễu. Trên site của chúng tôi biên nhiễu là khoảng ±12 pixel, đến từ ảnh lazy-load và font nạp muộn. Chênh 8 pixel không có nghĩa là vỡ; chênh 60 pixel thì có.
Nếu bạn định làm việc này trên site của mình
Ba điều chúng tôi sẽ nói với chính mình của ngày 20/07:
Đừng nạp bù file gốc dưới bất kỳ hình thức nào. Nếu chưa tin file cắt, hãy để nó ở môi trường thử và mở rộng danh sách URL quét coverage. Nạp bù bất đồng bộ là cách tự tay xoá phần lợi mà vẫn tưởng mình cẩn thận.
Xác định trước bạn đang mua chỉ số nào. Cắt CSS mua TBT và mua điểm tổng. Nó không mua LCP. Nếu vấn đề của bạn là LCP thì phần thời gian nằm ở máy chủ và ở ảnh — chỗ đó phải sửa bằng cache và bằng ảnh, như trường hợp cache bật rồi mà web vẫn chậm 1,3 giây mà chúng tôi vấp ba lần trong ba tuần.
Đo bằng công cụ ngoài, đừng đo trên chính máy chủ. VPS đang chạy site không phải chỗ để chấm điểm hiệu năng: nó vừa phải phục vụ request vừa phải chạy trình duyệt đo, số ra không dùng được. Chúng tôi lấy số từ PageSpeed Insights chạy phía Google. Cách kiểm tra cơ bản cho site WordPress thì đã viết riêng trong bài kiểm tra tốc độ load web WordPress đúng cách.
File cắt vẫn đang phục vụ tại marketing365.vn tính đến hôm nay, 14/08/2026, gần một tháng sau khi đẩy lên. Chưa có báo cáo vỡ giao diện nào. Phần lợi thật nhận được là TBT giảm hai phần ba và CLS về 0 — nhỏ hơn kỳ vọng ban đầu, nhưng là số thật.



