Zum Inhalt springen

Vom Pages Router zum App Router ohne Feature-Stopp

Beide Router laufen nebeneinander in derselben Anwendung, die Migration läuft also Route für Route, während Ihr Team weiter ausliefert. Hier die Reihenfolge und die drei üblichen Fehler.

7 Min. Lesezeit

Die nützlichste Tatsache zu dieser Migration lautet: Sie müssen sich nicht entscheiden. pages/ und app/ laufen in derselben Anwendung, im selben Prozess, gegen dasselbe Deployment. Eine Anfrage wird zuerst gegen app/ geprüft und fällt auf pages/ zurück, wenn nichts passt.

Das heißt: Ein Feature-Stopp ist keine Voraussetzung der Migration. Er ist ein Symptom davon, sie als eine große Änderung statt als vierzig kleine geplant zu haben.

Die Reihenfolge, die funktioniert

Migrieren Sie von den Blättern nach innen, nicht von der Wurzel nach außen.

  1. Zuerst eine Route mit wenig Traffic und wenig Risiko. Etwa /about. Nicht weil sie wichtig wäre, sondern weil sie Sie zwingt, das Layout, den Metadaten-Helfer und die Konventionen fürs Datenholen zu bauen, die Sie überall sonst brauchen - auf einer Seite, deren Ausfall für eine Stunde niemandem auffällt.
  2. Der Rest der statischen Marketing-Routen. Die konvertieren fast mechanisch, und dort ist der Performance-Gewinn am größten.
  3. Dynamische Routen, die nur lesen. Produktseiten, Artikel, Listen. Hier verwandeln Sie getStaticProps und getServerSideProps in async -Komponenten, und das ist der Großteil der mechanischen Arbeit.
  4. Authentifizierte und interaktive Routen. Dashboards, Einstellungen, Checkout. Sie tragen das echte Risiko, weil sie die Mutationen tragen.
  5. Das Root-Layout zuletzt. _app.tsx und _document.tsx früh zu verschieben zwingt jede Route in den neuen Baum, bevor eine davon bereit ist.

Teams, die das umdrehen - weil die Hülle sich wie das Fundament anfühlt -, sind die Teams, die am Ende einfrieren.

Was aus jeder Pages-API wird

Pages RouterApp Router
getStaticPropsasync-Komponente + fetch mit revalidate oder Tag
getServerSidePropsasync-Komponente + cache: 'no-store'
getStaticPathsgenerateStaticParams
_app.tsxapp/layout.tsx
_document.tsxapp/layout.tsx (die html- und body-Tags)
next/headder metadata-Export oder generateMetadata
useRouter().querydie Props params und searchParams
API-RoutenRoute Handler, oder Server Actions für Mutationen

Eine Route wird dabei meist kürzer. Aus diesem:

// pages/products/[slug].tsx
export async function getStaticProps({ params }) {
  const product = await getProduct(params.slug);
  if (!product) return { notFound: true };
  return { props: { product }, revalidate: 3600 };
}
 
export async function getStaticPaths() {
  const products = await getProducts();
  return {
    paths: products.map((p) => ({ params: { slug: p.slug } })),
    fallback: 'blocking',
  };
}
 
export default function ProductPage({ product }) {
  return <Product data={product} />;
}

wird dieses:

// app/products/[slug]/page.tsx
export async function generateStaticParams() {
  const products = await getProducts();
  return products.map((p) => ({ slug: p.slug }));
}
 
export default async function ProductPage({ params }) {
  const { slug } = await params;
  const product = await getProduct(slug);
  if (!product) notFound();
 
  return <Product data={product} />;
}

Beachten Sie await params. In aktuellen Next.js-Versionen sind params und searchParams Promises. Code aus einem zwei Jahre alten Tutorial destrukturiert sie direkt und schlägt auf eine Weise fehl, die wie ein Datenproblem aussieht.

Was sich die beiden Router teilen und was nicht

Die Koexistenz, die eine inkrementelle Migration überhaupt möglich macht, ist nicht vollständig - und dort, wo sie es nicht ist, bleibt eine Migration stecken.

Sie teilen sich den Request. Ein Deployment, eine Domain, eine next.config, und eine Route löst zu dem Router auf, dem sie gehört - wobei app/ gewinnt, wenn beide denselben Pfad definieren. Genau dieses Detail trägt die ganze Strategie: Sie fügen die App-Router-Variante hinzu, sie gewinnt, und die alte Datei bleibt als Rollback liegen, der eine Löschung vom Aufräumen und ein Revert vom Wiederinbetriebnehmen entfernt ist.

Sie teilen sich kein Layout. Ein pages/_app.tsx umschließt nur Pages-Routen, ein app/layout.tsx nur App-Routen - alles Globale also, Analytics, ein Theme-Provider, ein Header, existiert für die Dauer der Migration doppelt. Duplizieren Sie es bewusst und löschen Sie am Ende eine Kopie. Die Duplizierung zu vermeiden, indem Sie eines in das andere importieren, ist der Weg, auf dem ein Client-Provider den Server-Baum umschließt.

Sie teilen sich keine clientseitige Navigation. Ein Link von einer Pages-Route zu einer App-Route ist ein vollständiger Seitenaufbau, kein Client-Übergang. In einer langen Migration zeigt sich das als Seite, die sich langsamer anfühlt als beide Enden, und es löst sich, sobald das Paar auf derselben Seite liegt.

Die drei Stellen, an denen es schiefgeht

Die Hülle use client markieren, damit die Migration kompiliert. Irgendwas im alten Layout nutzt einen Context-Provider, der Build meckert, und jemand setzt 'use client' an den Anfang des Root-Layouts. Die Anwendung kompiliert wieder, jede Route schickt den gesamten Baum an den Browser, und der Hauptnutzen der Migration ist weg, während die Kosten weiter anfallen. Schieben Sie Provider in eine Client-Komponente hinunter, die children umschließt, und lassen Sie das Layout auf dem Server. Das ist dieselbe Disziplin wie in wo use client Sie wirklich kostet.

Die Datenschicht unverändert übernehmen. getServerSideProps lief einmal pro Route, also haben die meisten Codebasen eine einzige Alles-Holen-Funktion pro Seite gebaut. Im App Router kann jede Komponente holen, was sie braucht, und identische Anfragen in einem Render werden für Sie dedupliziert. Die Gott-Funktion funktioniert weiterhin - und lässt die ganze Route weiter auf den langsamsten Aufruf warten, sodass nichts streamt und Suspense Ihnen nichts bringt.

next/head stehen lassen. In app/ ist es still wirkungslos. Kein Fehler, keine Warnung - nur eine Route ohne Titel und ohne Beschreibung, was niemandem auffällt, bis einen Monat später ein SEO-Report kommt. Greppen Sie danach, bevor Sie ausliefern, und konvertieren Sie jede Stelle zum metadata-Export.

Die Auth-Prüfung zuletzt migrieren. Authentifizierung, die für getServerSideProps geschrieben wurde, gibt ein Redirect aus einer Funktion zurück, der die ganze Route gehört. Im App Router gehört diese Entscheidung meist in ein Layout oder an die Grenze der Route selbst, und die Form unterscheidet sich genug, dass Teams sie aufschieben - womit die Routen dahinter als Letzte umziehen und die Migration einen Schritt vor dem Ziel stehen bleibt, ausgerechnet auf den wichtigsten Oberflächen. Ziehen Sie eine authentifizierte Route absichtlich früh um, solange wenig auf dem Spiel steht.

Route für Route belegen, dass es gewirkt hat

Bevor Sie eine Route verschieben, halten Sie fest, was sie jetzt tut: LCP und INP aus Felddaten, das gerenderte HTML, Titel und Canonical, das JSON-LD, das First Load JS. Nach dem Verschieben vergleichen Sie. Eine Migration ohne Vorher ist ein Rewrite mit Zusatzschritten.

Die Routen, die wir so verschoben haben, verlieren typischerweise 30-50 % ihres JavaScripts und gewinnen kein Risiko dazu, weil zu keinem Zeitpunkt mehr als eine Route in einem unbekannten Zustand war.

Wann man aufhören darf

Eine Migration, die bei neunzig Prozent ankommt und dort parkt, ist ein häufiges und unterschätztes Ergebnis - und nicht automatisch ein Scheitern. Zwei Router in einer Anwendung sind eine unterstützte Konfiguration, kein kaputter Zustand, und die letzten zehn Prozent sind meist eine Handvoll Routen mit einem echten Grund, sperrig zu sein: eine Abhängigkeit von einem Custom Server, eine Bibliothek ohne Server-Component-Geschichte, eine Seite, die ohnehin nächstes Quartal ersetzt wird.

Der Test, ob Parken akzeptabel ist, lautet: kosten die verbliebenen Pages-Routen laufend etwas? Sind sie isoliert, wenig besucht und ändert sie niemand, ist Fertigwerden Ordnungsliebe statt Wert. Halten sie das gemeinsame Layout in Geiselhaft - sodass die App-Router-Seite den duplizierten Provider nicht entfernen kann -, dann kosten sie Sie auf jeder Route, und das ist das Argument fürs Fertigwerden.

Nie akzeptabel ist, aufzuhören, ohne es festzuhalten. Eine Migration ohne schriftlichen Endzustand wird innerhalb eines Personalwechsels zu einer Codebasis, in der niemand weiß, welche Konvention gilt - und beide weiter kopiert werden.

Wenn die Codebasis, die Sie migrieren, zusätzlich eine ist, die niemand anfassen will, liegt darauf ein zweites Problem - eine geerbte Next.js-Codebasis übernehmen beschreibt, was zuerst zu tun ist.

Verwandte Fragen

Können beide Router gleichzeitig laufen?
Ja, und genau darum geht es. Sie existieren in einer Anwendung nebeneinander, die Migration läuft also Route für Route und nichts muss dafür stehenbleiben.
Welche Route sollte zuerst umziehen?
Eine echte, aber nicht kritische. Eine Marketingseite bringt dem Team das Modell bei. Der Checkout bringt es ihm unter Druck bei.
Was ersetzt getServerSideProps?
Nichts, denn die Komponente selbst wird zu dem Ort, an dem Daten geholt werden. Das ist die Änderung, die am längsten braucht, bis sie sitzt, und die es verdient, richtig statt mechanisch gemacht zu werden.
Müssen wir alles umziehen?
Irgendwann ja, wenn Sie ein einziges Denkmodell im Code wollen. Aber eine steckengebliebene Migration, die die schweren Routen zurückgelassen hat, ist immer noch besser als eine Neuentwicklung, die nie ausgeliefert wurde.

← Zurück zu allen Artikeln

Anrufen+1 848 272 7583WhatsApp+90 850 308 5436E-Mailinfo@codefacture.comKontaktseite