Podział kodu z dynamicznymi importami w Next.js

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

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.

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. Gdy użytkownicy poruszają się po aplikacji, pobierają fragmenty powiązane z innymi ścieżkami. Dzielenie kodu na podstawie trasy minimalizuje ilość kodu, który musi zostać przeanalizowany i skompilowany jednocześnie, co powoduje szybsze wczytywanie stron.

Dzielenie kodu na podstawie trasy jest dobrym domyślnym ustawieniem, ale możesz dodatkowo 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 akcji

Ten post zawiera kilka wersji przykładowej aplikacji, która składa się z jednej prostej strony z jednym przyciskiem. Gdy klikniesz przycisk, zobaczysz uroczego szczeniaczka. W każdej wersji aplikacji zobaczysz, czym importy dynamiczne różnią się od importów 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 sprawdzić, jak Next.js łączy aplikację, sprawdź ślad sieci w Narzędziach deweloperskich:

  1. Aby wyświetlić podgląd strony, kliknij Wyświetl aplikację. Następnie kliknij Pełny ekran peł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ę.

Gdy wczytasz stronę, cały niezbędny kod, w tym komponent Puppy.js, jest zawarty w elementach 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 JavaScript o setki kilobajtów.

Importowanie dynamiczne z niestandardowym wskaźnikiem wczytywania

W przypadku wczytywania opóźnionego zasobów 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 ładowania, symuluj powolne połączenie z internetem w DevTools:

  1. Aby wyświetlić podgląd strony, kliknij Wyświetl aplikację. Następnie kliknij Pełny ekran peł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. Z listy Ograniczanie wybierz 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 importowania dynamicznego Next.js umożliwia dzielenie kodu na poziomie komponentu, co może zminimalizować ładunki JavaScript i przyspieszyć wczytywanie aplikacji. Wszystkie komponenty są domyślnie renderowane po stronie serwera, ale w razie potrzeby możesz wyłączyć tę opcję.