Podział kodu z dynamicznymi importami w Next.js

Jak przyspieszyć działanie aplikacji Next.js za pomocą dzielenia kodu i inteligentnych strategii wczytywania.

Data publikacji: 8 listopada 2019 r.

Dowiedz się więcej o różnych typach dzielenia kodu i o tym, jak używać importów dynamicznych, aby przyspieszyć działanie aplikacji Next.js.

Dzielenie kodu na podstawie tras i komponentów

Domyślnie Next.js dzieli JavaScript na osobne fragmenty dla każdej trasy. Gdy użytkownicy wczytują aplikację, Next.js wysyła tylko kod potrzebny do początkowej ścieżki. Gdy użytkownicy poruszają się po aplikacji, pobierają fragmenty powiązane z innymi ścieżkami. Dzielenie kodu na poziomie przekierowań minimalizuje ilość skryptu, który musi być analizowany i kompilowany jednocześnie, co skutkuje szybszym wczytywaniem stron.

Podział kodu na podstawie trasy to dobra opcja domyślna, ale możesz jeszcze bardziej zoptymalizować proces wczytywania, dzieląc kod na poziomie komponentu. Jeśli w aplikacji masz duże komponenty, warto podzielić je na osobne części. Dzięki temu duże komponenty, które nie są kluczowe lub są renderowane tylko w przypadku określonych interakcji użytkownika (np. kliknięcia przycisku), mogą być ładowane z opóźnieniem.

Next.js obsługuje dynamiczneimport(), które umożliwia dynamiczne importowanie modułów JavaScript (w tym komponentów React) i wczytywanie każdego importu jako osobnego fragmentu. Umożliwia to dzielenie kodu na poziomie komponentów i kontrolowanie ładowania zasobów, dzięki czemu użytkownicy pobierają tylko kod potrzebny do wyświetlenia części witryny, którą przeglądają. W Next.js te komponenty są domyślnie renderowane po stronie serwera.

Dynamiczne importy w praktyce

Ten post zawiera kilka wersji przykładowej aplikacji, która składa się z prostej strony z jednym przyciskiem. Gdy klikniesz przycisk, zobaczysz uroczego szczeniaka. Przechodząc przez kolejne wersje aplikacji, zobaczysz, czym importy dynamiczne różnią się od importów statycznych i jak z nich korzystać.

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

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

Aby zobaczyć, jak Next.js łączy aplikację w pakiet, sprawdź log czasu sieci w Narzędziach deweloperskich:

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

  2. Aby otworzyć Narzędzia dla programistów, naciśnij Ctrl+Shift+J (lub Command+Option+J na Macu).

  3. Kliknij kartę Sieć.

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

  5. Odśwież stronę.

Gdy wczytasz stronę, cały niezbędny kod, w tym komponent Puppy.js, zostanie spakowany w index.js:

Karta Sieć w Narzędziach deweloperskich pokazująca 6 plików JavaScript: index.js, app.js, webpack.js, main.js, 0.js i plik dll (biblioteka linków dynamicznych).

Gdy klikniesz przycisk Click me (Kliknij mnie), na karcie Sieć pojawi się tylko żądanie pliku JPEG z szczeniakiem:

Karta Sieć w Narzędziach deweloperskich po kliknięciu przycisku, na której widać te same 6 plików JavaScript i 1 obraz.

Wadą tego podejścia jest to, że nawet jeśli użytkownicy nie klikną przycisku, aby zobaczyć szczeniaka, muszą załadować komponent Puppy, ponieważ jest on uwzględniony w index.js. W tym prostym przykładzie nie ma to większego znaczenia, ale w rzeczywistych aplikacjach wczytywanie dużych komponentów tylko wtedy, gdy jest to konieczne, często stanowi ogromne ulepszenie.

Teraz sprawdź drugą wersję aplikacji, w której import statyczny został zastąpiony importem dynamicznym. Next.js zawiera next/dynamic, co umożliwia używanie dynamicznych importów w przypadku dowolnych komponentów w Next:

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

// ...

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

Aby sprawdzić ślad sieci, wykonaj czynności z pierwszego przykładu.

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

Narzędzia deweloperskie – sieć pokazują te same 6 plików JavaScript, z tym że plik index.js jest teraz o 0,5 KB mniejszy.

Komponent Puppy znajduje się teraz w osobnym bloku 1.js, który jest wczytywany tylko wtedy, gdy naciśniesz przycisk:

Karta Sieć w Narzędziach deweloperskich po kliknięciu przycisku. Widoczny jest dodatkowy plik 1.js i obraz dodany na dole listy plików.

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

Importy dynamiczne z niestandardowym wskaźnikiem wczytywania

Podczas leniwego wczytywania zasobów warto podać wskaźnik wczytywania na wypadek opóźnień. W Next.js możesz to zrobić, podając dodatkowy argument funkcji dynamic():

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

Aby zobaczyć wskaźnik ładowania w działaniu, zasymuluj wolne połączenie sieciowe w Narzędziach deweloperskich:

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

  2. Aby otworzyć Narzędzia dla programistów, naciśnij Ctrl+Shift+J (lub Command+Option+J na Macu).

  3. Kliknij kartę Sieć.

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

  5. Z listy Ograniczanie przepustowości wybierz Szybkie 3G.

  6. Naciśnij przycisk Kliknij mnie.

Teraz, gdy klikniesz przycisk, wczytanie komponentu zajmie trochę czasu, a w tym czasie aplikacja wyświetli komunikat „Wczytywanie…”.

Ciemny ekran z tekstem

Importy dynamiczne bez SSR

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

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

Podsumowanie

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