Contact salesFree trial
Blog

Vermeiden Sie häufige Fehler mit dem Next.js App Router

next.jsJavaScriptAPILeistung
Teilen Sie

Der Next.js App Router hat eine Reihe nützlicher Funktionen eingeführt, wie z.B. Unterstützung für React Server Components, besseres Caching, Streaming Responses und verschachtelte Layouts. Diese Funktionen ermöglichen es Entwicklern, eine verbesserte Benutzererfahrung zu schaffen und gleichzeitig eine bessere Entwicklererfahrung zu genießen. Auch wenn diese Funktionen hilfreich und benutzerfreundlich sind, können sie bei falscher Anwendung zu Fehlern oder Leistungsproblemen führen.

In diesem Artikel gehen wir auf die häufigsten Fehler ein, die man mit dem Next.js App Router machen kann. Außerdem werden wir Lösungen mit Codebeispielen vorstellen, um diese Fehler zu entschärfen und das Benutzererlebnis zu verbessern.

Beachten Sie, dass die Beispiele in diesem Artikel die Next.js /src/app Projektstruktur verwenden.

Redundante Netzwerkanfragen innerhalb von Komponenten

Das Abrufen von Daten auf der Client-Seite ist in React seit vielen Jahren gängige Praxis. So kann man leicht in die Falle tappen, redundante Netzwerkanfragen auf der Clientkomponentezu stellen , was aufgrund der zusätzlichen Netzwerklatenz und der Notwendigkeit, verschiedene HTTP-Routenhandler zu erstellen, nicht so effizient ist.

Im folgenden Beispiel holt die <UserComponent> Daten auf der Client-Seite mit dem useEffect-Hook ab. Dieser Ansatz funktioniert zwar, ist aber nicht ideal, da er mehr Netzwerkanfragen auf der Client-Seite erzeugt und zu einer langsameren Benutzererfahrung führen kann.

"use client"; import { useEffect, useState } from "react"; const UserComponent = () => { const [data, setData] = useState<{ name: string; email: string } | null>( null ); useEffect(() => { const fetchData = async () => { const res = await fetch("https://jsonplaceholder.typicode.com/users/1"); const result = await res.json(); setData(result); }; fetchData(); }, []); if (!data) return <div>Laden...</div>; return ( <div> <h1>{data.name}</h1> <p>{data.email}</p> </div> ); }; export default UserComponent;

Um dies zu beheben, können Sie eine Serverkomponenteerstellen , die Daten auf der Serverseite abruft und sie in der Komponente UserComponentFixed auf dem Server wiedergibt:

const fetchUser = async () => { const res = await fetch("https://jsonplaceholder.typicode.com/users/1"); if (!res.ok) throw new Error("Failed to fetch user"); return res.json(); }; const UserComponentFixed = async () => { const data = await fetchUser(); // Abrufen auf der Serverseite return ( <div> <h1>{data.name}</h1> <p>{data.email}</p> </div> ); }; export default UserComponentFixed;

Dadurch wird sichergestellt, dass der Client ein HTML-Markup erhält, was die Belastung des Clients beim Abrufen und Rendern der Daten verringert und somit die allgemeine Benutzerfreundlichkeit verbessert.

Dynamische Routen versehentlich als statisch rendern

Der Next.js App Router implementiert einen Static-First-Ansatz, was bedeutet, dass Seiten immer als statisch gerendert werden, sofern sie nicht explizit dynamische APIsverwenden . Während dieser Ansatz die Leistung der Anwendung verbessert, kann er Probleme verursachen, indem er die Seite fälschlicherweise als statisch identifiziert, wenn Sie keine dynamischen APIs verwenden, aber die Seite dynamisch rendern wollen.

Im folgenden Beispiel holt die Serverkomponente Home bei jeder Anforderung zufällige Benutzerdaten ab und zeigt sie auf der Seite an:

// src/app/page.tsx export function random(min: Zahl, max: Zahl) { return Math.floor(Math.random() * (max - min) + min); } export const fetchUser = async (id: Zahl) => { const res = await fetch("https://jsonplaceholder.typicode.com/users/" + id); if (!res.ok) throw new Error("Failed to fetch user"); const data = await res.json(); return { ...data }; }; export default async function Home() { const userId = random(1, 10); const data = await fetchUser(userId); return ( <div> <h1>{data.name}</h1> <p>{data.email}</p> </div> ); }

Während der Entwicklung generiert die Home-Komponente bei jedem Rendering eine neue Zufallszahl. Im Produktions-Build, das nach dem Ausführen des nächsten Builds erstellt wird, wird die Seite jedoch statisch generiert, so dass sie stets die Details für denselben Benutzer anzeigt. Dies ist nicht das erwartete Verhalten.

Um dieses Problem zu beheben, können Sie die Verbindungsfunktionverwenden , um die Seite explizit als dynamische Seite zu rendern. Die Seite wird dann bei jedem Neuladen zufällige Benutzerdaten darstellen:

// src/app/page.tsx import { connection } from "next/server"; export function random(min: Zahl, max: Zahl) { return Math.floor(Math.random() * (max - min) + min); } export const fetchUser = async (id: Zahl) => { const res = await fetch("https://jsonplaceholder.typicode.com/users/" + id); if (!res.ok) throw new Error("Failed to fetch user"); const data = await res.json(); return { ...data }; }; export default async function Home() { await connection(); const userId = random(1, 10); const data = await fetchUser(userId); return ( <div> <h1>{data.name}</h1> <p>{data.email}</p> </div> ); }

Der Versuch, serverspezifische Aktionen in Client-Komponenten zu verwenden

MitServer-Aktionen können Sie von den Server- und Client-Komponenten aus Funktionen auf dem Server ausführen, z. B. für die Übermittlung von Formularen und die Änderung von Daten.

Während Sie eine Serveraktion direkt innerhalb einer Serverkomponente erstellen können, ist dies bei Clientkomponenten nicht möglich. Um Serveraktionen sicher in Client-Komponenten zu definieren und zu verwenden, müssen Sie Serveraktionen in einer separaten Datei definieren und in die Client-Komponenten importieren. Auf diese Weise stellen Sie sicher, dass die Serveraktion nur auf dem Server ausgeführt wird und der Code nicht in das Client-Bundle aufgenommen wird.

Wenn Sie eine Client-Komponente für ein To-Do-Element erstellen, ist es sinnvoll, eine separate Datei für die Server-Aktion zu erstellen und sie in die Client-Komponente zu importieren, wie im folgenden Beispiel gezeigt:

"use server"; // src/app/actions.ts export async function completeTodo(id: string) { // server action to perform todo completion }

"use client"; // src/components/TodoItem.tsx import { completeTodo } from "@/app/actions"; export function TodoItem() { const id = "todo_23"; return <button onClick={() => completeTodo(id)}>Complete</button>; }

Falsche Platzierung von Suspense-Grenzen, was zu schlechten Ladeerfahrungen führt

Mit React Suspense können Sie einen Ladezustand anzeigen, wenn Sie auf eine Antwort warten. Aber Sie müssen auch wissen, wo Sie die Suspense Boundaries platzieren müssen, um sicherzustellen, dass sie die Leistung der Anwendung verbessern und ein gutes Benutzererlebnis bieten.

Im folgenden Beispiel wird eine Spannungsgrenze auf der gesamten Seite gesetzt:

// app/page.tsx import { Suspense } from "react"; import UserProfile from "./components/UserProfile"; import RecentPosts from "./components/RecentPosts"; export default function Page() { return ( <Suspense fallback={<div>Loading page...</div>}> <UserProfile />{" "}{/* Beide Komponenten werden verzögert, wenn eine noch geladen wird */} <RecentPosts /> </Suspense> ); }

Dadurch wird das Rendering blockiert, bis alle Komponenten der Seite fertig geladen sind. Es kann auch zu langsamen Ladezeiten führen und sich negativ auf die Leistung der Anwendung auswirken.

Es ist sinnvoller, die Suspense-Grenze auf jede Komponente zu legen, die Daten lädt:

// app/page.tsx import { Suspense } from "react"; import UserProfile from "./components/UserProfile"; import RecentPosts from "./components/RecentPosts"; export default function Page() { return ( <div> <h1>Willkommen auf dem Dashboard</h1> {/* Platzieren Sie Suspense-Grenzen um jeden Abschnitt */} <Suspense fallback={<div>Loading profile...</div>}> <UserProfile /> {/* Zeigt den Ladezustand nur für diese Komponente */}</Suspense> <Suspense fallback={<div>Laden der letzten Beiträge...</div>}> <RecentPosts /> {/* Zeigt einen anderen Ladezustand für Beiträge */}</Suspense> </div> ); }

Auf diese Weise kann der Benutzer die Dashboard-Seite mit Ladeskeletten sehen und die Komponenten werden gerendert, wenn sie geladen werden, ohne sich gegenseitig zu blockieren.

Verwendung clientseitiger Hooks für den Zugriff auf Anfragedaten, was zu Fehlern führt

Der App Router bietet Hilfsfunktionen für den Zugriff auf Anfragedaten, wie z. B. Header und Cookies, direkt in den Route-Handlern und den Serverkomponenten. Sie können diese Funktionen jedoch nicht in den Client-Komponenten verwenden, da es sich um serverseitige Dienstprogramme handelt. Stattdessen können Sie die usePathname-, useSearchParams- und useParams-Hooks in den Client-Komponenten verwenden, um auf Informationen über die aktuelle Route zuzugreifen.

Wenn Sie in der Client-Komponente auf die Anfrage-Header oder Cookies zugreifen müssen, können Sie auf diese in einer Server-Komponente zugreifen und den abgeleiteten Wert an die Client-Komponente übergeben. Im folgenden Beispiel muss die Client-Komponente UserProfile auf den Namen des angemeldeten Benutzers zugreifen, der nur aus dem gesicherten Cookie abgerufen werden kann:

// src/components/UserProfile.tsx "use client"; // Markiert als Clientkomponententyp UserProfileProps = { userName: string; }; export default function UserProfile({ userName }: UserProfileProps) { // einige interaktive Logik für die Client-Komponente return ( <div> <h2>Benutzerinformationen</h2> <p>Angemeldet als: {Benutzername}</p> </div> ); }

Um dieses Problem zu lösen, können Sie eine Serverkomponente erstellen, die den Benutzernamen aus dem Cookie abruft und ihn an die Clientkomponente weitergibt:

// src/app/page.tsx import { cookies } from "next/headers"; import { UserProfile } from "./components/UserProfile"; export default async function Page() { const cookieStore = await cookies(); const session = cookieStore.get("session"); const user = await getUserFromSession(session); // this is a custom function return ( <div> <UserProfile userName={user.name} /> </div> ); }

Platzierung von Kontextanbietern in Serverkomponenten, wo sie nicht unterstützt werden

React Context ist eine reine Client-API und wird in den Server-Komponenten nicht unterstützt. Daher würde die Verwendung von Context direkt im Stammlayout nicht funktionieren, wie im folgenden Beispiel gezeigt:

// src/app/layout.tsx import { createContext } from "react"; // createContext wird in Serverkomponenten nicht unterstützt export const ThemeContext = createContext({});

 
export default function RootLayout({ children }) { return ( <html> <body> <ThemeContext.Provider value="dark">{children}</ThemeContext.Provider> </body> </html> ); }

Um dieses Problem zu beheben, können Sie eine Clientkomponente erstellen, die den Kontext exportiert und ihn in der Serverkomponente verwendet:

// src/components/ThemeProvider.tsx "use client"; import { createContext } from "react"; export const ThemeContext = createContext({}); export default function ThemeProvider({ children, }: { children: React.ReactNode; }) { return <ThemeContext.Provider value="dark">{children}</ThemeContext.Provider>; }

// src/app/layout.tsx import ThemeProvider from "./components/ThemeProvider"; export default function RootLayout({ children, }: { children: React.ReactNode; }) { return ( <html> <body><ThemeProvider>{children}</ThemeProvider> </body> </html> ); }

Es ist besser, die Context-Provider so tief wie möglich im Anwendungsbaum zu verwenden, damit Next.js so viel wie möglich optimieren kann. Wenn also der ThemeProvider nur in der /dashboard/settings-Route verwendet wird, sollte er in der nächstgelegenen layout.tsx-Datei gerendert werden, um die beste Leistung zu erzielen.

Unsachgemäße Vermischung von Server- und Client-Komponenten, die zu Funktionsproblemen führt

Server- und Client-Komponenten arbeiten zusammen, haben aber jeweils eine spezifische Funktion. Verwenden Sie Client-Komponenten für die Zustandsverwaltung und Interaktivität. Sie können React-Hooks innerhalb einer Client-Komponente verwenden, aber nicht innerhalb einer Server-Komponente, wie im folgenden Beispiel:

// src/components/CheckoutForm.tsx "use client"; import { useState, useEffect } from "react"; export default function CheckoutForm() { const [name, setName] = useState("");

 
 useEffect(() => { // some effect logic }, []); return <form>{/* interaktives Formular */}</form>; }

Serverkomponenten eignen sich am besten für das Abrufen von Daten, da sie zusätzliche Sicherheits- und Leistungsvorteile bieten. Sie werden auch bevorzugt, wenn Sie umfangreiche Bibliotheken für die Datenmanipulation benötigen, wie z. B. Bibliotheken für die Datenverwaltung oder das Parsen von Daten. Hier ist ein Beispiel:

// src/components/UserDetails.tsx import { fetchUser } from "./UserAvatar"; const UserDetailsComponent = async () => { const data = await fetchUser(5); return ( <div> Kein Speicher:
 <h1>{data.name}</h1> <p>{data.email}</p> </div> ); }; export default UserDetailsComponent;

Das folgende Beispiel demonstriert die Verwendung von Client- und Serverkomponenten auf derselben Seite:

// src/app/page.tsx import UserDetails from "./components/UserDetails"; export default function Page() { // Die Funktion Page ist eine Serverkomponente return ( <div> <h1>Dashboard</h1>{/* Einbindung einer Client-Komponente zur Behandlung dynamischer Zustände */}<CheckoutForm /> <UserDetails /> </div> ); }

Fehler im dynamischen Routing, insbesondere bei der Migration von getStaticPaths und getServerSideProps

Die neueste Next. js-Version unterstützt auch die alte Pages-Router-Verzeichnisstruktur, um einen reibungslosen Übergang für bestehende Projekte zu ermöglichen, die nach und nach die App-Router-Funktionen übernehmen wollen.

Die Funktion getServerSideProps wird verwendet, um die Daten auf der Serverseite zu holen und das Ergebnis an die Seitenkomponente zu übergeben. Bei der Migration kann es also verlockend sein, die Funktion in einen Route-Handler umzuwandeln und sie über einen Fetch-API-Aufruf zu konsumieren. Durch das Hinzufügen von React Server Components können Sie jedoch den serverseitigen Code zum Abrufen von Daten direkt in einer Serverkomponente platzieren.

Im folgenden Beispiel wird die Funktion getServerSideProps verwendet, um Daten auf der Serverseite zu holen und sie an die Seitenkomponente Orders weiterzugeben, und die Komponente Orders wird dann verwendet, um die Daten auf der Clientseite zu rendern:

// pages/orders.js (Pages Router) import React from "react"; export default function Orders({ orders }) { return ( <div><h1>Ihre Bestellungen</h1> <ul> {orders.map((order) => ( <li key={order.id}> <p>Bestellung #{bestellung.id}: {order.item} </p></li> ))}</ul> </div> ); } // Daten für die Seite mit getServerSideProps holen export async function getServerSideProps() { const res = await fetch("https://api.example.com/orders"); const orders = await res.json(); return { props: { orders, }, }; }

Im App Router können Sie die Bestelldaten direkt in der Serverkomponente OrdersPage abrufen und auf der Serverseite rendern:

// src/app/orders/page.tsx (App Router, Server Component) export default async function OrdersPage() { // Holen Sie die Daten direkt in der Serverkomponente const res = await fetch("https://api.example.com/orders"); const orders = await res.json(); return ( <div> <h1>Ihre Bestellungen</h1> <ul> {orders.map((order) => ( <li key={order.id}> <p> Bestellung #{bestellung.id}: {order.item} </p> </li> ))} </ul> </div> ); }

In ähnlicher Weise wird die Funktion getStaticPaths im Pages Router verwendet, um eine Liste von Pfaden für eine statisch gerenderte dynamische Route zu erzeugen. Im App Router wird die Funktion generateStaticParams verwendet, um ein Array von Params-Objekten für dynamische Routen zu generieren, wie folgt:

import PostLayout from "./components/post-layout"; export async function generateStaticParams() { return [{ id: "1" }, { id: "2" }]; } async function getPost(params) { const res = await fetch(`https://api.example.com/posts/${params.id}`); const post = await res.json(); return post; } export default async function Post({ params }) { const post = await getPost(params); return <PostLayout post={post} />; }

Fehler durch falsche Modulimporte oder serverseitigen Code in Client-Komponenten

Die Verwendung von Client-Komponenten innerhalb von Server-Komponenten bietet zwar beträchtliche Möglichkeiten für die Komposition, kann aber auch dazu führen, dass die Grenzen zwischen Client- und Server-Code verwischt werden. Diese Verwischung birgt die Gefahr, dass sensibler serverseitiger Code oder Geheimnisse versehentlich in die Client-Komponente aufgenommen werden.

Um sicherzustellen, dass dies nicht passiert, verwenden Sie immer die Anweisung use server am Anfang der Deklarationen der Server-Aktionsfunktionen. Noch besser ist es, eine separate actions.ts-Datei zu erstellen, die alle Server-Aktionen enthält, und die Direktive use server auf der obersten oder Modulebene zu verwenden.

Sie können auch das server-only npm-Paket verwenden, um ein server-only-Modul explizit zu deklarieren. Wenn Sie beispielsweise ein Datenabrufmodul mit dem Namen src/data.ts haben, können Sie das server-only-Modul installieren und wie folgt verwenden, um den Code aus dem clientseitigen Bundle auszuschließen, selbst wenn er versehentlich importiert wird:

 import "server-only"; export async function getUsers() { const res = await fetch("https://jsonplaceholder.typicode.com/users"); return res.json(); }

Probleme mit APIs und Slug-Behandlung, oft aufgrund falscher Konfiguration

Im App Router können Sie API-Routen direkt erstellen, indem Sie eine neue route.ts-Datei unter dem gewünschten Pfad anlegen. Aus dieser Datei können Sie verschiedene HTTP-Methoden exportieren, wie z. B. GET, POST, PUT und andere.

Für die GET-Methode können Sie die Zwischenspeicherung konfigurieren, indem Sie die Option dynamic route configexportieren . Zum Beispiel wird die folgende Route zwischengespeichert und alle sechzig Sekunden neu bestätigt:

export const dynamic = "force-static"; export const revalidate = 60; export async function GET() { const res = await fetch("https://jsonplaceholder.typicode.com/users"); const data = await res.json(); return Response.json({ data }); }

Eine Einschränkung bei der Verwendung von force-static ist, dass die dynamischen Funktionen, wie Header oder Cookies, leere Werte zurückgeben, so dass Sie sie nicht verwenden können.

Die meisten API-Routen benötigen einen oder mehrere dynamische Parameter, um die angeforderte Ressource zu identifizieren. Hierfür können Sie den Slug-Parameter verwenden. Sie können Slugs auf die folgenden Arten definieren:

  • app/users/[slug]/route.ts. Dieses Format passt zu /users/1, /users/2 und so weiter.
  • app/users/[...slug].ts. Dieses Format fängt alle dynamischen Segmente ab, was bedeutet, dass diese Route auf /users/1, /users/1/orders, /users/1/orders/1 usw. passt.
  • app/users/[[...slug]].ts. Dies ist vergleichbar mit einem Catch-All, passt aber auch zu /users, da dynamische Segmente optional sind.

Sie können in Layouts, Seiten und Routen über die params-Anweisung auf den Slug zugreifen. Die Seitesrc/app/blog/[slug]/page.tsx kann beispielsweise wie folgt auf den Slug über die params-Angabe zugreifen:

// src/app/blog/[slug]/page.tsx export default function Page({ params }: { params: { slug: string } }) { return <div>Mein Beitrag: {params.slug}</div>; }

Auf ähnliche Weise können Sie auf den Slug über die Option params in einem GET-Routenhandler zugreifen, wie folgt:

export async function GET( request: Request, { params }: { params: Promise<{ slug: string }> } ) { const slug = (await params).slug; }

Schlussfolgerung

In diesem Artikel haben wir einige der häufigsten Fallstricke bei der Verwendung des Next.js App Routers behandelt und Möglichkeiten erörtert, diese zu beseitigen und die allgemeine Benutzererfahrung zu verbessern.

Wenn Sie mit Next.js-Anwendungenentwickeln , können Sie diese auf Upsun bereitstellen, das Funktionen wie vertikale und horizontale Skalierung, Edge-Caching und Beobachtbarkeit von Haus aus bietet .

Ihr größtes Werk
steht vor der Tür

Kostenloser Test
Discord
© 2025 Platform.sh. All rights reserved.