Rozkładanie dużych kompilacji za pomocą Netlify i Next.js
Opublikowany: 2022-03-10Ten artykuł został życzliwie poparty przez naszych drogich przyjaciół z Netlify, którzy są zróżnicowaną grupą niesamowitych talentów z całego świata i oferują platformę dla twórców stron internetowych, która zwielokrotnia produktywność. Dziękuję Ci!
Jedną z największych bolączek pracy ze statycznie generowanymi witrynami internetowymi jest stopniowe wolniejsze kompilacje wraz z rozwojem aplikacji. Jest to nieunikniony problem, na który napotyka każdy stos w pewnym momencie i może uderzyć z różnych punktów, w zależności od rodzaju produktu, z którym pracujesz.
Na przykład jeśli aplikacja ma wiele stron (widoków, tras) podczas generowania artefaktu wdrażania, każda z tych tras staje się plikiem. Następnie, gdy osiągniesz tysiące, zaczynasz się zastanawiać, kiedy możesz wdrożyć bez konieczności planowania z wyprzedzeniem. Ten scenariusz jest powszechny na platformach e-commerce lub blogach, które już stanowią dużą część sieci, ale nie całość. Trasy nie są jednak jedynym możliwym wąskim gardłem.
Aplikacja wymagająca dużej ilości zasobów również w końcu osiągnie ten punkt zwrotny. Wiele generatorów statycznych przeprowadza optymalizację zasobów, aby zapewnić jak najlepsze wrażenia użytkownika. Bez optymalizacji kompilacji (kompilacje przyrostowe, buforowanie, wkrótce do nich dojdziemy) w końcu stanie się to również niemożliwe do zarządzania — pomyśl o przejrzeniu wszystkich obrazów w witrynie internetowej: zmianie rozmiaru, usuwaniu i/lub tworzeniu nowych plików w kółko. I kiedy już to zrobisz: pamiętaj, że Jamstack obsługuje nasze aplikacje z krańców sieci dostarczania treści . Więc nadal musimy przenieść rzeczy z serwera, na którym zostały skompilowane, na brzegi sieci.
Do tego dochodzi jeszcze jeden fakt: dane są często dynamiczne, co oznacza, że kiedy budujemy naszą aplikację i ją wdrażamy, może to zająć kilka sekund, kilka minut, a nawet godzinę. Tymczasem świat wciąż się kręci, a jeśli pobieramy dane z innych źródeł, nasza aplikacja na pewno stanie się przestarzała. Gorszący! Zbuduj ponownie, aby zaktualizować!
Buduj raz, aktualizuj w razie potrzeby
Od jakiegoś czasu rozwiązywanie Bulky Builds było na pierwszym miejscu w zasadzie dla każdej platformy, frameworka lub usługi Jamstack. Wiele rozwiązań opiera się na kompilacjach przyrostowych. W praktyce oznacza to, że kompilacje będą tak obszerne, jak różnice, które niosą w stosunku do obecnego wdrożenia.
Zdefiniowanie algorytmu porównania nie jest jednak łatwym zadaniem. Aby użytkownik końcowy mógł faktycznie skorzystać z tego ulepszenia, należy wziąć pod uwagę strategie unieważniania pamięci podręcznej. Krótko mówiąc: nie chcemy unieważniać pamięci podręcznej dla strony lub zasobu, który się nie zmienił.
Next.js wymyślił przyrostową regenerację statyczną ( ISR ). Zasadniczo jest to sposób na zadeklarowanie dla każdej trasy, jak często chcemy ją odbudowywać. Pod maską upraszcza wiele pracy po stronie serwera. Ponieważ każda trasa (dynamiczna lub nie) odbuduje się w określonych ramach czasowych i po prostu idealnie pasuje do aksjomatu Jamstack unieważniania pamięci podręcznej przy każdej kompilacji. Pomyśl o tym jako o nagłówku max-age
, ale o trasach w aplikacji Next.js.
Aby uruchomić aplikację, ISR po prostu od właściwości konfiguracyjnej. W komponencie trasy (w katalogu /pages
) przejdź do metody getStaticProps
i dodaj klucz revalidate
do zwracanego obiektu:
export async function getStaticProps() { const { limit, count, pokemons } = await fetchPokemonList() return { props: { limit, count, pokemons, }, revalidate: 3600 // seconds } }
Powyższy fragment sprawi, że moja strona będzie odbudowywała się co godzinę i będzie pobierać więcej Pokemonów do wyświetlenia.
Nadal otrzymujemy kompilacje zbiorcze od czasu do czasu (podczas wydawania nowego wdrożenia). Ale to pozwala nam oddzielić treść od kodu, przenosząc treść do systemu zarządzania treścią (CMS), możemy zaktualizować informacje w kilka sekund, niezależnie od tego, jak duża jest nasza aplikacja. Pożegnaj się z webhookami do aktualizacji literówek!
Konstruktorzy na żądanie
Netlify niedawno uruchomiło On-Demand Builders, które jest ich podejściem do obsługi ISR dla Next.js, ale działa również w ramach frameworków, w tym Eleveny i Nuxt. W poprzedniej sesji ustaliliśmy, że ISR był wielkim krokiem w kierunku skrócenia czasu kompilacji i zajęliśmy się znaczną częścią przypadków użycia. Niemniej jednak istniały zastrzeżenia:
- Pełne kompilacje po ciągłym wdrożeniu.
Etap przyrostowy ma miejsce dopiero po wdrożeniu i dla danych. Nie jest możliwe stopniowe wysyłanie kodu - Kompilacje przyrostowe są produktem czasu.
Pamięć podręczna jest unieważniana na podstawie czasu. Dlatego mogą wystąpić niepotrzebne kompilacje lub potrzebne aktualizacje mogą potrwać dłużej w zależności od okresu rewalidacji ustawionego w kodzie.
Nowa infrastruktura wdrożeniowa Netlify pozwala programistom tworzyć logikę, aby określić, które elementy ich aplikacji będą oparte na wdrożeniu, a które zostaną odroczone (i jak zostaną odroczone).
- Krytyczny
Żadne działanie nie jest potrzebne. Wszystko, co wdrożysz, zostanie zbudowane w trybie push . - Odroczony
Określona część aplikacji nie zostanie zbudowana po wdrożeniu, zostanie odroczona do zbudowania na żądanie za każdym razem, gdy wystąpi pierwsze żądanie, a następnie zostanie zbuforowana jako każdy inny zasób tego typu.
Tworzenie kreatora na żądanie
Przede wszystkim dodaj pakiet netlify/functions jako devDependency
do swojego projektu:
yarn add -D @netlify/functions
Gdy to zrobisz, jest to dokładnie to samo, co utworzenie nowej funkcji Netlify. Jeśli nie ustawiłeś dla nich określonego katalogu, przejdź do netlify/functions/
i utwórz plik o dowolnej nazwie dla swojego kreatora.
import type { Handler } from '@netlify/functions' import { builder } from '@netlify/functions' const myHandler: Handler = async (event, context) => { return { statusCode: 200, body: JSON.stringify({ message: 'Built on-demand! ' }), } } export const handler = builder(myHandler)
Jak widać z powyższego fragmentu, konstruktor na żądanie oddziela się od zwykłej funkcji Netlify, ponieważ owija swoją procedurę obsługi wewnątrz metody builder()
. Ta metoda łączy naszą funkcję z zadaniami budowania. I to wszystko, czego potrzebujesz, aby część aplikacji została odroczona do zbudowania tylko wtedy, gdy jest to konieczne. Małe kompilacje przyrostowe od samego początku!
Next.js na Netlify
Aby zbudować aplikację Next.js na Netlify, należy dodać 2 ważne wtyczki, aby uzyskać ogólnie lepsze wrażenia: Netlify Plugin Cache Next.js i Essential Next-on-Netlify. Pierwsza z nich bardziej efektywnie buforuje Twój NextJS i musisz ją dodać samodzielnie, podczas gdy druga wprowadza kilka drobnych zmian w sposobie budowania architektury Next.js, aby lepiej pasowała do Netlify i jest domyślnie dostępna dla każdego nowego projektu, który Netlify może zidentyfikować. przy użyciu Next.js.
Konstruktorzy na żądanie z Next.js
Wydajność budowania, wydajność wdrażania, buforowanie, doświadczenie programisty. To wszystko są bardzo ważne tematy, ale jest ich dużo — a ich prawidłowe skonfigurowanie wymaga czasu. Następnie dochodzimy do starej dyskusji o skupieniu się na doświadczeniu programisty zamiast doświadczenia użytkownika. W tym czasie rzeczy trafiają do ukrytego miejsca w zaległościach, aby zapomnieć. Nie całkiem.
Netlify wspiera Cię. W zaledwie kilku krokach możemy w pełni wykorzystać możliwości Jamstack w naszej aplikacji Next.js. Czas zakasać rękawy i złożyć to wszystko razem.
Definiowanie wstępnie renderowanych ścieżek
Jeśli pracowałeś wcześniej z generowaniem statycznym w Next.js, prawdopodobnie słyszałeś o metodzie getStaticPaths
. Ta metoda jest przeznaczona dla tras dynamicznych (szablonów stron, które renderują szeroki zakres stron). Nie zagłębiając się zbytnio w zawiłości tej metody, należy zauważyć, że zwracany typ to obiekt z 2 kluczami, tak jak w naszym Proof-of-Concept będzie to plik trasy dynamicznej [Pokemon]:
export async function getStaticPaths() { return { paths: [], fallback: 'blocking', } }
-
paths
toarray
zawierająca wszystkie ścieżki pasujące do tej trasy, które zostaną wstępnie wyrenderowane -
fallback
ma 3 możliwe wartości: blocking,true
lubfalse
W naszym przypadku nasza getStaticPaths
określa:
- Żadne ścieżki nie zostaną wstępnie wyrenderowane;
- Za każdym razem, gdy ta trasa jest wywoływana, nie będziemy obsługiwać szablonu rezerwowego, wyrenderujemy stronę na żądanie i sprawimy, że użytkownik będzie czekał, blokując aplikację przed zrobieniem czegokolwiek innego.
Korzystając z narzędzi On-Demand Builders, upewnij się, że Twoja strategia awaryjna spełnia cele aplikacji, oficjalne dokumenty Next.js: dokumenty awaryjne są bardzo przydatne.
Przed budowniczymi na żądanie nasza getStaticPaths
była nieco inna:
export async function getStaticPaths() { const { pokemons } = await fetchPkmList() return { paths: pokemons.map(({ name }) => ({ params: { pokemon: name } })), fallback: false, } }
Zbieraliśmy listę wszystkich stron pokemonów, które zamierzaliśmy mieć, mapowaliśmy wszystkie obiekty pokemon
na tylko string
z nazwą pokemona i przekierowywaliśmy zwracając obiekt { params }
niosący go do getStaticProps
. Nasza fallback
była ustawiona na wartość false
, ponieważ jeśli trasa nie była dopasowana, chcieliśmy, aby Next.js wyrzucił stronę 404: Not Found
.
Możesz sprawdzić obie wersje wdrożone w Netlify:
- Z kreatorem na żądanie: kod, na żywo
- W pełni statyczne generowane: kod, live
Kod jest również open-source na Github i możesz go łatwo wdrożyć samodzielnie, aby sprawdzić czasy kompilacji. I w tej kolejce przechodzimy do następnego tematu.
Czasy kompilacji
Jak wspomniano powyżej, poprzednie demo jest w rzeczywistości dowodem koncepcji , nic nie jest naprawdę dobre ani złe, jeśli nie możemy zmierzyć. Do naszego małego badania przeszedłem do PokeAPI i postanowiłem złapać wszystkie pokemony.
Dla celów odtwarzalności ograniczyłem naszą prośbę (do 1000
). Nie są to tak naprawdę wszystkie w interfejsie API, ale wymusza to, że liczba stron będzie taka sama dla wszystkich kompilacji, niezależnie od tego, czy rzeczy zostaną zaktualizowane w dowolnym momencie.
export const fetchPkmList = async () => { const resp = await fetch(`${API}pokemon?limit=${LIMIT}`) const { count, results, }: { count: number results: { name: string url: string }[] } = await resp.json() return { count, pokemons: results, limit: LIMIT, } }
A następnie odpalili obie wersje w osobnych gałęziach do Netlify, dzięki wdrożeniom podglądowym mogą współistnieć w zasadzie w tym samym środowisku. Aby naprawdę ocenić różnicę między obiema metodami, podejście ODB było ekstremalne, żadne strony nie były wstępnie renderowane dla tej dynamicznej trasy. Chociaż nie jest to zalecane w rzeczywistych scenariuszach (będziesz chciał wstępnie renderować trasy o dużym natężeniu ruchu), wyraźnie wskazuje zakres poprawy wydajności w czasie budowy, jaki możemy osiągnąć dzięki temu podejściu.
Strategia | Numer stron | Liczba aktywów | Czas budowy | Całkowity czas wdrożenia |
---|---|---|---|---|
W pełni statyczne wygenerowane | 1002 | 1005 | 2 minuty 32 sekundy | 4 minuty 15 sekund |
Konstruktorzy na żądanie | 2 | 0 | 52 sekundy | 52 sekundy |
Strony w naszej małej aplikacji PokeDex są dość małe, zasoby graficzne są bardzo szczupłe, ale zysk z czasu wdrożenia jest bardzo znaczący. Jeśli aplikacja ma średnią lub dużą liczbę tras, zdecydowanie warto rozważyć strategię ODB.
Sprawia, że Twoje wdrożenia są szybsze, a tym samym bardziej niezawodne. Uderzenie wydajności ma miejsce tylko przy pierwszym żądaniu, od następnego żądania i dalej renderowana strona będzie buforowana bezpośrednio na Edge, dzięki czemu wydajność będzie dokładnie taka sama, jak w przypadku wygenerowanej w pełni statycznej.
Przyszłość: rozproszone renderowanie trwałe
Tego samego dnia ogłoszono i wprowadzono wczesny dostęp kreatorów On-Demand Builders, Netlify opublikowało również swoje Request for Comments on Distributed Persistent Rendering (DPR).
DPR to kolejny krok dla konstruktorów na żądanie. Wykorzystuje szybsze kompilacje, korzystając z takich asynchronicznych kroków budowania, a następnie buforując zasoby, dopóki nie zostaną faktycznie zaktualizowane. Nigdy więcej pełnych kompilacji dla witryny 10 tys. DPR daje programistom pełną kontrolę nad tworzeniem i wdrażaniem systemów poprzez solidne buforowanie i używanie On-Demand Builders.
Wyobraź sobie taki scenariusz: witryna e-commerce ma 10 tys. stron produktów, co oznacza, że zbudowanie całej aplikacji do wdrożenia zajęłoby około 2 godzin. Nie musimy się spierać, jak bardzo jest to bolesne.
Dzięki DPR możemy ustawić 500 najlepszych stron do tworzenia przy każdym wdrożeniu. Nasze strony o największym natężeniu ruchu są zawsze gotowe dla naszych użytkowników. Ale jesteśmy sklepem, czyli liczy się każda sekunda. Tak więc dla pozostałych 9500 stron możemy ustawić hak po kompilacji, aby wyzwolić ich twórców — asynchroniczne wdrożenie pozostałych naszych stron i natychmiastowe buforowanie. Żaden użytkownik nie został ranny, nasza strona została zaktualizowana o najszybszą możliwą wersję, a wszystko inne, co nie istniało w pamięci podręcznej, zostało następnie zapisane.
Wniosek
Chociaż wiele punktów dyskusji w tym artykule miało charakter koncepcyjny i implementacja ma zostać zdefiniowana, jestem podekscytowany przyszłością Jamstack. Postępy, które robimy jako społeczność, obracają się wokół doświadczenia użytkownika końcowego.
Jakie jest Twoje zdanie na temat rozproszonego renderowania trwałego? Czy wypróbowałeś w swojej aplikacji narzędzia On-Demand Builders? Daj znać więcej w komentarzach lub zadzwoń na Twitterze. Jestem naprawdę ciekawa!
Bibliografia
- „Kompletny przewodnik po przyrostowej regeneracji statycznej (ISR) za pomocą Next.js”, Lee Robinson
- „Szybsze kompilacje dla dużych witryn w Netlify z kreatorami na żądanie”, Asavari Tayal, blog Netlify
- „Rozproszone renderowanie trwałe: nowe podejście Jamstack do szybszych kompilacji”, Matt Biilmann, blog Netlify
- „Rozproszone renderowanie trwałe (DPR)”, Cassidy Williams, GitHub