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.
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.
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.
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.
