Die neuesten bahnbrechenden Änderungen in Next.js 14

10.01.2024Tom Trapp
Mobile Next.js JavaScript Frontend Framework Hands-on How-to

banner.png

  • Nahezu alle Next.js Projekte sind veraltet? 💥
  • Noch schlimmer, alle Next.js Kurse und Lernmaterialen sind outdated? 🤯
  • Next.js kann nun SEO? 💡
  • Next.js erlaubt SQL Injection? 💣
  • PHP erwacht zum Leben in Next.js? ⁉️
  • Next.js ist 700x schneller als Webpack? 🔍

Diese und weitere Fragen wollen wir in diesem TechUp beantworten!

Schon vor einiger Zeit hatten wir zwei TechUps über Next.js, einerseits Vollwertiger Full-Stack mit Next.js und dann Das Next beste Update auf Next.js 12. In den vergangenen Monaten haben wir Next.js oft auf unterschiedlichen Konferenzen gehört, wir haben mit Entwicklern an der KubeCon gequatscht, haben uns mit Kunden und Interessierten an den Kubernetes Community Days unterhalten und haben über Next.js am Cloud Native Basel Meetup philosophiert. Da vor Kurzem Next.js 14 released wurde, schauen wir uns natürlich auch hier die neuesten Updates an.

Was ist Next.js?

Kurzer Recap: Next.js ist ein React Framework, welches es erlaubt, React Applikationen auf unterschiedliche Arten zu deployen. Hinter Next.js steht Vercel, das Unternehmen, welches auch die Serverless Plattform Vercel betreibt.

Mehr dazu hier: Vollwertiger Full-Stack mit Next.js.

Next.js Recent Updates

Seit dem letzten Blogpost über Next.js hat sich einiges getan. Inzwischen ist Next.js 14 verfügbar und es gab einige Neuerungen im Vergleich zu Next.js 12. Die wichtigsten Änderungen schauen wir uns jetzt an.

App Directory & App Router

Das grundlegende Routing und dadurch das Layout eines Projekts wurde überarbeitet. Neu gibt es einen app Folder auf Top-Level, welcher eine Directory Based Routing Struktur enthält. Neu können hier nicht nur Pages, sondern auch vererbbare Layouts, Components und weitere Nested Routes abgelegt werden.

🤔 Aber Moment, dann ändert sich ja einfach nur die Ordnerstruktur, oder? So einfach ist es leider nicht…

Der App Router definiert ein Subset von Arten von Komponenten, welche pro Page oder gar vererbbar, das Verhalten einer Page definieren und implementieren.

  • layout: Gemeinsame Benutzeroberfläche für ein Segment und seine Kinder
  • page: Einzigartige Benutzeroberfläche für eine Route und öffentliche Zugänglichkeit der Routes
  • loading: Lade-Benutzeroberfläche für ein Segment und dessen “Kinder”
  • not-found: Benutzeroberfläche für “Nicht gefunden”, für ein Segment und dessen “Kinder”
  • error: Fehler-Benutzeroberfläche für ein Segment und dessen “Kinder”
  • global-error: Globale Fehler-Benutzeroberfläche
  • route: Serverseitiger API-Endpunkt
  • template: Spezialisierte neu gerenderte Layout-Benutzeroberfläche
  • default: Standard-Benutzeroberfläche für parallele Routen

All diese Dateien werden in einer definierten Reihenfolge gerendert oder abgearbeitet. Diese Struktur mit reservierten Dateinamen bietet auch einen weiteren Vorteil: Tests, CSS etc. können direkt in der gleichen Struktur abgelegt werden.

Neu könnte die Struktur beispielsweise so aussehen:

img_1.png Quelle: https://nextjs.org/docs/app/building-your-application/routing/pages-and-layouts

ℹ️ Sämtliche Informationen zur neuen Projektstruktur sind hier zu finden.

An diesen Punkt wollen wir zwei Fragen beantworten:

Sind nahezu alle Next.js Projekte veraltet? Nimmt man an, dass der Pages Router irgendwann verschwinden wird und alles nur noch auf den App Router setzen soll dann ja.

Hilfreich ist hier, dass die Migration parallel laufen kann und beide Router gleichzeitig unterstützt werden. Die Dokumentation spezifiziert aber klar, dass bei URL-Konflikten der App Router bevorzugt wird. Ein klares Zeichen, in welche Richtung es geht.

Noch schlimmer, sind jetzt alle Next.js Kurse und Lernmaterialien outdated? Auch hier kann man mit ja beantworten, wobei schon zahlreiche Kurse aktualisiert wurden. Glücklicherweise bietet Next.js neu eine eigene Lernplattform an um den Einstieg zu erleichtern: https://nextjs.org/learn.

Glücklicherweise bietet uns Next.js einen Upgrade Path vom Pages auf den App-Router an, welcher hier zu finden ist. So kann Step by Step migriert werden, beide Router können parallel betrieben werden. Bei Konflikten der URLs wird der App Router einfach bevorzugt.

Forget ISR, SSR, SSG, CSR, welcome Server and Client Components

Die bisherigen Begriffe wie SSG, SSR, ISR und CSR sind nicht mehr aktuell. Diese werden nur noch im Zusammenhang mit dem alten Pages Router genutzt. Neu gibt es Server Components und Client Components. Wie der Name schon sagt, werden Server Komponenten auf dem Server und Client Komponenten auf dem Client gerendert. Soweit so gut, also SSR und CSR. Aber was ist mit SSG und ISR?

SSG (Static Site Generation) ist weiterhin unterstützt, nennt sich neu aber Static Exports. Mittels einer speziellen Konfiguration in der next.config.js kann ein Projekt als statische Website gebaut werden.

ISR (Incremental Static Regeneration) ist in der Dokumentation nicht mehr zu finden, hierfür gibt es neu Konzepte wie das Data Fetching und Streaming inkl. Caching.

Layouts

Layouts sind eine der wichtigsten Neuerungen. Neu können Layouts definiert werden, welche auf allen Pages oder auch nur auf bestimmten Pages verwendet werden können. Diese Layouts werden automatisch vererbt und können auf einer tieferen Ebene wieder überschrieben oder erweitert werden.

Das Layout wird in einer layout.js File definiert:

img_3.png

Eine Layout-Komponente kann wie folgt aussehen:

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
export default function HomeLayout({
    children, // will be a page or nested layout
}: {
    children: React.ReactNode
}) {
    return (
    <section>
        <nav></nav>
        <div class="main"
            {children}
        </div>
    </section>
)
}

In das Tag {children} wird dann die Page oder ein weiteres Layout gerendert. Das macht es sehr einfach, Pages und Designs zu vererben und zu überschreiben. Ebenfalls hat dieses Vorgehen grosse Performancevorteile, da das Layout nur einmal geladen werden muss und dann für alle Pages gilt. Mehr Infos zu Layouts findest du hier.

Streaming

Der App Router unterstützt neu Streaming von Server Komponenten. Hierbei wird für die Ladezeit nur das Layout mit einem Loading State geladen.

img_4.png

Ausserdem gibt es über die react/Suspense Komponenten die Möglichkeit, eine Seite in unterschiedliche und unabhängige Segmente zu unterteilen, welche dann individuell geladen werden können. Dies verbessert die Performance aber auch die User-Experience, da die unabhängigen Teile der Seite schneller und parallel geladen werden und der User nicht auf das komplette Rendering warten muss.

Ein solcher Aufbau könnte wie folgt aussehen:

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
import { Suspense } from 'react'
import { TechUps, Decodify } from './Components'

export default function Posts() {
    return (
        <section>
            <Suspense fallback={<p>Loading techups...</p>}>
                <TechUps />
            </Suspense>
            <Suspense fallback={<p>Loading decodify...</p>}>
                <Decodify />
            </Suspense>
        </section>
    )
}

Beide Komponenten würden parallel und unabhängig voneinander geladen werden.

SEO

Mit der neuen Metadata API, welche File-Based funktioniert, können Metadaten direkt in der layout.js oder page.js Datei definiert werden. Grundlegend gibt es zwei Möglichkeiten, Metadaten zu setzen:

  • The Metadata Object - über dieses Objekt können Metadaten statisch gesetzt werden.
    1
    2
    3
    4
    5
    6
    7
    8
    
      import { Metadata } from 'next'
    
      export const metadata: Metadata = {
          title: 'Toms cooles TechUp',
          description: 'Heute gehts mal wieder um Next.js, juhu!',
      }
    
      export default function Page() {}
    
  • The generateMetadata Methode - über diese Funktion, welche async ausgeführt wird, können Metadatan dynamisch auf Basis von Data Fetchings gesetzt werden.
     1
     2
     3
     4
     5
     6
     7
     8
     9
    10
    11
    12
    13
    14
    15
    16
    17
    18
    19
    20
    21
    22
    23
    24
    25
    26
    
    import { Metadata, ResolvingMetadata } from 'next'
    
    type Props = {
      params: { id: string }
      searchParams: { [key: string]: string | string[] | undefined }
    }
    
    export async function generateMetadata(
      { params, searchParams }: Props,
      parent: ResolvingMetadata
    ): Promise<Metadata> {
    // read route params
    const id = params.slug
    
    // fetch data
    const blog = await fetch(`https://b-nova.com/what-is-that/${slug}`).then((res) => res.json())
    
    return {
      title: blog.title,
      openGraph: {
          images: ['/some-specific-page-image.jpg'],
       },
      }
    }
    
    export default function Page({ params, searchParams }: Props) {}
    

Zusätzlich gibt es neue vordefinierte Dateinamen, welche genutzt werden können, um Metadata Elemente im HTML Header zu definieren. So kann beispielsweise mit einer Datei opengraph-image.(jpg|png|svg) das og:image Element definiert werden.

Praktischerweise lassen sich über ein sitemap.js File dynamische Inhalte für die Sitemap definieren. Diese werden dann automatisch in die Sitemap integriert.

💡 Hier ist wichtig zu erwähnen, dass die Metadata API nur mit dem App-Router funktioniert. Der Pages Router geht leer aus…

Kann Next.js nun SEO? Richtig, mit der Metadata API können nun sehr einfach statische oder dynamische Metadaten definiert werden. Diese werden dann automatisch in den HTML Header integriert. Selbst auf Layout Ebene können diese Informationen definiert und vererbt werden.

Data Fetching

Neu sind sämtliche Next.js Komponenten per Default Server Components, welche es erlaubeen, sehr einfach zwischen SSG, SSR und ISR zu wechseln.

Im App-Router wird zwischen vier Möglichkeiten unterschieden, wie Daten gelesen werden können:

  • Im Server mit fetch https://nextjs.org/docs/app/building-your-application/data-fetching/fetching-caching-and-revalidating#fetching-data-on-the-server-with-fetch

    • fetch Antworten werden standardmässig auf dem Server gecached (Application Cache)
    • Neben dem Caching gibt es auch den Mechanismus der Revalidation
      • time-based: revalidate: 60 - Daten werden alle 60 Sekunden neu geladen
      • on-demand: Fetch Calls lassen sich taggen, diese Cache Daten anhand des Tags können dann in einem Route Handler oder einer Server Action wieder revalidated werden.
  • Im Server mit Third-Party Libraries wie Beispielsweise DB Calls. Hier gibt es die Möglichkeit, ein Caching in der Kompletten Funktion zu definieren:

    1
    2
    3
    4
    5
    6
    7
    8
    
    import { cache } from 'react'
    
    export const revalidate = 3600 // revalidate the data at most every hour
    
    export const getBlog = cache(async (slug: string) => {
      const item = await db.blog.findUnique({ slug })
      return item
    })
    
  • Im Client mit Route Handler

    • Ein Route Handler ist eine Funktion, welche im Client aufgerufen werden kann und auf dem Server läuft diese ermöglicht es, im Frontend als Teil der normalen Projektstruktur, API Endpunkte mit einer route.ts für GET, POST, PUT, PATCH, HEAD, OPTIONS und DELETE zu definieren.
    • Sensible Daten und Logininformationen für weitere Services können im Backend versteckt werden und müssen nicht ins Frontend exponiert werden.
    • Diese Funktionen können selbstverständlich auch gecached werden.
  • Im Client mit Third Party Libraries

    • Hier gibt es die Möglichkeit, externe Bibliotheken wie SWR oder React Query zu nutzen.

Wir sehen also, dass in einem Monorepo-Ansatz ein API First-Ansatz mit klarer Kommunikation zwischen Frontend & Backend inklusive Caching definitiv möglich ist.

Turbopack (Alpha)

Der Compiler der zugrundeligenden Rust Engine, welcher die Performance von Next.js deutlich verbessert, kann aktuell genutzt werden mit next dev --turbo. Laut Next.js selbst ist Turbopack beim Anzeigen vom Updates im next dev Modus 10x schneller als der Vorgänger Vite und 700x schneller als Webpack.

Next.js ist 700x schneller als Webpack? Richtig, beim HMR (Hot Module Replacement) mit next dev!

Und ja, unsere Tests haben es bestätigt: Hot Code Replacement mit Turbopack ist unglaublich schnell. 🔥

Middleware

Neu können Middleware Funktionen definiert werden, welche vor dem eigentlichen Rendering der Page ausgeführt werden. Diese Middleware Funktionen können beispielsweise für Authentifizierung, Logging, Caching usw. genutzt werden. So können nun auch Responses zurückgegeben werden, welche direkt an den Client gesendet werden.

Next.js definiert eine bestimmte Reihenfolge, wie Rendering gemacht wird, Middleware Functions stehen hier an 3. Stelle. Die Funktionen können entweder fix auf Pfade, per Regex auf Pfade oder mit Conditional Statements aktiviert und ausgeführt werden.

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
import {NextResponse} from 'next/server';

export function middleware(request: Request) {
    if (!isUserAuthorized(request)) {
        return NextResponse.json({message: 'Please log in'});
    }

    const requestHeaders = new Headers(request.headers);
    requestHeaders.set('x-coolest-podcast-ever', 'decodify');

    return NextResponse.next({
        request: {
            headers: requestHeaders,
        },
    });
}

Edge Runtime

Ein leichtgewichtes Subset von Node.js APIs kann speziell aktiviert werden, um Next.js API Routes direkt in der Edge Location ausführen zu können. Definiert wird eine Edge Runtime wie folgt:

1
2
3
4
5
6
7
export const config = {
	runtime: 'edge',
};

export default function handler(req: Request) {
	return new Response('Hello from b-nova');
}

Ziel ist es, dass diese Edge Runtime einen sehr geringen Footprint hat, mit Browsern und Edge Locations kompatibel ist und so immer sehr schnell und nah am User ausgeführt werden kann.

Diese Edge Runtime lässt sich sicher gut mit Middleware Functions kombinieren. Next.js würde, bei einem Deployment auf die Vercel Platform, diese Functions dann in einem Edge Network über den ganzen Globus verteilen. Mit einem self hosted Ansatz bekommt man laut Doku eine Single Regon Runtime.

Server Actions (Stable)

img.png

Quelle: https://www.linkedin.com/posts/jseyehunts_daily-memes-programming-life-memes-software-activity-7069634192099192833-JO5S?utm_source=share&utm_medium=member_desktop

Neu können beispielsweise schreibende Vorgänge direkt in der Next.js Komponente definiert und gesichert aufgerufen werden. So ist es nicht mehr nötig, dedizierte API Routes zu definieren und diese via fetch auf HTTP aufzurufen.

ℹ️ Und mit “in der Next.js Komponente” ist hier wirklich inline im HTML gemeint. 🤯

img_2.png

Quelle: https://www.reddit.com/r/nextjs/comments/17hgtrt/so_whats_the_deal_with_the_code_on_this_been/

An dieser Stelle ist zu erwähnen, dass Server Actions für viel Wind sorgten mit ihrer vermeintlichen SQL Injection. Korrekt angewendet gibt es hier keinerlei Gefahr für eine SQL Injection Sicherheitslücke, mehr dazu hier: https://www.youtube.com/watch?v=2Ggf45daK7k&ab_channel=Joshtriedcoding.

Erwacht PHP in Next.js 14 wieder zum Leben? Auf den ersten Blick sieht es so aus, als ob PHP wieder zum Leben erwacht ist. Aber keine Angst, es ist kein PHP, sondern eine JavaScript Funktion, welche auf dem Server ausgeführt wird. Diese Funktion kann dann beispielsweise Daten in eine Datenbank schreiben, eine Datei auf dem Server erstellen oder sonstige schreibende Vorgänge ausführen. Sicher lässt sich speziell als Backend oder Middleware Developer hier anmerken, dass dies nicht die sauberste Lösung ist. Aber für Frontend Entwickler ist dies eine sehr einfache und schnelle Möglichkeit, Daten zu schreiben.

Erlaubt Next.js jetzt SQL Injection? Nein, ein sogenanntes TemplateStringsArray wird genutzt, um SQL Injection zu verhindern. Dieses TemplateStringsArray wird dann in ein Prepared Statement umgewandelt und verhindert so SQL Injection.

Pages vs. App Router

Die Frage “Macht es Sinn auf den neuen App-Router zu migrieren?” lässt sich sehr schnell und einfach beantworten: Ja!

Der App Router ist beispielsweise im Thema Caching, Partial Rendering und Performance deutlich besser als der Pages Router und sorgt somit auch für eine bessere User- und auch Developer-Experience. Sicherlich ist eine Migration nicht ganz einfach, aber es lohnt sich. Diese Migration sollte gut vorbereitet sein. Hier muss unbedingt geprüft werden, ob alle Dependencies, Plugins usw. schon mit dem App-Router kompatibel sind.

Fazit

Next.js hebt sich klar von anderen Frameworks wie Svelte, Angular oder Vue ab. Die neuesten Entwicklungen zeigen, dass Next.js sich immer mehr zu einem Full-Stack Framework entwickelt, welches sehr einfach und schnell zu nutzen ist. Durch den App Router lassen sich Client und Server Funktionalitäten noch einfacher und besser zentral definieren aber separat getrennt voneinander deployen und nutzen.

Aus meiner persönlichen Sicht entwickelt sich Next.js immer mehr zu einer eierlegenden Wollmilchsau. Aus meiner Sicht das go-to Framework, wenn man eine Full-Stack Application im Node.js Ecosystem aufbauen und betreiben will.

More to come, stay tuned! 🔥

Tom Trapp

Tom Trapp – Problemlöser, Innovator, Sportler. Am liebsten feilt Tom den ganzen Tag an der moderner Software und legt viel Wert auf objektiv sauberen, leanen Code.