Podział kodu z dynamicznymi importami w Next.js

Jak przyspieszyć działanie aplikacji Next.js dzięki podziałowi kodu i strategiom inteligentnego ładowania.

Czego się nauczysz?

W tym poście opisujemy różne typy dzielenia kodu oraz wyjaśniamy, jak używać dynamicznych importów, aby przyspieszyć działanie aplikacji Next.js.

Dzielenie kodu na podstawie trasy i komponentu

Domyślnie Next.js dzieli kod JavaScript na osobne fragmenty dla każdej ścieżki. Gdy użytkownicy wczytują Twoją aplikację, Next.js wysyła tylko kod potrzebny do początkowej trasy. Podczas poruszania się po aplikacji użytkownicy pobierają fragmenty powiązane z innymi trasami. Dzielenie kodu na podstawie trasy minimalizuje ilość kodu, który musi zostać przeanalizowany i skompilowany naraz, co powoduje szybsze wczytywanie stron.

Dzielenie kodu na podstawie trasy jest dobrym domyślnym ustawieniem, 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 fragmenty. Dzięki temu duże komponenty, które nie są kluczowe lub renderują się tylko w przypadku określonych interakcji użytkownika (np. kliknięcia przycisku), mogą być ładowane z opóźnieniem.

Next.js obsługuje dynamiczne import(), co pozwala na importowanie modułów JavaScript (w tym komponentów React) dynamicznie i wczytywanie każdego importu jako osobnego fragmentu. Umożliwia to dzielenie kodu na poziomie komponentu i kontrolowanie wczytywania zasobów, tak aby użytkownicy pobierali tylko kod potrzebny do wyświetlania danej części witryny. W Next.js te komponenty są domyślnie renderowane po stronie serwera (SSR).

Importowanie dynamiczne w działaniu

Ten post zawiera kilka wersji przykładowej aplikacji, która składa się z jednej prostej strony z jednym przyciskiem. Po kliknięciu przycisku zobaczysz słodki szczeniaczek. W każdej wersji aplikacji zobaczysz, czym importy dynamiczne różnią się od statycznych oraz jak z nimi pracować.

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

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

Aby zobaczyć, jak Next.js łączy aplikację w pakiety, sprawdź ślad sieciowy w Narzędziach deweloperskich:

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

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

  3. Kliknij kartę Sieć.

  4. Zaznacz pole wyboru Disable cache (Wyłącz pamięć podręczną).

  5. Odśwież stronę.

Po załadowaniu strony cały niezbędny kod, włącznie z komponentem Puppy.js, jest w pakiecie index.js:

Karta Sieć w DevTools, na której widać 6 plików JavaScriptu: index.js, app.js, webpack.js, main.js, 0.js i plik DLL (dynamiczna biblioteka linków).

Gdy klikniesz przycisk Kliknij mnie, do karty Sieć zostanie dodana tylko prośba o plik JPEG z szczeniaczkiem:

Karta Sieć w DevTools po kliknięciu przycisku, na której widać te same 6 plików JavaScriptu i 1 obraz.

Minusem tego podejścia jest to, że nawet jeśli użytkownicy nie klikną przycisku, aby zobaczyć szczeniaka, muszą wczytać komponent Puppy, ponieważ jest on zawarty w komponencie index.js. W tym małym przykładzie nie ma to większego znaczenia, ale w rzeczywistych zastosowaniach często wczytywanie dużych komponentów tylko wtedy, gdy jest to konieczne, powoduje znaczną poprawę wydajności.

Teraz sprawdź drugą wersję aplikacji, w której import statyczny został zastąpiony importem dynamicznym. Next.js zawiera next/dynamic, dzięki czemu możesz używać importów dynamicznych 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 opisane w pierwszym przykładzie.

Podczas pierwszego wczytywania aplikacji pobierany jest tylko index.js. Tym razem plik jest mniejszy o 0,5 KB (z 37,9 KB do 37,4 KB), ponieważ nie zawiera kodu komponentu Puppy:

Sieć w Narzędziach dla programistów, która pokazuje te same 6 plików JavaScriptu, z tym że plik index.js jest teraz mniejszy o 0,5 KB.

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

Karta Sieć w DevTools po kliknięciu przycisku, pokazująca dodatkowy plik 1.js i obraz dodany na dole listy plików.

W przypadku rzeczywistych aplikacji komponenty są często znacznie większe, a ich wczytywanie z opóźnieniem może zmniejszyć początkowy ładunek kodu JavaScript o setki kilobajtów.

Importy dynamiczne z niestandardowym wskaźnikiem wczytywania

W przypadku wczytywania zasobów z opóźnieniem warto wyświetlić 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ć działanie wskaźnika wczytywania, przeprowadź symulację wolnego połączenia sieciowego w Narzędziach deweloperskich:

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

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

  3. Kliknij kartę Sieć.

  4. Zaznacz pole wyboru Disable cache (Wyłącz pamięć podręczną).

  5. Na liście Throttling (Throttling) wybierz Fast 3G (Szybkie 3G).

  6. Naciśnij przycisk Kliknij mnie.

Gdy klikniesz przycisk, komponent będzie się wczytywać, a aplikacja wyświetli komunikat „Ładowanie…”.

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 importu dynamicznego usługa Next.js umożliwia podział 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, ale w razie potrzeby możesz wyłączyć tę opcję.