Einzelseitenanwendungen (SPAs) mit Next.js erstellen
Next.js unterstützt die Erstellung von Einzelseitenanwendungen (Single-Page Applications, SPAs) vollständig.
Dazu gehören schnelle Routenübergänge mit Prefetching, clientseitiges Datenabrufen, die Verwendung von Browser-APIs, die Integration mit Drittanbieter-Client-Bibliotheken, das Erstellen statischer Routen und mehr.
Wenn Sie bereits eine SPA haben, können Sie zu Next.js migrieren, ohne Ihren Code wesentlich ändern zu müssen. Next.js ermöglicht es Ihnen dann, schrittweise Server-Funktionen nach Bedarf hinzuzufügen.
Was ist eine Einzelseitenanwendung?
Die Definition einer SPA variiert. Wir definieren eine "strikte SPA" als:
- Client-seitiges Rendering (CSR): Die Anwendung wird durch eine HTML-Datei (z.B.
index.html
) bereitgestellt. Jede Route, Seitenübergang und Datenabfrage wird durch JavaScript im Browser behandelt. - Keine vollständigen Seitenneuladungen: Anstatt ein neues Dokument für jede Route anzufordern, manipuliert clientseitiges JavaScript das DOM der aktuellen Seite und ruft Daten nach Bedarf ab.
Strikte SPAs benötigen oft große Mengen an JavaScript, die geladen werden müssen, bevor die Seite interaktiv sein kann. Zudem können clientseitige Daten-Wasserfälle schwer zu verwalten sein. Die Erstellung von SPAs mit Next.js kann diese Probleme lösen.
Warum Next.js für SPAs verwenden?
Next.js kann Ihre JavaScript-Bundles automatisch code-splitten und mehrere HTML-Einstiegspunkte für verschiedene Routen generieren. Dadurch wird vermieden, dass unnötiger JavaScript-Code clientseitig geladen wird, was die Bundle-Größe reduziert und schnellere Seitenladezeiten ermöglicht.
Die next/link
-Komponente prefetched automatisch Routen, was Ihnen die schnellen Seitenübergänge einer strikten SPA bietet, aber mit dem Vorteil, dass der Anwendungs-Routing-Zustand in der URL für Verlinkungen und Sharing erhalten bleibt.
Next.js kann als statische Website oder sogar als strikte SPA starten, bei der alles clientseitig gerendert wird. Wenn Ihr Projekt wächst, ermöglicht Next.js Ihnen, schrittweise weitere Server-Funktionen (z.B. React Server Components, Server Actions und mehr) nach Bedarf hinzuzufügen.
Beispiele
Lassen Sie uns gängige Muster für die Erstellung von SPAs und deren Lösung mit Next.js untersuchen.
Verwendung von Reacts use
innerhalb eines Context Providers
Wir empfehlen, Daten in einer übergeordneten Komponente (oder einem Layout) abzurufen, das Promise zurückzugeben und dann den Wert in einer Client-Komponente mit Reacts use
-Hook zu entpacken.
Next.js kann das Datenabrufen frühzeitig auf dem Server starten. In diesem Beispiel ist das das Root-Layout – der Einstiegspunkt Ihrer Anwendung. Der Server kann sofort mit dem Streamen einer Antwort an den Client beginnen.
Indem Sie Ihr Datenabrufen in das Root-Layout "hochziehen", startet Next.js die angegebenen Anfragen auf dem Server frühzeitig, bevor andere Komponenten Ihrer Anwendung geladen werden. Dies eliminiert clientseitige Wasserfälle und verhindert mehrere Roundtrips zwischen Client und Server. Es kann auch die Leistung erheblich verbessern, da Ihr Server näher (und idealerweise colokalisiert) an Ihrer Datenbank ist.
Aktualisieren Sie beispielsweise Ihr Root-Layout, um das Promise aufzurufen, aber nicht darauf zu warten.
import { UserProvider } from './user-provider'
import { getUser } from './user' // eine serverseitige Funktion
export default function RootLayout({
children,
}: {
children: React.ReactNode
}) {
let userPromise = getUser() // NICHT await verwenden
return (
<html lang="en">
<body>
<UserProvider userPromise={userPromise}>{children}</UserProvider>
</body>
</html>
)
}
import { UserProvider } from './user-provider'
import { getUser } from './user' // eine serverseitige Funktion
export default function RootLayout({ children }) {
let userPromise = getUser() // NICHT await verwenden
return (
<html lang="en">
<body>
<UserProvider userPromise={userPromise}>{children}</UserProvider>
</body>
</html>
)
}
Während Sie ein einzelnes Promise verzögern und als Prop an eine Client-Komponente übergeben können, sehen wir dieses Muster häufig in Kombination mit einem React Context Provider. Dies ermöglicht einen einfacheren Zugriff von Client-Komponenten mit einem benutzerdefinierten React-Hook.
Sie können ein Promise an den React Context Provider weiterleiten:
'use client';
import { createContext, useContext, ReactNode } from 'react';
type User = any;
type UserContextType = {
userPromise: Promise<User | null>;
};
const UserContext = createContext<UserContextType | null>(null);
export function useUser(): UserContextType {
let context = useContext(UserContext);
if (context === null) {
throw new Error('useUser must be used within a UserProvider');
}
return context;
}
export function UserProvider({
children,
userPromise
}: {
children: ReactNode;
userPromise: Promise<User | null>;
}) {
return (
<UserContext.Provider value={{ userPromise }}>
{children}
</UserContext.Provider>
);
}
'use client'
import { createContext, useContext, ReactNode } from 'react'
const UserContext = createContext(null)
export function useUser() {
let context = useContext(UserContext)
if (context === null) {
throw new Error('useUser must be used within a UserProvider')
}
return context
}
export function UserProvider({ children, userPromise }) {
return (
<UserContext.Provider value={{ userPromise }}>
{children}
</UserContext.Provider>
)
}
Schließlich können Sie den useUser()
-Hook in jeder Client-Komponente aufrufen und das Promise entpacken:
'use client'
import { use } from 'react'
import { useUser } from './user-provider'
export function Profile() {
const { userPromise } = useUser()
const user = use(userPromise)
return '...'
}
'use client'
import { use } from 'react'
import { useUser } from './user-provider'
export function Profile() {
const { userPromise } = useUser()
const user = use(userPromise)
return '...'
}
Die Komponente, die das Promise konsumiert (z.B. Profile
oben), wird suspendiert. Dies ermöglicht partielle Hydration. Sie können das gestreamte und vorgerenderte HTML sehen, bevor JavaScript vollständig geladen ist.
SPAs mit SWR
SWR ist eine beliebte React-Bibliothek für das Datenabrufen.
Mit SWR 2.3.0 (und React 19+) können Sie Server-Funktionen schrittweise neben Ihrem bestehenden SWR-basierten clientseitigen Datenabrufcode übernehmen. Dies ist eine Abstraktion des obigen use()
-Musters. Das bedeutet, Sie können Datenabruf zwischen Client und Server verschieben oder beides verwenden:
- Nur Client:
useSWR(key, fetcher)
- Nur Server:
useSWR(key)
+ RSC-bereitgestellte Daten - Gemischt:
useSWR(key, fetcher)
+ RSC-bereitgestellte Daten
Beispielsweise können Sie Ihre Anwendung mit <SWRConfig>
und einem fallback
umschließen:
import { SWRConfig } from 'swr'
import { getUser } from './user' // eine serverseitige Funktion
export default function RootLayout({
children,
}: {
children: React.ReactNode
}) {
return (
<SWRConfig
value={{
fallback: {
// Wir verwenden hier KEIN await für getUser()
// Nur Komponenten, die diese Daten lesen, werden suspendiert
'/api/user': getUser(),
},
}}
>
{children}
</SWRConfig>
)
}
import { SWRConfig } from 'swr'
import { getUser } from './user' // eine serverseitige Funktion
export default function RootLayout({ children }) {
return (
<SWRConfig
value={{
fallback: {
// Wir verwenden hier KEIN await für getUser()
// Nur Komponenten, die diese Daten lesen, werden suspendiert
'/api/user': getUser(),
},
}}
>
{children}
</SWRConfig>
)
}
Da dies eine Server-Komponente ist, kann getUser()
sicher Cookies, Header lesen oder mit Ihrer Datenbank kommunizieren. Es ist keine separate API-Route erforderlich. Client-Komponenten unterhalb von <SWRConfig>
können useSWR()
mit demselben Schlüssel aufrufen, um die Benutzerdaten abzurufen. Der Komponentencode mit useSWR
erfordert keine Änderungen gegenüber Ihrer bestehenden clientseitigen Abfragelösung.
'use client'
import useSWR from 'swr'
export function Profile() {
const fetcher = (url) => fetch(url).then((res) => res.json())
// Das gleiche SWR-Muster, das Sie bereits kennen
const { data, error } = useSWR('/api/user', fetcher)
return '...'
}
'use client'
import useSWR from 'swr'
export function Profile() {
const fetcher = (url) => fetch(url).then((res) => res.json())
// Das gleiche SWR-Muster, das Sie bereits kennen
const { data, error } = useSWR('/api/user', fetcher)
return '...'
}
Die fallback
-Daten können vorgerendert und in der initialen HTML-Antwort enthalten sein, dann sofort in den untergeordneten Komponenten mit useSWR
gelesen werden. SWRs Polling, Revalidierung und Caching laufen weiterhin nur clientseitig, wodurch die gesamte Interaktivität erhalten bleibt, die Sie für eine SPA benötigen.
Da die initialen fallback
-Daten automatisch von Next.js behandelt werden, können Sie jetzt jegliche bedingte Logik löschen, die zuvor benötigt wurde, um zu prüfen, ob data
undefined
war. Während die Daten geladen werden, wird die nächstgelegene <Suspense>
-Grenze suspendiert.
SWR | RSC | RSC + SWR | |
---|---|---|---|
SSR-Daten | |||
Streaming während SSR | |||
Anfragen deduplizieren | |||
Client-seitige Funktionen |
SPAs mit React Query
Sie können React Query mit Next.js sowohl auf dem Client als auch auf dem Server verwenden. Dies ermöglicht es Ihnen, sowohl strikte SPAs zu erstellen als auch Server-Funktionen in Next.js in Kombination mit React Query zu nutzen.
Erfahren Sie mehr in der React Query-Dokumentation.
Rendern von Komponenten nur im Browser
Client-Komponenten werden während next build
vorgerendert. Wenn Sie das Vorrendering für eine Client-Komponente deaktivieren und sie nur in der Browser-Umgebung laden möchten, können Sie next/dynamic
verwenden:
import dynamic from 'next/dynamic'
const ClientOnlyComponent = dynamic(() => import('./component'), {
ssr: false,
})
Dies kann nützlich sein für Drittanbieter-Bibliotheken, die auf Browser-APIs wie window
oder document
angewiesen sind. Sie können auch einen useEffect
hinzufügen, der die Existenz dieser APIs überprüft, und wenn sie nicht vorhanden sind, null
oder einen Ladezustand zurückgeben, der vorgerendert wird.
Shallow Routing auf dem Client
Wenn Sie von einer strikten SPA wie Create React App oder Vite migrieren, haben Sie möglicherweise bestehenden Code, der shallow routing verwendet, um den URL-Zustand zu aktualisieren. Dies kann nützlich sein für manuelle Übergänge zwischen Ansichten in Ihrer Anwendung ohne die standardmäßige Next.js-Dateisystem-Routing zu verwenden.
Next.js ermöglicht es Ihnen, die nativen Methoden window.history.pushState
und window.history.replaceState
zu verwenden, um den Browser-Verlauf zu aktualisieren, ohne die Seite neu zu laden.
pushState
- und replaceState
-Aufrufe integrieren sich in den Next.js-Router, sodass Sie mit usePathname
und useSearchParams
synchronisiert werden können.
'use client'
import { useSearchParams } from 'next/navigation'
export default function SortProducts() {
const searchParams = useSearchParams()
function updateSorting(sortOrder: string) {
const urlSearchParams = new URLSearchParams(searchParams.toString())
urlSearchParams.set('sort', sortOrder)
window.history.pushState(null, '', `?${urlSearchParams.toString()}`)
}
return (
<>
<button onClick={() => updateSorting('asc')}>Aufsteigend sortieren</button>
<button onClick={() => updateSorting('desc')}>Absteigend sortieren</button>
</>
)
}
'use client'
import { useSearchParams } from 'next/navigation'
export default function SortProducts() {
const searchParams = useSearchParams()
function updateSorting(sortOrder) {
const urlSearchParams = new URLSearchParams(searchParams.toString())
urlSearchParams.set('sort', sortOrder)
window.history.pushState(null, '', `?${urlSearchParams.toString()}`)
}
return (
<>
<button onClick={() => updateSorting('asc')}>Aufsteigend sortieren</button>
<button onClick={() => updateSorting('desc')}>Absteigend sortieren</button>
</>
)
}
Erfahren Sie mehr darüber, wie Routing und Navigation in Next.js funktionieren.
Verwendung von Server Actions in Client-Komponenten
Sie können Server Actions schrittweise übernehmen, während Sie weiterhin Client-Komponenten verwenden. Dadurch können Sie Boilerplate-Code zum Aufrufen einer API-Route entfernen und stattdessen React-Funktionen wie useActionState
verwenden, um Lade- und Fehlerzustände zu behandeln.
Erstellen Sie beispielsweise Ihre erste Server Action:
'use server'
export async function create() {}
'use server'
export async function create() {}
Sie können eine Server Action vom Client importieren und verwenden, ähnlich wie das Aufrufen einer JavaScript-Funktion. Sie müssen keine API-Endpoint manuell erstellen:
Erfahren Sie mehr über Datenmutation mit Server Actions.
Statischer Export (optional)
Next.js unterstützt auch die Generierung einer vollständig statischen Website. Dies hat einige Vorteile gegenüber strikten SPAs:
- Automatisches Code-Splitting: Anstatt ein einzelnes
index.html
auszuliefern, generiert Next.js eine HTML-Datei pro Route, sodass Ihre Besucher den Inhalt schneller erhalten, ohne auf das clientseitige JavaScript-Bundle warten zu müssen. - Verbessertes Nutzererlebnis: Anstatt eines minimalen Skeletts für alle Routen erhalten Sie vollständig gerenderte Seiten für jede Route. Wenn Benutzer clientseitig navigieren, bleiben die Übergänge instant und SPA-ähnlich.
Um einen statischen Export zu aktivieren, aktualisieren Sie Ihre Konfiguration:
import type { NextConfig } from 'next'
const nextConfig: NextConfig = {
output: 'export',
}
export default nextConfig
Nach Ausführung von next build
erstellt Next.js einen out
-Ordner mit den HTML/CSS/JS-Assets für Ihre Anwendung.
Hinweis: Next.js-Server-Funktionen werden mit statischen Exporten nicht unterstützt. Erfahren Sie mehr.
Migration bestehender Projekte zu Next.js
Sie können schrittweise zu Next.js migrieren, indem Sie unseren Anleitungen folgen:
Wenn Sie bereits eine SPA mit dem Pages Router verwenden, können Sie lernen, wie Sie den App Router schrittweise übernehmen.
Selbsthosting
Erfahren Sie, wie Sie Ihre Next.js-Anwendung auf einem Node.js-Server, als Docker-Image oder statische HTML-Dateien (statischer Export) selbst hosten können.
Statische Exports
Next.js ermöglicht den Start als statische Website oder Single-Page Application (SPA), mit der Option, später auf Funktionen zu erweitern, die einen Server erfordern.