Tách mã bằng tính năng nhập động trong Next.js

Cách tăng tốc ứng dụng Next.js bằng cách phân tách mã và chiến lược tải thông minh.

Bài đăng này giải thích các loại phân tách mã và cách sử dụng tính năng nhập động để tăng tốc ứng dụng Next.js.

Theo mặc định, Next.js sẽ chia JavaScript của bạn thành các phần riêng biệt cho từng tuyến. Khi người dùng tải ứng dụng của bạn, Next.js chỉ gửi mã cần thiết cho tuyến đường ban đầu. Khi người dùng di chuyển trong ứng dụng, họ sẽ tìm nạp các đoạn liên kết với các tuyến khác. Tính năng phân tách mã dựa trên tuyến đường giúp giảm thiểu lượng tập lệnh cần được phân tích cú pháp và biên dịch cùng một lúc, nhờ đó rút ngắn thời gian tải trang.

Mặc dù tính năng phân tách mã dựa trên tuyến đường là một lựa chọn mặc định phù hợp, nhưng bạn có thể tối ưu hoá thêm quy trình tải bằng cách phân tách mã ở cấp thành phần. Nếu có các thành phần lớn trong ứng dụng, bạn nên chia các thành phần đó thành các phần riêng biệt. Bằng cách đó, mọi thành phần lớn không quan trọng hoặc chỉ hiển thị trên một số lượt tương tác nhất định của người dùng (chẳng hạn như nhấp vào một nút) đều có thể được tải lười.

Next.js hỗ trợ import() động, cho phép bạn nhập các mô-đun JavaScript (bao gồm cả các thành phần React) một cách linh động và tải từng lần nhập dưới dạng một phần riêng biệt. Điều này giúp bạn phân tách mã ở cấp thành phần và cho phép bạn kiểm soát việc tải tài nguyên để người dùng chỉ tải mã họ cần cho phần trang web mà họ đang xem. Trong Next.js, các thành phần này được hiển thị phía máy chủ (SSR) theo mặc định.

Nhập động trong thực tế

Bài đăng này bao gồm một số phiên bản của ứng dụng mẫu bao gồm một trang đơn giản với một nút. Khi nhấp vào nút này, bạn sẽ thấy một chú chó con dễ thương. Khi di chuyển qua từng phiên bản ứng dụng, bạn sẽ thấy sự khác biệt giữa tính năng nhập động và tính năng nhập tĩnh cũng như cách xử lý các tính năng này.

Trong phiên bản đầu tiên của ứng dụng, chú chó con nằm trong components/Puppy.js. Để hiển thị chú chó con trên trang, ứng dụng sẽ nhập thành phần Puppy trong index.js bằng câu lệnh nhập tĩnh:

import Puppy from "../components/Puppy";

Để xem cách Next.js gói ứng dụng, hãy kiểm tra dấu vết mạng trong DevTools:

  1. Để xem trước trang web, hãy nhấn vào Xem ứng dụng. Sau đó, nhấn vào biểu tượng Toàn màn hình toàn màn hình.

  2. Nhấn tổ hợp phím `Ctrl+Shift+J` (hoặc `Command+Option+J` trên máy Mac) để mở DevTools.

  3. Nhấp vào thẻ Mạng.

  4. Chọn hộp đánh dấu Tắt bộ nhớ đệm.

  5. Tải lại trang.

Khi bạn tải trang, tất cả mã cần thiết, bao gồm cả thành phần Puppy.js, sẽ được đóng gói trong index.js:

Thẻ Mạng trong DevTools hiển thị 6 tệp JavaScript: index.js, app.js, webpack.js, main.js, 0.js và tệp dll (thư viện liên kết động).

Khi bạn nhấn nút Click me (Nhấp vào tôi), chỉ yêu cầu về tệp JPEG của chú chó con mới được thêm vào thẻ Network (Mạng):

Thẻ Mạng trong DevTools sau khi nhấp vào nút, hiển thị cùng 6 tệp JavaScript và 1 hình ảnh.

Nhược điểm của phương pháp này là ngay cả khi người dùng không nhấp vào nút để xem chú chó con, họ vẫn phải tải thành phần Puppy vì thành phần này có trong index.js. Trong ví dụ nhỏ này, điều đó không thành vấn đề lớn, nhưng trong các ứng dụng thực tế, việc chỉ tải các thành phần lớn khi cần thiết thường là một điểm cải tiến lớn.

Bây giờ, hãy xem phiên bản thứ hai của ứng dụng, trong đó lệnh nhập tĩnh được thay thế bằng lệnh nhập động. Next.js bao gồm next/dynamic, cho phép bạn sử dụng tính năng nhập động cho mọi thành phần trong Next:

import Puppy from "../components/Puppy";
import dynamic from "next/dynamic";

// ...

const Puppy = dynamic(import("../components/Puppy"));

Làm theo các bước trong ví dụ đầu tiên để kiểm tra dấu vết mạng.

Trong lần đầu tiên tải ứng dụng, chỉ index.js được tải xuống. Lần này, kích thước nhỏ hơn 0,5 KB (giảm từ 37,9 KB xuống 37,4 KB) vì không bao gồm mã cho thành phần Puppy:

Mạng DevTools hiển thị cùng 6 tệp JavaScript, ngoại trừ index.js hiện nhỏ hơn 0,5 KB.

Thành phần Puppy hiện nằm trong một phần riêng biệt, 1.js, chỉ được tải khi bạn nhấn nút:

Thẻ Mạng của DevTools sau khi nhấp vào nút, hiển thị tệp 1.js bổ sung và hình ảnh được thêm vào cuối danh sách tệp.

Trong các ứng dụng thực tế, các thành phần thường lớn hơn nhiều và việc tải từng phần có thể giúp giảm tải trọng JavaScript ban đầu của bạn xuống hàng trăm kilobyte.

Nhập động bằng chỉ báo tải tuỳ chỉnh

Khi tải lười các tài nguyên, bạn nên cung cấp chỉ báo tải trong trường hợp có độ trễ. Trong Next.js, bạn có thể thực hiện việc đó bằng cách cung cấp một đối số bổ sung cho hàm dynamic():

const Puppy = dynamic(() => import("../components/Puppy"), {
  loading: () => <p>Loading...</p>
});

Để xem chỉ báo đang tải, hãy mô phỏng kết nối mạng chậm trong DevTools:

  1. Để xem trước trang web, hãy nhấn vào Xem ứng dụng. Sau đó, nhấn vào biểu tượng Toàn màn hình toàn màn hình.

  2. Nhấn tổ hợp phím `Ctrl+Shift+J` (hoặc `Command+Option+J` trên máy Mac) để mở DevTools.

  3. Nhấp vào thẻ Mạng.

  4. Chọn hộp đánh dấu Tắt bộ nhớ đệm.

  5. Trong danh sách thả xuống Throttling (Giới hạn), hãy chọn Fast 3G (3G nhanh).

  6. Nhấn vào nút Nhấp vào tôi.

Bây giờ, khi bạn nhấp vào nút, sẽ mất một chút thời gian để tải thành phần và ứng dụng sẽ hiển thị thông báo "Đang tải…" trong thời gian chờ đợi.

Màn hình tối có văn bản

Nhập động mà không cần SSR

Nếu chỉ cần hiển thị một thành phần ở phía máy khách (ví dụ: tiện ích trò chuyện), bạn có thể thực hiện việc đó bằng cách đặt tuỳ chọn ssr thành false:

const Puppy = dynamic(() => import("../components/Puppy"), {
  ssr: false,
});

Kết luận

Với tính năng hỗ trợ nhập động, Next.js cung cấp cho bạn tính năng phân tách mã cấp thành phần. Tính năng này có thể giảm thiểu tải trọng JavaScript và cải thiện thời gian tải ứng dụng. Theo mặc định, tất cả các thành phần đều được kết xuất phía máy chủ và bạn có thể tắt tuỳ chọn này bất cứ khi nào cần thiết.