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.
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.
- 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. - Der Rest der statischen Marketing-Routen. Die konvertieren fast mechanisch, und dort ist der Performance-Gewinn am größten.
- Dynamische Routen, die nur lesen. Produktseiten, Artikel, Listen. Hier
verwandeln Sie
getStaticPropsundgetServerSidePropsinasync-Komponenten, und das ist der Großteil der mechanischen Arbeit. - Authentifizierte und interaktive Routen. Dashboards, Einstellungen, Checkout. Sie tragen das echte Risiko, weil sie die Mutationen tragen.
- Das Root-Layout zuletzt.
_app.tsxund_document.tsxfrü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 Router | App Router |
|---|---|
getStaticProps | async-Komponente + fetch mit revalidate oder Tag |
getServerSideProps | async-Komponente + cache: 'no-store' |
getStaticPaths | generateStaticParams |
_app.tsx | app/layout.tsx |
_document.tsx | app/layout.tsx (die html- und body-Tags) |
next/head | der metadata-Export oder generateMetadata |
useRouter().query | die Props params und searchParams |
| API-Routen | Route 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.
