Next.js-Entwickler in Duisburg

Next.js Entwickler für Unternehmen in Duisburg: eine bestehende Anwendung Seite für Seite vom Pages Router auf den App Router umstellen, mit Layouts, Metadaten, Datenabruf und Tests. Schriftlicher Ablauf, erste Einschätzung kostenlos.

Next.js-Entwickler

Mein Platz in der WordPress-Community

  • Mein wordpress.org-Konto besteht seit 2006.
  • Ich habe zum WordPress-Core in den Versionen 7.0 und 7.1 beigetragen.
  • Ich betreue 30 Plugins auf wordpress.org.
  • Ich organisiere WordCamps und trete dort als Speaker auf.
  • Ich war bei jedem WordCamp Europe von 2017 bis 2026 dabei.

Mein WordPress.org-Profil

Die Anwendung läuft, die Kunden nutzen sie, und doch liegt sie noch im Verzeichnis pages. Neue Funktionen von Next.js entstehen im App Router, und wer dort weiterbauen will, muss einmal hinüber. Auf dieser Seite beschreibe ich, wie ich eine bestehende Anwendung Route für Route umstelle, was sich bei Datenabruf, Layouts und Metadaten ändert und was ich vor der Abnahme teste. Ich arbeite allein, remote und ausschließlich in Textform. Grundlage sind die Anleitung How to migrate from Pages to the App Router und das Upgrade auf Version 15 auf nextjs.org.

#Beide Router laufen nebeneinander

Der Router im Verzeichnis app kam mit Next.js 13; für die Migration verlangt die Anleitung Version 13.4 oder neuer. Laut npm-Registry erschien 13.4.0 am 4. Mai 2023, die aktuelle Version 16.4.6 am 9. November 2026. Die Anleitung betont, dass app absichtlich gleichzeitig mit pages läuft, damit Seite für Seite umgestellt werden kann. Genau so plane ich es: keine Umstellung in einem Zug, sondern eine Liste von Routen, die einzeln wandern.

Zwei Folgen davon sollten Sie vorher kennen:

  • Beim Wechsel zwischen einer Route aus pages und einer aus app gibt es eine harte Navigation, und next/link lädt über die Grenze hinweg nichts vor. Während der Übergangszeit ist das normal und kein Fehler.
  • _app und _document bleiben stehen, bis die letzte Route im alten Verzeichnis verschwunden ist. Die Anleitung rät dazu, weil Stile aus dem neuen Wurzel-Layout nicht auf Routen in pages wirken.

Der erste Schritt im Code ist klein: Version anheben, Verzeichnis app anlegen, eine Datei app/layout.tsx mit <html> und <body> schreiben. Dieses Wurzel-Layout ersetzt am Ende _app und _document.

#Pro Route zwei Schritte statt einer Neuschreibung

Seiten im Verzeichnis app sind standardmäßig Server Components, Seiten in pages sind Client Components. Wer beides in einem Zug ändert, sucht Fehler an zwei Stellen. Die Anleitung empfiehlt deshalb zwei Schritte, die ich übernehme:

  1. Die Seitenkomponente wandert unverändert in eine neue Datei mit der Direktive 'use client'. Sie verhält sich wie vorher, wird weiter auf dem Server vorgerendert und bekommt ihre Daten als Props.
  2. Eine neue page.tsx im Verzeichnis app bleibt Server Component, lädt die Daten und reicht sie an die Client Component weiter.

Erst wenn die Route so läuft und getestet ist, verlagere ich Teile der Oberfläche, die keine Interaktion brauchen, aus der Client Component in die Server Component. So sinkt das JavaScript, das zum Browser geht, Schritt für Schritt und nicht auf Verdacht.

Datei oder Funktion in pagesEntsprechung in app
index.js, about.js, blog/[slug].jspage.js, about/page.js, blog/[slug]/page.js
_app.js und _document.jsein Wurzel-Layout app/layout.js
getServerSideProps, getStaticPropsasync Server Component mit fetch
getStaticPathsgenerateStaticParams
fallback in getStaticPathsdynamicParams
pages/404.jsnot-found.js
pages/_error.jserror.js je Routensegment
pages/api/*Route Handler route.js

API-Routen in pages/api laufen laut Anleitung ohne Änderung weiter. Ich ziehe sie erst um, wenn sie ohnehin angefasst werden.

#Datenabruf: aus getServerSideProps wird fetch im Server Component

Ein Beispiel aus der Anleitung, leicht gekürzt. Vorher, im Verzeichnis pages:

export async function getServerSideProps() {
  const res = await fetch('https://...')
  const projects = await res.json()
  return { props: { projects } }
}

export default function Dashboard({ projects }) {
  return (
    <ul>
      {projects.map((p) => <li key={p.id}>{p.name}</li>)}
    </ul>
  )
}

Nachher, im Verzeichnis app:

async function getProjects() {
  const res = await fetch('https://...', { cache: 'no-store' })
  return res.json()
}

export default async function Dashboard() {
  const projects = await getProjects()
  return (
    <ul>
      {projects.map((p) => <li key={p.id}>{p.name}</li>)}
    </ul>
  )
}

Die Funktion getProjects darf laut Anleitung beliebig heißen. Die Option cache: 'no-store' sagt, dass die Antwort nie zwischengespeichert wird, das entspricht getServerSideProps. Wer in getServerSideProps an req.cookies oder Header kam, liest sie nun mit cookies() und headers() aus next/headers. In Version 15 sind beide asynchron, ebenso params und searchParams: Aus const { slug } = params wird const { slug } = await params. Für diesen Umbau gibt es ein Codemod, das die Upgrade-Anleitung nennt.

#Die Stelle, an der sich Verhalten still ändert

Aus getStaticProps mit revalidate: 60 wird in der Anleitung ein fetch mit next: { revalidate: 60 }. Wichtig ist, was zwischen Version 14 und 15 geschah: Laut der Upgrade-Anleitung werden fetch-Anfragen seit Version 15 nicht mehr standardmäßig zwischengespeichert, und GET-Route-Handler ebenfalls nicht. Eine Seite, die früher zur Bauzeit entstand, kann nach einer unbedachten Umstellung bei jedem Aufruf das Backend belasten.

Deshalb schreibe ich für jede ersetzte Funktion das Zwischenspeichern ausdrücklich hin:

  • getServerSideProps wird zu cache: 'no-store'.
  • getStaticProps wird zu cache: 'force-cache'.
  • getStaticProps mit revalidate wird zu next: { revalidate: n }.

Dazu kommt getStaticPaths: Es wird zu generateStaticParams, und fallback wird zu dynamicParams. Mit dem Standard true werden noch nicht erzeugte Pfade bei der ersten Anfrage gerendert und zwischengespeichert, mit false liefern sie einen 404, das entspricht fallback: false. Für fallback: 'blocking' gibt es laut Anleitung keine eigene Option, weil der Unterschied zu true mit Streaming vernachlässigbar ist.

#Layouts und Kontext statt getLayout

Wer in pages das Muster Page.getLayout verwendet, bekommt im App Router verschachtelte Layouts als Dateikonvention. Die Anleitung beschreibt den Weg: Den Inhalt der bisherigen Layout-Komponente in eine Client Component verschieben, in einer layout.js einbinden und das getLayout an der Seite löschen. Später lassen sich die nicht interaktiven Teile in das Server-Layout heben.

Der häufigere Stolperstein sind React-Kontexte. Provider für Warenkorb, Sitzung oder Thema aus _app müssen laut Anleitung in eine Client Component. Ich bündele sie in einer Datei providers.tsx und binde sie im Wurzel-Layout ein.

Zwei Einzelheiten aus der Anleitung, die oft vergessen werden: Skripte mit der Strategie beforeInteractive wandern aus _document in ein Wurzel-Layout, und die Handler onLoad, onReady und onError funktionieren nicht in Server Components. Bei Tracking- und Einwilligungsskripten ist das keine Kleinigkeit, siehe den Abschnitt zur LDI NRW weiter unten.

#Metadaten: next/head wird zu metadata

Die Komponente next/head entfällt im App Router. An ihre Stelle tritt für feste Angaben ein exportiertes Objekt metadata in layout.js oder page.js. Hängt der Titel von Daten ab, etwa vom Namen eines Produkts, exportieren Sie die Funktion generateMetadata. Beide gibt es laut Dokumentation nur in Server Components.

Lädt generateMetadata dieselben Daten wie die Seite, wäre das ein doppelter Abruf. Die Dokumentation schlägt dafür vor, die Ladefunktion mit cache aus React zu umhüllen, sodass sie nur einmal läuft.

Ein Detail, das für die Suche zählt: Bei dynamisch gerenderten Seiten streamt Next.js die Metadaten getrennt und fügt sie nach dem Laden in das HTML ein. Für Bots, die die Metadaten im <head> erwarten, ist das Streaming laut Dokumentation abgeschaltet; genannt werden Twitterbot, Slackbot und Bingbot. Ich prüfe trotzdem bei jeder Route den ausgelieferten Kopf, weil ein vergessenes Head ohne Fehlermeldung zu einer Seite ohne Titel führt.

#Router-Hooks

In Client Components kommen useRouter, usePathname und useSearchParams aus next/navigation. Der neue useRouter liefert keinen pathname und kein query-Objekt mehr, dafür gibt es usePathname, useSearchParams und useParams. Auch asPath, isReady und die Werte der eingebauten Mehrsprachigkeit (locale, locales, defaultLocale) sind weggefallen. Und router.push gibt kein Promise mehr zurück.

Komponenten, die in beiden Verzeichnissen laufen müssen, bekommen in der Übergangszeit den useRouter aus next/compat/router. Nach der letzten Route tausche ich ihn gegen den aus next/navigation.

#Was ich vor der Abnahme teste

Die Dokumentation von Next.js weist darauf hin, dass manche Testwerkzeuge asynchrone Server Components noch nicht vollständig unterstützen, und empfiehlt Ende-zu-Ende-Tests statt Unit-Tests. Deshalb lege ich vor der ersten Umstellung Playwright-Tests für die wichtigsten Wege an: Anmeldung, Suche, Formulare, Bestellung oder was Ihre Anwendung trägt.

PrüfpunktWas ich vergleiche
Ausgelieferter HTML-KopfTitel, Beschreibung, kanonische Adresse und Robots-Angaben je Route, alt gegen neu
Datengleicher Inhalt wie vorher, und wie oft das Backend gefragt wird
Übergang der Routerharte Navigation zwischen pages und app an den Stellen, an denen Nutzer sie spüren
Fehlerseiten404 und Fehlerseite zeigen weiter Impressum und Datenschutzlink
SkripteEinwilligung und Tracking laden erst nach der Zustimmung
Formulare und AnmeldungCookies, Weiterleitungen und Fehlermeldungen wie bisher
LeistungswerteMessung der Core Web Vitals nach den Schwellen von web.dev: LCP bis 2,5 s, INP bis 200 ms, CLS bis 0,1

Die Liste lässt sich auch ohne mich abarbeiten. Wichtig ist die Reihenfolge: erst die Tests anlegen, solange die Anwendung noch im alten Zustand läuft, dann umstellen. Dann zeigt jede rote Zeile eine Änderung, die durch die Umstellung entstand.

#Duisburg: Hochschulen, Institute und die LDI NRW

Gebäude auf dem Campus Duisburg der Universität Duisburg-Essen

Foto: Harald Lordick, CC BY-SA 3.0, Wikimedia Commons.

Duisburg Business & Innovation nennt Duisburg einen Wissenschaftsstandort mit fünf Hochschulen und zehn Forschungsinstituten, darunter die Universität Duisburg-Essen mit knapp 40.000 Studierenden, die Folkwang Universität der Künste und die FOM Hochschule. Quelle: Wissenschaftsstandort Duisburg. Für Anwendungen, die an solchen Einrichtungen oder in Unternehmen der Region laufen, gilt dieselbe Vorgehensweise: erst Bestand aufnehmen, dann Route für Route umstellen.

Für Datenschutz in Nordrhein-Westfalen ist bei privaten Betreibern die LDI NRW zuständig. In ihrem Muster für Datenschutzhinweise steht, dass die Hinweise den Nutzern zum Zeitpunkt der Datenerhebung vorliegen müssen und der Link von jeder einzelnen Seite mit einem Klick erreichbar sein muss. Die Erklärung ist zu ergänzen, wenn Cookies, aktive Inhalte, Kontaktformulare oder Social-Media-Plugins im Einsatz sind.

Für die Umstellung folgt daraus:

  • Der Link zu Impressum und Datenschutzhinweisen gehört in das Wurzel-Layout. Dann steht er auch auf not-found.js und in den Fehlerseiten. Das Impressum verlangt § 5 DDG leicht erkennbar und unmittelbar erreichbar.
  • Skripte, die Informationen im Endgerät speichern oder auslesen, brauchen nach § 25 TDDDG grundsätzlich eine Einwilligung. Wandert ein Skript aus _document in das Layout, prüfe ich, dass es weiterhin erst nach der Zustimmung lädt.
  • Ändert sich beim Umbau, welche Dienste Daten empfangen, aktualisiere ich die Liste dafür, damit die Datenschutzhinweise stimmen. Die Bewertung selbst ist Sache Ihrer Datenschutzberatung, ich gebe keine Rechtsberatung.

#So läuft die Umstellung mit mir ab

  1. Ihre Anfrage in Textform: Adresse, Version, Backend, Ziel, Termin.
  2. Meine Rückfragen, schriftlich: meist zu Routen, Authentifizierung, Hosting und dazu, was nicht kaputtgehen darf.
  3. Bestandsaufnahme: Routen, Datenfunktionen, Abhängigkeiten und Skripte als Liste, mit einer Reihenfolge der Routen.
  4. Umfang als Dokument: Leistungen, Ausschlüsse und Annahmen. Ich beginne erst nach Ihrer schriftlichen Bestätigung, zusätzliche Wünsche schätze ich getrennt ein.
  5. Tests zuerst, dann Route für Route auf einer Testumgebung, die Sie selbst durchklicken können.
  6. Abnahme gegen den Umfang, offene Punkte als Liste.
  7. Danach: Updates und kleine Änderungen übernehme ich auf Wunsch in der Wartung in Duisburg. Nicht genutzte Stunden eines Monats gehen nicht auf den nächsten über.

Was Sie mitbringen: Gestaltung, Mockups und Wireframes liefern Sie oder Ihre Agentur, ich entwerfe nichts selbst. Fehlt ein Entwurf, markieren Sie in meiner Tabellenvorlage je Ansicht, welches Element wohin gehört. Dazu brauche ich Zugang zu Repository, Hosting und Testumgebung.

#Verwandte Seiten für Duisburg

#Fragen zur Umstellung

#Muss ich die ganze Anwendung auf einmal umstellen?

Nein. Laut Next.js-Dokumentation ist das Verzeichnis app absichtlich so gebaut, dass es gleichzeitig mit pages läuft. Sie können Route für Route verschieben. Der Wechsel zwischen beiden Routern ist dabei eine harte Navigation.

#Was ersetzt getServerSideProps im App Router?

Ein async Server Component, das die Daten mit fetch lädt. Für Daten, die bei jeder Anfrage frisch sein müssen, gibt die Dokumentation die Option cache: 'no-store' an. Cookies und Header liest die Seite mit cookies() und headers() aus next/headers, beide asynchron.

#Warum liefert mein ehemaliges getStaticProps plötzlich ständig frische Daten?

Seit Next.js 15 werden fetch-Anfragen nicht mehr standardmäßig zwischengespeichert. Für das Verhalten von getStaticProps schreibe ich cache: 'force-cache' hin, für getStaticProps mit revalidate die Option next: { revalidate: n } mit der Sekundenzahl.

#Verliere ich beim Umstellen Metadaten wie Titel und Beschreibung?

Nur wenn next/head vergessen wird. Im App Router ersetzen das Objekt metadata und die Funktion generateMetadata die Komponente Head. Ich vergleiche vor der Abnahme den ausgelieferten HTML-Kopf jeder Route mit dem alten Stand.

#Lassen sich asynchrone Server Components mit Unit-Tests prüfen?

Laut Next.js-Dokumentation unterstützen manche Werkzeuge asynchrone Server Components noch nicht vollständig, empfohlen werden Ende-zu-Ende-Tests. Ich lege deshalb vor der Umstellung für die wichtigsten Wege Playwright-Tests an und lasse sie nach jeder verschobenen Route laufen.

#Was schicke ich Ihnen vorab, damit die Einschätzung konkret wird?

Die Adresse der Anwendung, die Next.js-Version aus der package.json, das Backend oder CMS, die Zahl der Routen und den gewünschten Termin. Wenn ein Layout existiert, legen Sie es bei; Gestaltung und Platzierung der Elemente liefern Sie, ich setze sie um.

#Ihre Umstellung in Duisburg

Ob ein Kundenportal, ein Shop-Frontend oder eine interne Anwendung noch im Verzeichnis pages liegt: Schreiben Sie mir über das Kontaktformular. Nennen Sie die Adresse der Anwendung, die Next.js-Version, das Backend, das Ziel und den gewünschten Termin. Ich antworte an Werktagen in Textform, und die erste Einschätzung ist kostenlos.

Brauchen Sie die Leistung: Next.js-Entwickler in Duisburg?

Beschreiben Sie Ihr Projekt per E-Mail. Antwort innerhalb von 48 Stunden.

Projekt beschreiben

Weitere WordPress-Dienste und Wissensbasis entdecken