Podział kodu z dynamicznymi importami w Next.js

Dowiedz się, jak przyspieszyć działanie aplikacji Next.js przez podział kodu i strategie inteligentnego wczytywania.

Czego się nauczysz?

W tym poście opisujemy różne typy dzielenia kodu oraz sposoby wykorzystywania dynamicznego importu do przyspieszenia działania aplikacji Next.js.

Podział kodu na podstawie trasy i komponentu

Domyślnie Next.js dzieli JavaScript na osobne fragmenty dla każdej trasy. Gdy użytkownicy ładują Twoją aplikację, Next.js wysyła tylko kod niezbędny dla trasy początkowej. Gdy użytkownicy poruszają się po aplikacji, pobierają fragmenty powiązane z innymi trasami. Dzielenie kodu na podstawie tras minimalizuje liczbę skryptów, które muszą być jednocześnie analizowane i skompilowane, co skraca czas wczytywania strony.

Dzielenie kodu na podstawie trasy jest dobrym ustawieniem domyślnym, ale można dodatkowo zoptymalizować proces wczytywania za pomocą podziału kodu na poziomie komponentu. Jeśli masz w aplikacji duże komponenty, warto je podzielić na osobne części. Dzięki temu wszystkie duże komponenty, które nie mają kluczowego znaczenia lub są renderowane tylko w przypadku określonych interakcji użytkownika (np. kliknięcia przycisku), mogą być ładowane leniwie.

Next.js obsługuje dynamiczny import(), co pozwala dynamicznie importować moduły JavaScript (w tym komponenty React) i ładować każdy import jako oddzielny fragment. Pozwala to podzielić kod na poziomie komponentu i kontrolować wczytywanie zasobów, aby użytkownicy pobierali tylko ten kod, którego potrzebują do danej części witryny, którą przeglądają. W Next.js komponenty te są domyślnie renderowane po stronie serwera (SSR).

Importowanie dynamiczne w praktyce

Ten post zawiera kilka wersji przykładowej aplikacji, która składa się z prostej strony z 1 przyciskiem. Po kliknięciu przycisku zobaczysz uroczego szczeniaka. W trakcie poruszania się po kolejnych wersjach aplikacji nauczysz się, czym importowanie dynamiczne różni się od importów statycznych, i jak korzystać z nich.

W pierwszej wersji aplikacji szczeniak mieszka w tym kraju: components/Puppy.js. Aby wyświetlić szczeniaka na stronie, aplikacja importuje komponent Puppy w index.js za pomocą statycznej instrukcji importu:

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

Aby zobaczyć, jak Next.js pakuje aplikację, sprawdź śledzenie sieci w Narzędziach deweloperskich:

  1. Aby wyświetlić podgląd strony, kliknij Wyświetl aplikację, a potem Pełny ekran pełny ekran.

  2. Naciśnij „Control + Shift + J” (lub „Command + Option + J” na Macu), aby otworzyć Narzędzia deweloperskie.

  3. Kliknij kartę Sieć.

  4. Zaznacz pole wyboru Wyłącz pamięć podręczną.

  5. Odśwież stronę.

Po załadowaniu strony cały niezbędny kod, w tym komponent Puppy.js, jest umieszczony w pakiecie index.js:

Karta Network (Sieć) z 6 plikami JavaScript: index.js, app.js, webpack.js, main.js, 0.js i dll (biblioteka linków dynamicznych).

Gdy naciśniesz przycisk Kliknij mnie, do karty Sieć zostanie dodana tylko prośba o włączenie pliku JPEG szczeniaka:

Karta Network (Sieć) po kliknięciu przycisku, z widocznymi 6 plikami JavaScript i 1 obrazem.

Wadą tego podejścia jest to, że nawet jeśli użytkownik nie kliknie przycisku, aby zobaczyć szczeniaka, będzie musiał załadować komponent Puppy, ponieważ zawiera on komponent index.js. W tym przykładzie to nie jest wielki problem, ale w rzeczywistych zastosowaniach często okazuje się, że duże komponenty można wczytywać tylko wtedy, gdy jest to konieczne.

Teraz zapoznaj się z drugą wersją aplikacji, w której import statyczny został zastąpiony przez import dynamiczny. Next.js zawiera obiekt next/dynamic, który umożliwia stosowanie dynamicznego importowania dla dowolnych komponentów w sekcji Next:

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

// ...

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

Wykonaj czynności z pierwszego przykładu, aby zbadać śledzenie sieci.

Przy pierwszym wczytaniu aplikacji pobierana jest tylko index.js. Tym razem jest on mniejszy o 0,5 KB (zmniejszył się z 37,9 KB do 37,4 KB), ponieważ nie zawiera kodu komponentu Puppy:

Sieć narzędzi deweloperskich z tymi samymi 6 plikami JavaScript, z tym że plik index.js jest o 0,5 KB mniejszy.

Komponent Puppy znajduje się teraz w osobnym fragmencie (1.js), który jest ładowany dopiero po naciśnięciu przycisku:

Karta Network (Sieć) po kliknięciu przycisku, pokazująca dodatkowy plik 1.js i obraz dodany u dołu listy plików.

W rzeczywistych zastosowaniach komponenty są często znacznie większe, a leniwe ładowanie ich może skrócić początkowy ładunek JavaScript o setki kilobajtów.

Dynamiczne importy z niestandardowym wskaźnikiem wczytywania

W przypadku leniwego ładowania zasobów warto udostępnić wskaźnik ładowania w przypadku opóźnień. W Next.js możesz to zrobić, podając dodatkowy argument do funkcji dynamic():

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

Aby zobaczyć, jak działa wskaźnik ładowania, wykonaj symulację wolnego połączenia sieciowego w Narzędziach deweloperskich:

  1. Aby wyświetlić podgląd strony, kliknij Wyświetl aplikację, a potem Pełny ekran pełny ekran.

  2. Naciśnij „Control + Shift + J” (lub „Command + Option + J” na Macu), aby otworzyć Narzędzia deweloperskie.

  3. Kliknij kartę Sieć.

  4. Zaznacz pole wyboru Wyłącz pamięć podręczną.

  5. Z listy Ograniczanie wybierz Szybka 3G.

  6. Naciśnij przycisk Kliknij mnie.

Gdy klikniesz przycisk, wczytanie komponentu zajmie trochę czasu, a aplikacja wyświetli komunikat „Wczytuję...”.

Ciemny ekran z tekstem

Dynamiczne importy bez SSR

Jeśli chcesz renderować komponent tylko po stronie klienta (np. widżet czatu), możesz ustawić opcję ssr na false:

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

Podsumowanie

Dzięki obsłudze importowania dynamicznego Next.js umożliwia dzielenie kodu na poziomie komponentu, co może zminimalizować ładunki JavaScript i skrócić czas wczytywania aplikacji. Wszystkie komponenty są domyślnie renderowane po stronie serwera i w razie potrzeby możesz wyłączyć tę opcję.