Bối cảnh
phatdev.net là nơi khách hàng và nhà tuyển dụng xem mình làm gì. Nó cũng làm thêm hai việc nặng hơn một trang giới thiệu bình thường: chạy bộ tài liệu cho các plugin WordPress của mình, và là máy chủ phát bản cập nhật cho các plugin đó. Trang có hai ngôn ngữ, tiếng Việt và tiếng Anh, và không dùng CMS (hệ quản trị nội dung dựng sẵn như WordPress). Nội dung nằm trong một cơ sở dữ liệu MySQL, mình tự viết cả phần quản trị.
Đây là dự án của chính mình, nên không có khách nào bị nêu tên ở đây. Mình chọn kể nó vì nó có đủ số đo trước và sau, và vì mọi quyết định trong đó là quyết định mình tự chịu.

Thách thức
Ngày 18/10/2026, trang chủ đạt 95 điểm tốc độ trên điện thoại. Khá, nhưng chưa phải mức mình muốn khi đặt trang này làm bộ mặt. Phần chậm nhất là thời gian chờ phần chính của màn hình đầu hiện ra: 2,8 giây (chỉ số này tên là LCP).
Có ba việc khác cần giải quyết cùng lúc.
Thứ nhất, hai ngôn ngữ phải đúng với Google, tức mỗi trang biết trang bản dịch của nó ở đâu. Thứ hai, ngày càng nhiều người hỏi AI thay vì tự tìm, nên nội dung cần đọc được cả bằng máy mà không phải gỡ ra khỏi giao diện. Thứ ba, trang chỉ có một người vận hành, nên phải tự biết ai vào, lỗi gì xảy ra, mà không thuê thêm công cụ.
Cách làm
1. Đo trước khi sửa
Mình chạy Lighthouse (công cụ chấm tốc độ của Google) từ chính máy chủ, giả lập điện thoại tầm trung và mạng chậm, và đo mỗi trang ít nhất ba lần. Một lần đo không nói được gì, vì kết quả dao động theo việc máy đang bận hay rảnh.
2. Giảm những thứ nằm trước khi trang hiện ra
Công cụ đo tính thời gian hiện trang bằng cách cộng mọi thứ phải tải trước đó. Nên mình đi tìm thứ nặng nhất nằm trước điểm đó:
- Ảnh đại diện nặng khoảng 150 KB nhưng chỉ hiển thị nhỏ. Sau khi cắt đúng cỡ chỉ còn 3 đến 7 KB.
- Một thư viện hiệu ứng khoảng 37 KB chỉ để làm hiệu ứng hiện dần. Mình thay bằng CSS và một đoạn mã nhỏ.
- Bốn ảnh chụp màn hình ở trang giới thiệu plugin SL Migrations là PNG gần 1 MB mỗi ảnh. Đổi sang WebP (định dạng ảnh nhẹ hơn PNG) còn khoảng 90 KB.
- HTML trang chủ nặng 19 KB sau nén gzip. Khi mới nối với máy chủ, lượt gửi đầu tiên chỉ chở được khoảng 14,6 KB, phần dư phải chờ thêm một vòng hỏi đáp qua mạng. Mình đổi sang nén brotli (gọn hơn gzip) ở Nginx, phần mềm máy chủ web đứng trước ứng dụng. HTML còn 12,2 KB, gửi gọn trong lượt đầu.
Phần giải thích kỹ cho dev nằm ở bài Từ 95 lên 99 điểm tốc độ mobile: mình đã làm gì với một site Next.js. Ở đây mình chỉ giữ lại ý chính: tìm thứ nặng nhất nằm trước điểm LCP, sửa một thứ, đo lại.
3. Một nguồn dữ liệu, nhiều cách đọc
Mỗi trang nội dung đọc từ cùng một chỗ trong cơ sở dữ liệu, rồi ra bốn dạng: trang HTML cho người và Google, bản Markdown (văn bản thuần) cho máy, mục lục llms.txt (danh sách trang dành cho AI), và một cổng MCP để AI hỏi thẳng. Vì cùng nguồn nên không có chuyện bản cho AI cũ hơn bản cho người.

Thêm .md vào cuối địa chỉ của bất kỳ trang nội dung nào là ra bản Markdown của trang đó. Với trang nhiều chữ như một bài blog, bản này gọn hơn nhiều so với HTML đầy khung và hiệu ứng.

Cổng MCP (chuẩn để AI dùng công cụ của một trang web) có 12 công cụ, tất cả chỉ đọc và có giới hạn số lần gọi mỗi phút. Một AI agent có thể tìm dự án, đọc một bài, hay lấy thông tin liên hệ mà không cần thêm khoá hay tài khoản. Cách kết nối nằm ở tài liệu MCP.
4. Một mình vẫn vận hành được
Phần quản trị mình tự viết ghi lại lượt xem ngay ở máy chủ, tách người thật khỏi bot, gom lỗi theo nhóm và gửi cảnh báo về hộp thư riêng. Tài khoản quản trị có sẵn đăng nhập hai lớp. Nhờ vậy một người vẫn nuôi được cả hệ thống mà không phải thuê dịch vụ theo dõi bên ngoài.
Kết quả
Số đo ở đây là số đo trong phòng thí nghiệm, chạy bằng Lighthouse trên máy chủ của mình. Chúng khác dữ liệu người dùng thật và có thể lệch vài điểm so với công cụ PageSpeed của Google.
Đo bằng Lighthouse 12 (công cụ chấm điểm tốc độ của Google) chạy từ máy chủ của mình, giả lập điện thoại và mạng chậm. Trước: đo ngày 18/10/2026 (trang /en được 95 hoặc 96, ghi mức thấp). Sau: ngày 19/10/2026, /vi đo 3 lần đều 99 và 18 trên 18 lần đo ở các trang chính đạt từ 99.
LCP là thời gian trang hiện xong phần lớn nhất của màn hình đầu. Sau tối ưu, ba lần đo ra từ 1,81 đến 1,92 giây; biểu đồ dùng giá trị cao nhất.
Ảnh chụp: PNG từ 0,8 đến 1,1 MB xuống WebP khoảng 90 KB (lấy 0,9 MB). Ảnh đại diện sau tối ưu từ 3 đến 7 KB (lấy 7). HTML: nén gzip 19 KB, nén brotli 12,2 KB (hai cách nén trang trước khi gửi, brotli nhỏ hơn). Số 436 KB đo trên cổng thử của bản cũ (chưa qua Nginx), 194 KB đo trên bản chạy thật, nên chỉ nên đọc là cỡ lớn.
Điểm trên điện thoại đi từ 95 lên 99. Ở các trang chính, 18 trên 18 lần đo đạt từ 99 trở lên, bản máy tính 100, và ba mục còn lại là Accessibility (dễ truy cập), Best Practices (làm đúng chuẩn) và SEO đều 100. Độ xô lệch bố cục (CLS) giữ ở 0.
Có một điều mình không làm được. Thời gian trình duyệt bị kẹt (TBT) gần như không đổi, vì phần khởi động của chính React và Next.js (hai thư viện dựng giao diện) đã chiếm khoảng 115 mili giây mô phỏng và code của site không giảm thêm được.
Bài học
Đo ba lần rồi mới tin. Một lần đo ra 98 hay 99 là chuyện may rủi của máy.
Phần đáng giảm nhất là thứ nằm trước điểm LCP. Giảm byte của thứ tải sau đó thường không đổi điểm.
Làm cho AI đọc được không tốn nhiều khi trang đã có một nguồn dữ liệu sạch. Phần khó là giữ cho bản máy và bản người không lệch nhau, nên mình để chúng sinh ra từ cùng một chỗ.
Và nén phải có người đảm nhận. Mình tắt nén trong ứng dụng để Nginx nén thay. Nếu Nginx không được cài để nén thì trang đi ra không nén gì cả, nên sau khi đổi phải đo lại kích thước thật của trang.
Cần việc tương tự?
Nếu site của bạn đang chậm trên điện thoại, hoặc bạn muốn nội dung của mình đọc được bằng AI, gửi mình vài dòng về trang đó. Mình sẽ đo trước, rồi nói rõ cái nào đáng sửa và cái nào không.






