Aufschlüsselung sperriger Builds mit Netlify und Next.js
Veröffentlicht: 2022-03-10Dieser Artikel wurde freundlicherweise von unseren lieben Freunden bei Netlify unterstützt, die eine vielfältige Gruppe unglaublicher Talente aus der ganzen Welt sind und eine Plattform für Webentwickler bieten, die die Produktivität vervielfacht. Danke!
Eines der größten Probleme bei der Arbeit mit statisch generierten Websites sind die schrittweise langsameren Builds, wenn Ihre App wächst. Dies ist ein unvermeidliches Problem, mit dem jeder Stack irgendwann konfrontiert ist, und es kann an verschiedenen Stellen auftreten, je nachdem, mit welcher Art von Produkt Sie arbeiten.
Wenn Ihre App beispielsweise beim Generieren des Bereitstellungsartefakts über mehrere Seiten (Ansichten, Routen) verfügt, wird jede dieser Routen zu einer Datei. Sobald Sie Tausende erreicht haben, fragen Sie sich, wann Sie die Bereitstellung vornehmen können, ohne im Voraus planen zu müssen. Dieses Szenario ist auf E-Commerce-Plattformen oder Blogs üblich, die bereits einen großen Teil des Webs ausmachen, aber nicht alles. Routen sind jedoch nicht der einzig mögliche Engpass.
Auch eine ressourcenintensive App wird irgendwann diesen Wendepunkt erreichen. Viele statische Generatoren führen eine Asset-Optimierung durch, um die beste Benutzererfahrung zu gewährleisten. Ohne Build-Optimierungen (inkrementelle Builds, Caching, dazu kommen wir bald) wird auch dies irgendwann nicht mehr zu bewältigen sein – denken Sie daran, alle Bilder auf einer Website durchzugehen: Größe ändern, löschen und/oder immer wieder neue Dateien erstellen. Und wenn das alles erledigt ist: Denken Sie daran, dass Jamstack unsere Apps von den Rändern des Content Delivery Network aus bedient. Wir müssen also immer noch Dinge von dem Server, auf dem sie kompiliert wurden, an die Ränder des Netzwerks verschieben.
Darüber hinaus gibt es noch eine weitere Tatsache: Daten sind oft dynamisch, was bedeutet, dass es einige Sekunden, einige Minuten oder sogar eine Stunde dauern kann, wenn wir unsere App erstellen und bereitstellen. In der Zwischenzeit dreht sich die Welt weiter, und wenn wir Daten von woanders abrufen, ist unsere App zwangsläufig veraltet. Inakzeptabel! Zum Aktualisieren erneut erstellen!
Einmal erstellen, bei Bedarf aktualisieren
Das Lösen von Bulky Builds ist seit einiger Zeit für praktisch alle Jamstack-Plattformen, -Frameworks oder -Dienste oberstes Gebot. Viele Lösungen drehen sich um inkrementelle Builds. In der Praxis bedeutet dies, dass Builds so sperrig sein werden wie die Unterschiede, die sie gegenüber der aktuellen Bereitstellung aufweisen.
Das Definieren eines Diff- Algorithmus ist jedoch keine leichte Aufgabe. Damit der Endbenutzer tatsächlich von dieser Verbesserung profitieren kann, müssen Cache-Invalidierungsstrategien berücksichtigt werden. Um es kurz zu machen: Wir möchten den Cache nicht für eine Seite oder ein Asset ungültig machen, das sich nicht geändert hat.
Next.js hat die inkrementelle statische Regeneration ( ISR ) entwickelt. Im Wesentlichen ist es eine Möglichkeit, für jede Route anzugeben, wie oft sie neu erstellt werden soll. Unter der Haube vereinfacht es einen Großteil der Arbeit auf der Serverseite. Weil sich jede Route (dynamisch oder nicht) in einem bestimmten Zeitrahmen selbst neu erstellt und einfach perfekt in das Jamstack-Axiom passt, den Cache bei jedem Build ungültig zu machen. Betrachten Sie es als den max-age
Header, aber für Routen in Ihrer Next.js-App.
Um Ihre Anwendung zum Laufen zu bringen, ist ISR nur eine Konfigurationseigenschaft entfernt. Gehen Sie in Ihrer Routenkomponente (im /pages
-Verzeichnis) zu Ihrer getStaticProps
Methode und fügen Sie den revalidate
zum Rückgabeobjekt hinzu:
export async function getStaticProps() { const { limit, count, pokemons } = await fetchPokemonList() return { props: { limit, count, pokemons, }, revalidate: 3600 // seconds } }
Das obige Snippet stellt sicher, dass meine Seite stündlich neu erstellt wird und mehr Pokémon zum Anzeigen abrufen.
Wir erhalten immer noch die Bulk-Builds hin und wieder (bei der Ausgabe einer neuen Bereitstellung). Aber dadurch können wir Inhalte vom Code entkoppeln, indem wir Inhalte in ein Content Management System (CMS) verschieben, können wir Informationen in wenigen Sekunden aktualisieren, unabhängig davon, wie groß unsere Anwendung ist. Auf Wiedersehen zu Webhooks zum Aktualisieren von Tippfehlern!
On-Demand-Builder
Netlify hat kürzlich On-Demand Builders eingeführt, was ihr Ansatz zur Unterstützung von ISR für Next.js ist, aber auch über Frameworks wie Eleventy und Nuxt hinweg funktioniert. In der vorherigen Sitzung haben wir festgestellt, dass ISR ein großer Schritt in Richtung kürzerer Build-Zeiten war und einen erheblichen Teil der Anwendungsfälle angesprochen hat. Trotzdem waren die Vorbehalte da:
- Vollständig baut auf Continuous Deployment auf.
Die inkrementelle Phase findet erst nach der Bereitstellung und für die Daten statt. Es ist nicht möglich, Code inkrementell zu versenden - Inkrementelle Builds sind ein Produkt der Zeit.
Der Cache wird auf Zeitbasis ungültig gemacht. Daher können unnötige Builds auftreten oder erforderliche Updates können länger dauern, je nach dem im Code festgelegten Verlängerungszeitraum.
Die neue Bereitstellungsinfrastruktur von Netlify ermöglicht es Entwicklern, Logik zu erstellen, um zu bestimmen, welche Teile ihrer App auf der Bereitstellung aufbauen und welche Teile zurückgestellt werden (und wie sie zurückgestellt werden).
- Kritisch
Es ist keine Aktion erforderlich. Alles, was Sie bereitstellen, basiert auf Push . - Aufgeschoben
Ein bestimmter Teil der App wird bei der Bereitstellung nicht erstellt, er wird verschoben, um bei Bedarf erstellt zu werden, wenn die erste Anforderung auftritt, und wird dann wie jede andere Ressource seines Typs zwischengespeichert.
Erstellen eines On-Demand-Builders
Fügen Sie zunächst ein netlify/functions-Paket als devDependency
zu Ihrem Projekt hinzu:
yarn add -D @netlify/functions
Sobald dies erledigt ist, ist es genauso wie das Erstellen einer neuen Netlify-Funktion. Wenn Sie kein bestimmtes Verzeichnis dafür festgelegt haben, gehen Sie zu netlify/functions/
und erstellen Sie eine Datei mit einem beliebigen Namen für Ihren Builder.
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)
Wie Sie dem obigen Snippet entnehmen können, unterscheidet sich der On-Demand-Builder von einer regulären Netlify-Funktion, da er seinen Handler in eine builder()
Methode einschließt. Diese Methode verbindet unsere Funktion mit den Build-Aufgaben. Und das ist alles, was Sie brauchen, um einen Teil Ihrer Anwendung nur bei Bedarf zum Erstellen aufzuschieben. Kleine inkrementelle Builds von Anfang an!
Next.js auf Netlify
Um eine Next.js-App auf Netlify zu erstellen, gibt es 2 wichtige Plugins, die man hinzufügen sollte, um im Allgemeinen ein besseres Erlebnis zu haben: Netlify Plugin Cache Next.js und Essential Next-on-Netlify. Ersteres speichert Ihr NextJS effizienter und Sie müssen es selbst hinzufügen, während letzteres ein paar geringfügige Anpassungen an der Bauweise von Next.js vornimmt, damit es besser zu Netlify passt und standardmäßig für jedes neue Projekt verfügbar ist, das Netlify identifizieren kann mit Next.js.
On-Demand-Builder mit Next.js
Leistung aufbauen, Leistung bereitstellen, Caching, Entwicklererfahrung. Dies sind alles sehr wichtige Themen, aber es ist viel – und es braucht Zeit, sie richtig einzurichten. Dann kommen wir zu dieser alten Diskussion über die Konzentration auf die Entwicklererfahrung anstelle der Benutzererfahrung. Das ist die Zeit, in der Dinge an einen versteckten Ort in einem Rückstand geraten, um vergessen zu werden. Nicht wirklich.
Netlify steht hinter Ihnen. In nur wenigen Schritten können wir die volle Leistungsfähigkeit des Jamstack in unserer Next.js-App nutzen. Es ist an der Zeit, die Ärmel hochzukrempeln und jetzt alles zusammenzufügen.
Vorgerenderte Pfade definieren
Wenn Sie bereits mit der statischen Generierung in Next.js gearbeitet haben, haben Sie wahrscheinlich schon von der getStaticPaths
Methode gehört. Diese Methode ist für dynamische Routen gedacht (Seitenvorlagen, die eine Vielzahl von Seiten rendern). Ohne zu sehr auf die Feinheiten dieser Methode einzugehen, ist es wichtig zu beachten, dass der Rückgabetyp ein Objekt mit 2 Schlüsseln ist, wie in unserem Proof-of-Concept ist dies eine [Pokemon]dynamische Routendatei:
export async function getStaticPaths() { return { paths: [], fallback: 'blocking', } }
-
paths
ist einarray
, das alle Pfade enthält, die zu dieser Route passen, die vorab gerendert wird -
fallback
hat 3 mögliche Werte: Blocking,true
oderfalse
In unserem Fall bestimmt unser getStaticPaths
:
- Es werden keine Pfade vorgerendert;
- Immer wenn diese Route aufgerufen wird, stellen wir kein Fallback -Template bereit, wir rendern die Seite bei Bedarf und lassen den Benutzer warten, wodurch die App daran gehindert wird, irgendetwas anderes zu tun.
Stellen Sie bei der Verwendung von On-Demand-Buildern sicher, dass Ihre Fallback-Strategie die Ziele Ihrer App erfüllt. Die offiziellen Next.js-Dokumente: Fallback-Dokumente sind sehr nützlich.
Vor On-Demand Builders war unser getStaticPaths
etwas anders:
export async function getStaticPaths() { const { pokemons } = await fetchPkmList() return { paths: pokemons.map(({ name }) => ({ params: { pokemon: name } })), fallback: false, } }
Wir sammelten eine Liste aller Pokémon-Seiten, die wir haben wollten, ordneten alle pokemon
Objekte nur einer string
mit dem Pokémon-Namen zu und leiteten das { params }
-Objekt, das es trägt, an getStaticProps
. Unser fallback
wurde auf „ false
“ gesetzt, denn wenn eine Route nicht übereinstimmt, wollten wir, dass Next.js eine 404: Not Found
-Seite ausgibt.
Sie können beide auf Netlify bereitgestellten Versionen überprüfen:
- Mit On-Demand Builder: Code, live
- Vollständig statisch generiert: Code, live
Der Code ist auch Open-Source auf Github und Sie können ihn einfach selbst bereitstellen, um die Build-Zeiten zu überprüfen. Und mit dieser Warteschlange gleiten wir zu unserem nächsten Thema.
Bauzeiten
Wie oben erwähnt, ist die vorherige Demo eigentlich ein Proof-of-Concept , nichts ist wirklich gut oder schlecht, wenn wir es nicht messen können. Für unsere kleine Studie bin ich zur PokeAPI gegangen und habe mich entschieden, alle Pokémon zu fangen.
Aus Gründen der Reproduzierbarkeit habe ich unsere Anfrage begrenzt (auf 1000
). Diese befinden sich nicht wirklich alle in der API, aber es erzwingt, dass die Anzahl der Seiten für alle Builds gleich ist, unabhängig davon, ob die Dinge zu irgendeinem Zeitpunkt aktualisiert werden.
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, } }
Und dann beide Versionen in getrennten Zweigen an Netlify gefeuert, dank Preview-Deployments können sie im Grunde in derselben Umgebung koexistieren. Um den Unterschied zwischen beiden Methoden wirklich zu bewerten, war der ODB-Ansatz extrem, es wurden keine Seiten für diese dynamische Route vorgerendert. Obwohl es für reale Szenarien nicht empfohlen wird (Sie sollten Ihre verkehrsreichen Routen vorab rendern), markiert es deutlich den Bereich der Leistungsverbesserung während der Erstellungszeit, die wir mit diesem Ansatz erreichen können.
Strategie | Seitenzahl | Anzahl der Vermögenswerte | Bauzeit | Gesamtbereitstellungszeit |
---|---|---|---|---|
Vollständig statisch generiert | 1002 | 1005 | 2 Minuten 32 Sekunden | 4 Minuten 15 Sekunden |
On-Demand-Builder | 2 | 0 | 52 Sekunden | 52 Sekunden |
Die Seiten in unserer kleinen PokeDex-App sind ziemlich klein, die Bildressourcen sind sehr mager, aber die Gewinne bei der Bereitstellungszeit sind sehr signifikant. Wenn eine App eine mittlere bis große Anzahl an Routen hat, lohnt es sich auf jeden Fall, die ODB-Strategie in Betracht zu ziehen.
Es macht Ihre Bereitstellungen schneller und damit zuverlässiger. Der Leistungseinbruch tritt nur bei der allerersten Anfrage auf, ab der nachfolgenden Anfrage wird die gerenderte Seite direkt auf dem Edge zwischengespeichert, sodass die Leistung genau der von Fully Static Generated entspricht.
Die Zukunft: Distributed Persistent Rendering
Am selben Tag, an dem On-Demand-Builder angekündigt und für den frühen Zugriff freigegeben wurden, veröffentlichte Netlify auch seinen Request for Comments on Distributed Persistent Rendering (DPR).
DPR ist der nächste Schritt für On-Demand-Builder. Es profitiert von schnelleren Builds, indem es solche asynchronen Erstellungsschritte nutzt und die Assets dann zwischenspeichert, bis sie tatsächlich aktualisiert werden. Keine vollständigen Builds mehr für eine Website mit 10.000 Seiten. DPR ermöglicht den Entwicklern die vollständige Kontrolle über die Build- und Bereitstellungssysteme durch solides Caching und die Verwendung von On-Demand-Buildern.
Stellen Sie sich dieses Szenario vor: Eine E-Commerce-Website hat 10.000 Produktseiten, was bedeutet, dass es ungefähr 2 Stunden dauern würde, die gesamte Anwendung für die Bereitstellung zu erstellen. Wie schmerzhaft das ist, brauchen wir nicht zu diskutieren.
Mit DPR können wir die Top-500-Seiten festlegen, die bei jeder Bereitstellung erstellt werden sollen. Unsere verkehrsreichsten Seiten sind immer für unsere Benutzer bereit. Aber wir sind ein Shop, dh jede Sekunde zählt. Für die anderen 9500 Seiten können wir also einen Post-Build-Hook setzen, um ihre Builder auszulösen – indem wir die verbleibenden unserer Seiten asynchron bereitstellen und sofort zwischenspeichern. Keine Benutzer wurden verletzt, unsere Website wurde mit dem schnellstmöglichen Build aktualisiert, und alles andere, was nicht im Cache vorhanden war, wurde dann gespeichert.
Fazit
Obwohl viele der Diskussionspunkte in diesem Artikel konzeptionell waren und die Implementierung definiert werden muss, bin ich gespannt auf die Zukunft des Jamstack. Die Fortschritte, die wir als Community machen, drehen sich um die Endbenutzererfahrung.
Was halten Sie von Distributed Persistent Rendering? Haben Sie On-Demand Builder in Ihrer Anwendung ausprobiert? Lass es mich in den Kommentaren wissen oder ruf mich auf Twitter an. Ich bin wirklich neugierig!
Verweise
- „Ein vollständiger Leitfaden zur inkrementellen statischen Regeneration (ISR) mit Next.js“, Lee Robinson
- „Schnellere Builds für große Sites auf Netlify mit On-Demand-Buildern“, Asavari Tayal, Netlify Blog
- „Distributed Persistent Rendering: Ein neuer Jamstack-Ansatz für schnellere Builds“, Matt Biilmann, Netlify Blog
- „Distributed Persistent Rendering (DPR)“, Cassidy Williams, GitHub