Debugging & Troubleshooting

Cách tăng điểm PageSpeed Insights của Netalith.com từ 81 lên 93

Case study từng bước: chuyển cấu hình Tailwind CDN sang tailwind.config.js, xử lý lỗi lệch phiên bản CLI và cách khắc phục chính xác cho Django + Tailwind CSS.

Ảnh đại diện Long Nguyen

Long Nguyen

Lập trình viên Fullstack · Kỹ sư AI · Nhà nghiên cứu

3 phút đọc
Điểm hiệu suất PageSpeed Insights trên thiết bị di động tăng từ 81 lên 93 sau khi gỡ Tailwind CDN

Vấn đề: Điểm PageSpeed Insights bị kẹt ở mức 81

Netalith.com đạt 81 điểm về Hiệu suất và 96 điểm về Khả năng tiếp cận trong báo cáo PageSpeed Insights trên thiết bị di động. Bạn có thể xem toàn bộ báo cáo được sử dụng xuyên suốt bài viết tại đây: báo cáo PageSpeed Insights của netalith.com.

Phần chẩn đoán của báo cáo cho thấy một chuỗi yêu cầu chặn hiển thị gây thêm khoảng 1.140 mili giây độ trễ. Nguyên nhân là hai stylesheet được tải trên mọi trang: script Tailwind CDN và một stylesheet được build riêng, hoạt động song song và trùng lặp phần lớn CSS.

Chuỗi yêu cầu mạng cho thấy Tailwind CDN và flow.css cùng tải, gây độ trễ chặn hiển thị

Bước 1: Tạo tailwind.config.js và chuyển cấu hình CDN vào đó

Trước khi xóa bất kỳ thứ gì, cần chuyển cấu hình theme hiện tại sang một nơi không phụ thuộc vào việc script CDN có tồn tại hay không. Cấu hình ban đầu nằm inline, ngay cạnh script CDN, và tham chiếu đến đối tượng toàn cục tailwind chỉ xuất hiện vì script đó tạo ra nó:

<script src="https://cdn.tailwindcss.com"></script>

Một tệp mới có tên tailwind.config.js được tạo ở thư mục gốc của dự án, cùng cấp với manage.py. Mọi giá trị trong khối cấu hình nói trên được chuyển vào tệp này, bọc trong module.exports thay vì gán cho đối tượng toàn cục tailwind. Đồng thời, tệp có thêm mảng content để cho CLI biết các tệp template thực tế nằm ở đâu — điều mà bản build CDN không cần vì nó biên dịch mọi thứ trực tiếp trong trình duyệt:

// tailwind.config.js 
module.exports = {
  content: ["./templates/**/*.html"],
  darkMode: 'class',
  theme: {
        extend: {
            colors: {
                primary: {
                    "50": "#eff6ff",
                    "100": "#dbeafe",
                    "200": "#bfdbfe",
                    "300": "#93c5fd",
                    "400": "#60a5fa",
                    "500": "#3b82f6",
                    "600": "#2563eb",
                    "700": "#1d4ed8",
                    "800": "#1e40af",
                    "900": "#1e3a8a",
                    "950": "#172554"
                },
                brand: {
                    DEFAULT: '#2563eb',
                    dark: '#1d4ed8',
                },
            }
        },
        fontFamily: {
            'body': [
                'Inter', 'ui-sans-serif', 'system-ui', '-apple-system', 'system-ui',
                'Segoe UI', 'Roboto', 'Helvetica Neue', 'Arial', 'Noto Sans',
                'sans-serif', 'Apple Color Emoji', 'Segoe UI Emoji', 'Segoe UI Symbol', 'Noto Color Emoji'
            ],
            'sans': [
                'Inter', 'ui-sans-serif', 'system-ui', '-apple-system', 'system-ui',
                'Segoe UI', 'Roboto', 'Helvetica Neue', 'Arial', 'Noto Sans',
                'sans-serif', 'Apple Color Emoji', 'Segoe UI Emoji', 'Segoe UI Symbol', 'Noto Color Emoji'
            ]
        }
    }
}

Một chi tiết về cấu trúc rất quan trọng: colors phải nằm bên trong theme.extend, không đặt trực tiếp trong theme. Nếu đặt màu tùy chỉnh trực tiếp dưới theme, bạn sẽ thay thế toàn bộ bảng màu mặc định của Tailwind thay vì bổ sung vào đó. Khi ấy, mọi class mặc định như bg-blue-600 sẽ âm thầm bị loại khỏi bản build. fontFamily vẫn nằm ở cấp giống cấu hình CDN ban đầu, phù hợp với những gì website đang sử dụng.

Tiếp theo, một tệp CSS nguồn được tạo để CLI biên dịch, theo đúng cấu trúc tệp static hiện có của dự án (STATICFILES_DIRS trỏ đến thư mục public/):

@tailwind base;
@tailwind components;
@tailwind utilities;

Bản build được cấu hình để xuất ra public/dist/output.css, khớp với đường dẫn mà template Django sẽ tải thông qua {% static 'dist/output.css' %}.

Bước 2: Xóa script CDN

Sau khi chuyển cấu hình, thẻ script CDN cùng khối cấu hình inline được xóa hoàn toàn khỏi template nền. Chúng được thay bằng một liên kết stylesheet duy nhất trỏ đến tệp do CLI tạo ra:

<link rel="stylesheet" href="{% static 'dist/output.css' %}">

Tại thời điểm này, chưa có gì được build. Và đây chính là bước dẫn đến sai lầm tiếp theo.

Bước 3: Sai lầm — tải CLI mới nhất mà không kiểm tra phiên bản CDN

Để tạo tệp CSS thực tế, cần cài đặt Tailwind CLI. Tailwind cung cấp tệp thực thi độc lập cho từng nền tảng trên trang phát hành GitHub, vì vậy Windows không cần cài Node.js: github.com/tailwindlabs/tailwindcss/releases.

Sai lầm nằm ở việc tải tệp thực thi ở đầu trang — tức bản phát hành mới nhất — mà không kiểm tra trước website đang chạy phiên bản major nào. Script CDN được sử dụng trước đó là https://cdn.tailwindcss.com, cung cấp Tailwind CSS v3. Trong khi đó, tệp CLI mới nhất tải từ GitHub lúc ấy là Tailwind v4. Hai phiên bản major khác nhau của cùng một công cụ, với cách đọc cấu hình và quét class khác nhau, đã bị ghép dùng chung mà không được phát hiện trước.

Bước 4: Build, triển khai và làm hỏng bố cục

Tệp thực thi v4 chạy lệnh build không báo lỗi và tạo ra tệp output.css. Phần lớn trang trông vẫn ổn khi kiểm tra cục bộ, nên thay đổi được triển khai.

Trên website thực tế, một hero section sử dụng bố cục lưới tùy chỉnh, lg:grid-cols-[1.2fr_0.8fr], đã sập từ hai cột thành một cột, còn hình ảnh nổi bật bên cạnh cũng biến mất. Console trình duyệt không có lỗi và không có request nào thất bại. Đơn giản là CSS đã biên dịch trong output.css không chứa rule cho class lưới đó.

Đây chính là biểu hiện thực tế của việc lệch phiên bản v3/v4: CLI v4 không hiểu tailwind.config.js theo kiểu v3 giống như chính v3, nên một phần cơ chế quét class không được áp dụng đúng. Một số class được dùng trong template âm thầm vắng mặt khỏi tệp đầu ra. Bản build không hề thất bại rõ ràng; nó chỉ phát hành một stylesheet không đầy đủ.

 

Bố cục website thực tế của Netalith bị lỗi: hero section hai cột sập thành một cột và hình ảnh nổi bật biến mất

 

Bước 5: Tải đúng bản build v3 và build lại

Cách khắc phục là quay lại trang phát hành GitHub và tải một bản v3 cụ thể thay vì liên kết mặc định đến bản mới nhất, chẳng hạn v3.4.19. Phiên bản này khớp với major version mà script CDN và cú pháp tailwind.config.js hiện tại được viết cho. Trong danh sách Assets của bản phát hành đó, tệp phù hợp với máy Windows 64-bit tiêu chuẩn là tailwindcss-windows-x64.exe. Tệp được đổi tên thành tailwindcss.exe và đặt ở thư mục gốc dự án, thay thế tệp thực thi v4.

Build lại bằng cùng lệnh, với cùng tailwind.config.js nhưng nay sử dụng tệp thực thi đúng phiên bản, tạo ra một tệp đầu ra khác biệt rõ rệt. Class lưới trước đó bị thiếu đã xuất hiện trong CSS được biên dịch. Sau khi triển khai lại, bố cục khớp với phiên bản được render bởi CDN ban đầu.

Quy trình hoàn chỉnh và chính xác

Dưới đây là trình tự thực sự hiệu quả, không mắc lỗi phiên bản ở giữa:

  1. Vị trí tệp static. Tệp nguồn nằm tại public/src/input.css, tệp đầu ra đã biên dịch nằm tại public/dist/output.css. Cách sắp xếp này khớp với cấu trúc STATICFILES_DIRS hiện có của dự án, nên không cần thay đổi cơ chế xử lý tệp static của Django.
  2. Tạo tailwind.config.js. Chuyển mọi giá trị từ đối tượng tailwind.config inline của CDN vào module.exports, giữ màu tùy chỉnh bên trong theme.extend, đồng thời thêm mảng content trỏ đến ./templates/**/*.html hoặc vị trí thực tế của các template trong dự án.
  3. Xóa script CDN và khối cấu hình inline khỏi template nền, thay bằng liên kết stylesheet đến đường dẫn tệp đầu ra đã biên dịch.
  4. Kiểm tra phiên bản CDN trước khi tải CLI. URL và cách hoạt động của script CDN cho biết major version đang được sử dụng; tệp thực thi CLI tải về phải khớp với phiên bản đó, không đơn giản là bản được GitHub liệt kê là mới nhất.
  5. Tải tệp thực thi đúng phiên bản từ trang phát hành GitHub với tag phù hợp. Trên Windows, hãy chọn asset tailwindcss-windows-x64.exe rồi đổi tên thành tailwindcss.exe.
  6. Build: .\tailwindcss.exe -i public/src/input.css -o public/dist/output.css --minify, hoặc dùng --watch trong quá trình phát triển.
  7. Kiểm tra trước khi triển khai. So sánh trang đã render với phiên bản trước đây dùng CDN trên nhiều template, đồng thời kiểm tra kích thước tệp đầu ra có tương ứng với một bản build đầy đủ thay vì bản build một phần hay không.

Kết quả

Sau khi gỡ hoàn toàn CDN và triển khai stylesheet được build đúng cách, đồng thời thêm fetchpriority="high" cho hình ảnh Largest Contentful Paint và sửa hai thành phần giao diện có độ tương phản thấp được nêu trong cùng báo cáo, kết quả PageSpeed Insights trên thiết bị di động là:

  • Hiệu suất: 81 → 93
  • Khả năng tiếp cận: 96 → 100
  • Best Practices: 100
  • SEO: 100
Kết quả PageSpeed Insights cuối cùng: Hiệu suất 93, Khả năng tiếp cận 100, Best Practices 100 và SEO 100

Largest Contentful Paint đã được cải thiện nhưng vẫn là cơ hội tối ưu lớn nhất còn lại. Nguyên nhân chủ yếu hiện nằm ở kích thước hình ảnh, không còn là các tài nguyên chặn hiển thị. Đây sẽ là hạng mục tối ưu hóa tiếp theo.

Bài học thực sự đáng ghi nhớ

  • Khi thay thế một công cụ dựa trên CDN bằng phiên bản build cục bộ, hãy kiểm tra CDN đang cung cấp major version nào trước khi tải bất kỳ thứ gì. Bản tải xuống mới nhất mặc định không phải lúc nào cũng là lựa chọn đúng.
  • Tailwind build hoàn tất mà không báo lỗi không có nghĩa là tệp đầu ra chính xác. Class bị thiếu sẽ không tạo cảnh báo; chúng chỉ khiến bố cục bị hỏng sau khi triển khai.
  • Màu tùy chỉnh phải nằm bên trong theme.extend, không đặt trực tiếp trong theme, nếu không toàn bộ bảng màu mặc định sẽ bị ghi đè một cách âm thầm.
  • Kiểm tra stylesheet được build lại trên nhiều trang trước khi triển khai sẽ giúp phát hiện lỗi lệch phiên bản và sai đường dẫn content mà việc chỉ kiểm tra trang chủ không thể nhận ra.

CÂU HỎI THƯỜNG GẶP

Câu hỏi thường gặp

Vì sao Tailwind CDN làm giảm điểm PageSpeed Insights?

Script Tailwind CDN (cdn.tailwindcss.com) gửi toàn bộ framework đến trình duyệt và biên dịch các class CSS trong thời gian chạy bằng JavaScript. Điều này làm tăng đáng kể dung lượng chặn hiển thị trên critical rendering path, đồng thời bao gồm cả những utility class mà trang không thực sự sử dụng. Đây là nguyên nhân trực tiếp làm giảm điểm Hiệu suất trong Lighthouse và PageSpeed Insights.

Điều gì thay thế script Tailwind CDN khi đưa lên production?

Một tệp CSS tĩnh đã được biên dịch trước bằng Tailwind CLI. CLI quét các template, chỉ tạo những class CSS thực sự được dự án sử dụng và xuất ra một stylesheet minified duy nhất. Tệp này có thể tải ngay mà không cần biên dịch trong thời gian chạy.

Chuyển từ Tailwind CDN sang bản build đã biên dịch trên website đang hoạt động có rủi ro không?

Có, nếu thực hiện trực tiếp trên production mà không kiểm thử. Cấu hình runtime phụ thuộc vào đối tượng toàn cục tailwind (được script CDN sử dụng) sẽ gây lỗi sau khi script CDN bị xóa. Ngoài ra, mọi class không được quá trình quét content của bản build phát hiện sẽ bị thiếu trong stylesheet cuối cùng. Cả hai trường hợp đều có thể làm bố cục hiển thị sai rõ rệt.

Ngoài Tailwind CDN, những vấn đề nào khác ảnh hưởng đến điểm PageSpeed?

Các hình ảnh có kích thước lớn hơn kích thước hiển thị, thiếu fetchpriority trên hình ảnh Largest Contentful Paint và thời gian TTL bộ nhớ đệm ngắn cho những tài nguyên static vốn đã dùng hashed filename. Việc xử lý vấn đề CDN tạo ra tác động lớn nhất, nhưng kích thước hình ảnh và bộ nhớ đệm vẫn là những cơ hội tối ưu tiếp theo.

Chuyển sang bản build Tailwind đã biên dịch có ảnh hưởng đến điểm tiếp cận hoặc SEO không?

Không trực tiếp, nhưng cùng đợt tối ưu này là thời điểm phù hợp để xử lý các vấn đề về khả năng tiếp cận, chẳng hạn độ tương phản màu không đủ, vì cả hai đều được nêu trong cùng báo cáo PageSpeed Insights. Trong trường hợp này, việc sửa hai thành phần có độ tương phản thấp cùng với thay đổi CSS đã đưa điểm Khả năng tiếp cận lên mức tuyệt đối 100.

Cập nhật cùng Netalith

Nhận tài nguyên lập trình, cập nhật sản phẩm và ưu đãi đặc biệt ngay trong hộp thư của bạn.