Datenabruf-Muster
Es gibt einige empfohlene Muster und Best Practices für den Datenabruf in React und Next.js. Diese Seite behandelt einige der gängigsten Muster und deren Anwendung.
Datenabruf auf dem Server
Wann immer möglich, empfehlen wir den Datenabruf auf dem Server. Dies ermöglicht Ihnen:
- Direkten Zugriff auf Backend-Datenressourcen (z.B. Datenbanken).
- Erhöhte Sicherheit Ihrer Anwendung, indem sensible Informationen wie Zugriffstoken und API-Schlüssel nicht an den Client weitergegeben werden.
- Datenabruf und Rendering in derselben Umgebung. Dies reduziert sowohl die Kommunikation zwischen Client und Server als auch die Belastung des Hauptthreads auf dem Client.
- Mehrere Datenabrufe mit einer einzigen Roundtrip-Anfrage anstelle mehrerer einzelner Anfragen vom Client.
- Reduzierung von Wasserfall-Effekten zwischen Client und Server.
- Abhängig von Ihrer Region kann der Datenabruf näher an der Datenquelle erfolgen, was Latenz verringert und die Leistung verbessert.
Sie können Daten auf dem Server mit Server Components, Route Handlers und Server Actions abrufen.
Datenabruf dort, wo sie benötigt werden
Wenn Sie dieselben Daten (z.B. aktuellen Benutzer) in mehreren Komponenten eines Baums benötigen, müssen Sie die Daten nicht global abrufen oder Props zwischen Komponenten weitergeben. Stattdessen können Sie fetch
oder React cache
in der Komponente verwenden, die die Daten benötigt, ohne sich um Leistungseinbußen durch mehrere Anfragen für dieselben Daten sorgen zu müssen.
Dies ist möglich, weil fetch
-Anfragen automatisch memoized werden. Erfahren Sie mehr über Request Memoization
Gut zu wissen: Dies gilt auch für Layouts, da Daten nicht zwischen einem Eltern-Layout und seinen Kindern weitergegeben werden können.
Streaming
Streaming und Suspense sind React-Features, die es ermöglichen, Teile der UI progressiv zu rendern und schrittweise an den Client zu streamen.
Mit Server Components und verschachtelten Layouts können Sie Teile der Seite, die keine spezifischen Daten benötigen, sofort rendern und einen Ladezustand für Teile anzeigen, die Daten abrufen. Dies bedeutet, dass der Benutzer nicht warten muss, bis die gesamte Seite geladen ist, bevor er mit ihr interagieren kann.

Weitere Informationen zu Streaming und Suspense finden Sie auf den Seiten Loading UI und Streaming und Suspense.
Paralleler und sequenzieller Datenabruf
Beim Datenabruf innerhalb von React-Komponenten sollten Sie zwei Muster beachten: Paralleler und sequenzieller Datenabruf.

- Beim sequenziellen Datenabruf sind Anfragen in einer Route voneinander abhängig und erzeugen somit Wasserfälle. Es gibt Fälle, in denen Sie dieses Muster wünschen, weil ein Abruf vom Ergebnis eines anderen abhängt oder eine Bedingung erfüllt sein muss, bevor der nächste Abruf Ressourcen spart. Dieses Verhalten kann jedoch auch unbeabsichtigt sein und zu längeren Ladezeiten führen.
- Beim parallelen Datenabruf werden Anfragen in einer Route gleichzeitig initiiert und die Daten parallel geladen. Dies reduziert Wasserfälle zwischen Client und Server und die Gesamtzeit für den Datenabruf.
Sequenzieller Datenabruf
Wenn Sie verschachtelte Komponenten haben und jede Komponente ihre eigenen Daten abruft, erfolgt der Datenabruf sequenziell, wenn diese Datenanfragen unterschiedlich sind (dies gilt nicht für Anfragen nach denselben Daten, da diese automatisch memoized werden).
Zum Beispiel wird die Playlists
-Komponente erst mit dem Datenabruf beginnen, wenn die Artist
-Komponente damit fertig ist, da Playlists
von der artistID
-Prop abhängt:
// ...
async function Playlists({ artistID }: { artistID: string }) {
// Warten auf die Playlists
const playlists = await getArtistPlaylists(artistID)
return (
<ul>
{playlists.map((playlist) => (
<li key={playlist.id}>{playlist.name}</li>
))}
</ul>
)
}
export default async function Page({
params: { username },
}: {
params: { username: string }
}) {
// Warten auf den Künstler
const artist = await getArtist(username)
return (
<>
<h1>{artist.name}</h1>
<Suspense fallback={<div>Loading...</div>}>
<Playlists artistID={artist.id} />
</Suspense>
</>
)
}
// ...
async function Playlists({ artistID }) {
// Warten auf die Playlists
const playlists = await getArtistPlaylists(artistID)
return (
<ul>
{playlists.map((playlist) => (
<li key={playlist.id}>{playlist.name}</li>
))}
</ul>
)
}
export default async function Page({ params: { username } }) {
// Warten auf den Künstler
const artist = await getArtist(username)
return (
<>
<h1>{artist.name}</h1>
<Suspense fallback={<div>Loading...</div>}>
<Playlists artistID={artist.id} />
</Suspense>
</>
)
}
In solchen Fällen können Sie loading.js
(für Routensegmente) oder React <Suspense>
(für verschachtelte Komponenten) verwenden, um einen sofortigen Ladezustand anzuzeigen, während React das Ergebnis streamt.
Dies verhindert, dass die gesamte Route durch den Datenabruf blockiert wird, und der Benutzer kann mit den nicht blockierten Teilen der Seite interagieren.
Blockierende Datenanfragen:
Ein alternativer Ansatz zur Vermeidung von Wasserfällen besteht darin, Daten global im Stamm Ihrer Anwendung abzurufen. Dies blockiert jedoch das Rendering für alle darunterliegenden Routensegmente, bis der Datenabruf abgeschlossen ist. Dies kann als "Alles-oder-nichts"-Datenabruf beschrieben werden. Entweder Sie haben alle Daten für Ihre Seite oder Anwendung, oder keine.
Jede
await
-Anfrage blockiert das Rendering und den Datenabruf für den gesamten darunterliegenden Baum, es sei denn, sie ist in eine<Suspense>
-Grenze eingeschlossen oder es wirdloading.js
verwendet. Eine weitere Alternative ist der parallele Datenabruf oder das Preload-Muster.
Paralleler Datenabruf
Um Daten parallel abzurufen, können Sie Anfragen außerhalb der Komponenten definieren, die die Daten verwenden, und sie dann innerhalb der Komponente aufrufen. Dies spart Zeit, indem beide Anfragen parallel initiiert werden, allerdings sieht der Benutzer das gerenderte Ergebnis erst, nachdem beide Promises aufgelöst wurden.
Im folgenden Beispiel werden die Funktionen getArtist
und getArtistAlbums
außerhalb der Page
-Komponente definiert, innerhalb der Komponente aufgerufen und auf beide Promises gewartet:
import Albums from './albums'
async function getArtist(username: string) {
const res = await fetch(`https://api.example.com/artist/${username}`)
return res.json()
}
async function getArtistAlbums(username: string) {
const res = await fetch(`https://api.example.com/artist/${username}/albums`)
return res.json()
}
export default async function Page({
params: { username },
}: {
params: { username: string }
}) {
// Beide Anfragen parallel initiieren
const artistData = getArtist(username)
const albumsData = getArtistAlbums(username)
// Auf die Auflösung der Promises warten
const [artist, albums] = await Promise.all([artistData, albumsData])
return (
<>
<h1>{artist.name}</h1>
<Albums list={albums}></Albums>
</>
)
}
import Albums from './albums'
async function getArtist(username) {
const res = await fetch(`https://api.example.com/artist/${username}`)
return res.json()
}
async function getArtistAlbums(username) {
const res = await fetch(`https://api.example.com/artist/${username}/albums`)
return res.json()
}
export default async function Page({ params: { username } }) {
// Beide Anfragen parallel initiieren
const artistData = getArtist(username)
const albumsData = getArtistAlbums(username)
// Auf die Auflösung der Promises warten
const [artist, albums] = await Promise.all([artistData, albumsData])
return (
<>
<h1>{artist.name}</h1>
<Albums list={albums}></Albums>
</>
)
}
Um die Benutzererfahrung zu verbessern, können Sie eine Suspense Boundary hinzufügen, um das Rendering aufzuteilen und Teile des Ergebnisses so schnell wie möglich anzuzeigen.
Vorabruf von Daten
Eine weitere Möglichkeit, Wasserfälle zu vermeiden, ist das Preload-Muster. Sie können optional eine preload
-Funktion erstellen, um den parallelen Datenabruf weiter zu optimieren. Mit diesem Ansatz müssen Sie keine Promises als Props weitergeben. Die preload
-Funktion kann auch einen beliebigen Namen haben, da es sich um ein Muster und nicht um eine API handelt.
import { getItem } from '@/utils/get-item'
export const preload = (id: string) => {
// void wertet den gegebenen Ausdruck aus und gibt undefined zurück
// https://developer.mozilla.org/docs/Web/JavaScript/Reference/Operators/void
void getItem(id)
}
export default async function Item({ id }: { id: string }) {
const result = await getItem(id)
// ...
}
import { getItem } from '@/utils/get-item'
export const preload = (id) => {
// void wertet den gegebenen Ausdruck aus und gibt undefined zurück
// https://developer.mozilla.org/docs/Web/JavaScript/Reference/Operators/void
void getItem(id)
}
export default async function Item({ id }) {
const result = await getItem(id)
// ...
}
import Item, { preload, checkIsAvailable } from '@/components/Item'
export default async function Page({
params: { id },
}: {
params: { id: string }
}) {
// Datenabruf für Item starten
preload(id)
// weitere asynchrone Aufgabe ausführen
const isAvailable = await checkIsAvailable()
return isAvailable ? <Item id={id} /> : null
}
import Item, { preload, checkIsAvailable } from '@/components/Item'
export default async function Page({ params: { id } }) {
// Datenabruf für Item starten
preload(id)
// weitere asynchrone Aufgabe ausführen
const isAvailable = await checkIsAvailable()
return isAvailable ? <Item id={id} /> : null
}
Verwendung von React cache
, server-only
und dem Preload-Muster
Sie können die cache
-Funktion, das preload
-Muster und das server-only
-Paket kombinieren, um eine Datenabruf-Hilfsfunktion zu erstellen, die in Ihrer gesamten Anwendung verwendet werden kann.
import { cache } from 'react'
import 'server-only'
export const preload = (id: string) => {
void getItem(id)
}
export const getItem = cache(async (id: string) => {
// ...
})
import { cache } from 'react'
import 'server-only'
export const preload = (id) => {
void getItem(id)
}
export const getItem = cache(async (id) => {
// ...
})
Mit diesem Ansatz können Sie Daten vorab abrufen, Antworten zwischenspeichern und sicherstellen, dass dieser Datenabruf nur auf dem Server erfolgt.
Die Exporte von utils/get-item
können von Layouts, Seiten oder anderen Komponenten verwendet werden, um zu steuern, wann die Daten eines Elements abgerufen werden.
Gut zu wissen:
- Wir empfehlen die Verwendung des
server-only
-Pakets, um sicherzustellen, dass Server-Datenabruffunktionen niemals auf dem Client verwendet werden.