In diesem TechUp wollen wir NextJS kennenlernen, die Konzepte und Möglichkeiten welches es bietet ausprobieren und eine Übersicht der unterschiedlichen Deployment-Möglichkeiten geben.
Next, Nest, Nuxt… Grundsätzlich gibt es verschiedenste Frameworks, welche auf NodeJS aufbauen und JavaScript, TypeScript oder eine andere Frontend-Sprache ins Backend bringen wollen.
Hier eine kurze Übersicht, um Verwirrungen zu vermeiden.
- NestJs - server side applications, Typescript, APIs
- NuxtJs - Plugin für VueJS, Developer Experience, sustainable development
- NextJs -
The React Framework For Production
React, SEO optimized, SSR für React
NextJs
Was ist NextJS?
NextJS ist eine Erweiterung für React, welche nach eigenen Aussagen die beste Developer-Experience mit den Features, welche man für Produktion benötigt, bietet. Technisch gesehen baut NextJS auf Node.js auf und erweitert React so um Server-Side Rendering und Static Side Generation. Hinter NextJS steht die Firma Vercel, der erste Release wurde 2016 veröffentlicht.
Nachfolgende lernen wir einige zentrale Funktionen von NextJS kennen, das Framework bietet aber weit mehr Funktionalität wie ein E-Commerce Starter Pack, Code Splitting und weitere nützliche Features.
Welches Problem löst es?
Einfach gesagt löst Next.js wiederkehrende SEO Probleme in React-Applikationen, indem es Server Side Rendering einsetzt. Grundsätzlich nutzt ein React Projekt (und auch andere FE-Frameworks) einen Client Side Rendering (CSR) Ansatz (unten mehr).
SEO technisch kann dies zu zwei Problemen führen:
- Content kann nicht immer korrekt von SEO-Bots indexiert werden, da Bots JavaScript interpretieren und ausführen können müssen
- Der First Contentful Paint ist langsam, da erst alles im Client zusammengebaut werden muss
Um diese Probleme zu beseitigen, gibt es generell im Web unterschiedliche Rendering-Strategies. Drei davon unterstützt NextJS out-of-the-box. Diese Konzepte sind grundsätzlich NextJS oder React unabhängig und sind auch in anderen Technologien zu finden.
In unserem Beispiel wollen wir ToDos von einem Restendpunkt abfragen und darstellen, ein simpler Use-Case. Die Code-Beispiele sind wie immer auf GitHub zu finden.
Rendering Strategies
CSR - Client Side Rendering
Als erste Rendering Strategy wollen wir uns das Client Side Rendering genauer anschauen. Hier sei erwähnt, dass NextJS diesen Ansatz nicht out of the box unterstützt, der Vollständigkeit halber wollen wir aber trotzdem kurz darauf eingehen.
Beim CSR bekommt der Client ein Wrapper-HTML vom Browser, darin befindet sich noch kein Inhalt. Über dieses HTML werden dann JavaScript-Files wie zum Beispiel ein Client Side JavaScript Framework wie React geladen. React fragt dann alle dynamischen Daten ab, baut den Content zusammen, rendert die Page und macht die Seite interaktiv. Diesen Ansatz findet man bei den meisten Frontend-Frameworks.
Deployment
Das Deployment dieser Art ist recht simple, da keine Computing Power benötigt wird. Klassischerweise hat man einen Ordner mit statischen HTML, CSS & JS Files und muss dies hosten.
Bekannte Möglichkeiten dies zu deployen:
- Amplify
- Digital Ocean App ‘Static Site’
- Statisches Webhosting oder CDN (z. B. AWS S3 Bucket)
SSG - Static Site Generation
Unter SSG versteht man die komplette Generierung aller Resource & Pages zur Build Zeit. Zur Laufzeit werden nur noch HTML Files von einem Webserver oder einem CDN geladen.
Dies bedeutet, dass bei jeder Änderung am Inhalt der Seite das komplette Projekt erneut gebaut werden muss.
Hier hört man auch oft den Begriff Static Site Generator oder JamStack, mehr dazu in unserem TechUp So geht Headless-CMS mit JAMstack.
Technisch gesehen wird zur Build-Zeit der Server gestartet und alle bekannten Routes werden aufgerufen und deren HTML wird extrahiert. Dies bedeutet auch, dass zur Build-Zeit alle Umsysteme abgefragt werden.
Gibt es auf einer Page weitere URLs zu z. B. Detailseiten werden diese ebenfalls aufgerufen. So werden bei einem Online-Shop alle verlinkten Produktdetailseiten ebenfalls abgefragt und statisch extrahiert. Dies kann, je nach Grösse des Projektes zu langen Build-Zeiten führen.
Selbstverständlich hat man hier weiterhin die Möglichkeit, custom Javascript oder gar ein Framework zur Laufzeit zu laden und auf Benutzer Interaktionen reagieren zu können. Dieses Laden von Frameworks zur Laufzeit ‘on-top’ nennt man Hydration
.
Deployment
Das Deployment dieser Art ist ebenfalls recht simple, da keine Computing Power benötigt wird. Klassischerweise hat man einen Ordner mit statischen HTML, CSS & JS Files und muss dies hosten. Die Möglichkeiten sind identisch wie die beim CSR Ansatz.
NextJS Implementation
Branch: ssg
NextJS bietet diese Funktionalität out-of-the-box an.
Mit der Methode getStaticProps
sagt man NextJS, dass es sich um eine SSG-Page handelt und die Daten zur Build-Zeit abgerufen werden sollen.
|
|
Hierfür muss in der package.json
der Build-Befehl angepasst werden:
|
|
Mit export
sagt man der Next.js CLI, dass alle Pages als Static-Sites ausgegeben werden sollen und in den Order out/
gelegt werden.
Anschliessend können wir dann via npm run build
unser SSG-Projekt bauen. Glücklicherweise zeigt uns NextJS im Build genau an, um welche dieser Arten es sich handelt.
|
|
Um unser Ergebnis zu testen, müssen wir nach dem Build & Export einen lokalen HTTP-Server starten, damit die Dateien aufrufbar sind.
|
|
Anschliessend können wir die URL http://127.0.0.1:8080/ssg
aufrufen und sehen im Quelltext, dass das fertige HTML mit allen Inhalten des Restendpunkts vom HTTP-Server zurückkommt. Hier wurden die Daten des API-Endpunktes zur Build-Zeit abgerufen.
Wichtig hier ist, dass diese Art nicht mit anderen Arten vermischt werden kann (beispielsweise mit ssr), da es sonst beim next export
zu Buildfehlern kommt. Dies bedeutet, dass alle Seiten des kompletten Projekts im SSG-Ansatz zur Build-Zeit generiert werden müssen.
Dieser Ansatz bietet einen grossen Vorteil, wenn sich der eigentlich Content nicht oft ändert. Beispielsweise könnte so super einen Blog aufbauen und veröffentlichen. Selbstverständlich hat man auch hier die Möglichkeit, mittels des Prozesses der Hydration
zur Laufzeit ein Frontend-Framework zu laden und nur einzelne Daten (z. B. den aktuellen Preis eines Produkts) zu laden.
SSR - Server side rendering
Unter SSR versteht man genau das Gegenteil von SSG, die Pages werden zur Request-Time auf dem Server generiert. Dies bedeutet, dass der Client die fertig gerenderte HTML-Seite mit allen Inhalten vom Server bekommt. Diesen Ansatz kennen wir bereits aus der Backend- bzw. MVC-Entwicklung.
Hier wird die Last auf Client-Seite auf die Server-Seite verschoben, bei jedem Request werden Umsysteme erneut abgefragt, um die HTML-Seite zusammenzubauen. Dieser Ansatz kommt speziell bei sehr flüchtigem Content mit vielen beweglichen Teilen, welche gar pro User unterschiedlich sind, zum Einsatz.
Deployment
Wichtig ist hier zu beachten, dass man Rechenleistung benötigt, da man einen laufenden Server, welcher das Zusammenbauen der HTML-Seite übernimmt, braucht.
Bekannte Möglichkeiten dies zu deployen:
- Amplify in Kombination mit CloudFront & Lambda@Edge
- Digital Ocean App ‘Web Service’
- Netlify
- Node JS Server
- Docker Image
Grundsätzlich braucht man für diese Art des Deployments entweder einen Hosting-Provider, welcher NodeJS direkt oder aber welcher Docker-Container unterstützt.
NextJS Implementation
Branch: ssr
In NextJS ist diese Art und Weise per default aktiviert. Ähnlich wie beim SSG Ansatz bietet NextJS hier auch eine Methode an, um dieses Verhalten zu steuern. Die Methode getServerSideProps
wird somit zur Request-Time aufgerufen.
|
|
Hier ist schön zu sehen, dass die eigentliche Implementation identisch zum SSG-Ansatz ist, es ändert sich lediglich der Methodenname. Beim Builden benötigt man hier kein spezielles Target, man kann via next dev
sofort einen Dev-Server starten. Will man das Artefakt bauen und anschliessend mit dem produktiven Profile starten nutzt man next build && next start
.
Dies können wir via npm wie folgt machen:
|
|
Anschliessend können wir via http://localhost:3000/ssr
unsere SSR-Seite aufrufen, welche bei jeden Request erneut zusammengebaut wird. Da wird kein Caching implementiert haben wir hier auch bei jedem Request der API-Endpunkt vom Server aus abgefragt.
Schauen wir uns nochmal den Build-Output genauer an, fällt eine Besonderheit schnell auf:
|
|
Wir haben weiterhin unsere /ssg
Page im Projekt, der Build war erfolgreich. Dies ist einer der grossen Vorteile von NextJS, SSR & SSG lassen sich vermischen (aber nicht via next export
). Zur Build-Zeit wurde nun unsere SSG-Seite aufgerufen und gebaut und im Ordner .next/server/pages
im Projekt abgelegt. Dies erlaubt es uns, teile unserer Applikation statisch zur Build-Zeit und andere Seiten dynamisch zur Request-Time zusammenzubauen.
Selbstverständlich kann auch hier via Hydration
mittels JavaScript o.ä. auf Benutzerinteraktionen reagiert werden.
ISR - Incremental Static Regeneration
Sobald das Projekt grösser und grösser wird, haben beide vorherigen Arten ihre Nachteile und Tücken. Hat der Online-Shop beispielsweise 200'000 Produkte und ein Produkt benötigt ca. 50 MS Build-Zeit so läuft der Build mehr als 2 Stunden. Je nach Masse der User könnte es bei einem SSR Ansatz zu Last und Performance-Problemen kommen.
Grundsätzlich könnte hier mit einem custom implementieren Caching eine Verbesserung erzielt werden, es gibt aber auch eine bessere Möglichkeit!
Incremental Static Regeneration
oder kurz ISR erlaubt es, statisch gebaute Seiten zur Laufzeit zu aktualisieren.
Wir können so genau steuern, welche Pages wann aktualisiert werden sollen.
Deployment
Die Voraussetzungen für ein Deployment hier sind dieselben wie beim SSR Ansatz, da hier ebenfalls Rechenleistung benötigt wird.
NextJS Implementation
Revalidate
Branch: isr
ISR aktivieren wir, indem wir in die getStaticProps
Methode die revalidate
Property packen.
Hier sagen wir, nach mehr als 10 Sekunden soll die Page erneut generiert werden. Mittels dem Timestamp sehen wir zur Laufzeit, dass die Page nach den initialen 10 Sekunden aktualisiert wird. Davor sehen wir die SSG-Version der Page, welche zur Build-Zeit gebaut wurde.
|
|
Auch hier ist wieder schön zu sehen, dass nur ein Property hinzukommt und die eigentliche Implementation gleich bleibt.
Nach diesen 10 Sekunden wird der Cache aktualisiert und alle Benutzer bekommen für mindestens die nächsten 10 Sekunden dieselbe, aktualisierte Version aus dem Cache geliefert. Sobald ein User die Seite dann erneut aufruft wird die Seite einmalig server-side erneut gerendert, das Resultat wird wieder in den Cache gelegt.
Um dies zu prüfen, müssen wir unseren Server mit dem produktiven Profil starten, daher nutzen wir wie bei SSR auch folgende Kommandos:
|
|
Der Build-Output zeigt unter anderem ISR korrekt an:
|
|
Unter http://localhost:3000/isr
können wir die Seite dann wie gewohnt aufrufen, wir sehen, wir bekommen wie beim SSR das komplette HTML vom Server zurück.
Bei einem Refresh nach einiger Zeit sehen wir dann, dass sich der Timestamp ändert, die Seite wurde erneut generiert.
Laden wir dann sofort die Seite erneut (innerhalb von 10 Sekunden) bleibt die Anzeige dieselbe, wir haben die neue, gecachte Version.
Nun haben wir gelernt, wie wir statische Seiten nach einer bestimmten Zeit wieder aktualisieren können.
GetStaticRoutes
Der Build bei 200'000 Produkte würde aber immer noch sehr lange gehen, aber auch hier bietet NextJS eine clevere Lösung.
Mittels der Methode getStaticPaths
können wir steuern, welche Seiten zur Build-Zeit gebaut werden soll.
In Kombination mit revalidate können wir so genau steuern, welche Pages zur Build-Zeit gebaut und welche zur Request-Zeit für wie lange gecacht werden sollen. Dies erlaubt es uns, mit der Build-Zeit und Cache-Hit-Rate etwas zu spielen.
|
|
Beispiel aus https://vercel.com/docs/concepts/next.js/incremental-static-regeneration
Im oberen Code-Ausschnitt befinden wir uns in einer parametrisierbaren Route, welche über den ID-Parameter gesteuert wird. Fachlich gesehen handelt es sich hierbei um die Produktdetailseite, welche anhand der ID zusammengebaut wird.
Wir rufen hier die 1000 beliebtesten Produkte auf und lassen diese via SSG statisch bauen, die restlichen Produkte werden via SSR & ISR zur Request-Time gebaut und gecacht. Dies beschleunigt den Build enorm, trotzdem nutzen wir alle ISR Vorteile.
Mittels fallback: blocking
sagt man, dass für den ersten User, welcher eine Produkt ausserhalb der ‘Top 1000’ aufruft, die Seite server-side gerendert und dann in den Cache gelegt wird. Alternativ könnte man dem Benutzer eine Waiting-Page anzeigen lassen, die Page wird dann nach erfolgreichen generieren automatisch nachgeladen.
Deployment Docker
Selbstverständlich lässt sich eine NextJS Applikation auch containerisieren und so via Docker, z.B. in ein Kubernetes-Cluster deployen. In unserem Beispiel ist das Dockerfile auf dem Branch ssr
zu finden, dies kann mit folgenden Commands gebaut und gestartet werden:
|
|
Anschliessend ist unsere Application wie gewohnt unter localhost:3000
aufrufbar.
Dockerfile ist inspiriert von https://github.com/vercel/next.js/blob/canary/examples/with-docker/Dockerfile
Fazit
NextJS verspricht mit ihrem Slogan The React Framework for Production
Grosses! In der Tat bietet NextJS im Vergleich zu einer normalen React-Anwendung zahlreiche Vorteile, welche speziell in Produktion zum Tragen kommen.
Wie bei vielen Frameworks und Technologien muss man sich vorher klar über seine Use-Cases und das Einsatzgebiet sein. Neben den frontend-facing Funktionen bietet NextJS ja auch eine bereite Palette an Möglichkeiten wie zum Beispiel das Verbinden mit einer Datenbank. Hier muss man sicherlich, anhand der Grösse und Komplexität der Anwendung entscheiden, ob ein Einsatz einer anderen Technologie, z.B. Spring Boot mit Hibernate, nicht passender ist.
Nichtsdestotrotz bietet NextJS für kleinere bis mittlere Webanwendung und speziell interaktive Blogs eine sehr gute Developer-Experience mit zahlreichen modernen Funktionen, welche sich positiv auf das User-Erlebnis auswirken.
Die Beispiele sind wie immer auf GitHub zu finden.
Stay tuned! 🚀