Warum Ihre Next.js-App langsam ist: 7 Leistungsoptimierungen, die wirklich funktionieren
Ihre Next.js-App lädt in mehr als 4 Sekunden. Der Lighthouse-Score liegt bei 60. Die Nutzer springen ab. Hier sind 7 konkrete Lösungen mit Vorher-Nachher-Messungen – keine vagen Ratschläge, sondern nur Code.
1. Du sendest 500 KB JavaScript an den Browser
Führen Sie „npx @next/bundle-analyzer“ aus und überprüfen Sie, was in Ihrem Client-Bundle enthalten ist. Häufige Übeltäter: moment.js (300 KB → durch date-fns ersetzen), lodash (70 KB → nur bestimmte Funktionen importieren), Icon-Bibliotheken (alle Icons statt nur bestimmter importieren).
// SCHLECHT: Importiert die gesamte Bibliothek (70 KB)
import _ from 'lodash';
const result = _.debounce(fn, 300);
// GUT: Importiert nur das, was du brauchst (2 KB)
import debounce from 'lodash/debounce';
const result = debounce(fn, 300);
// SCHLECHT: importiert alle ĂĽber 1000 Symbole (200 KB)
import { ArrowRight } from 'lucide-react';
// GUT: Tree-Shake-fähiger Importimport
ArrowRight from 'lucide-react/dist/esm/icons/arrow-right';2. Jede Komponente ist „use client“
Wenn du bei jeder Komponente „use client“ verwendest, sendest du das gesamte JavaScript an den Browser. Serverkomponenten werden auf dem Server gerendert und senden HTML – ohne JavaScript. Verwende „use client“ nur für Komponenten, die Interaktivität erfordern (onClick, useState, useEffect).
// Serverkomponente (Standard) – sendet HTML, kein
JSasync function BlogList() {
const posts = await db.posts.findMany();
return (
<div>
{posts.map(post => (
<article key={post.id}>
<h2>{post.title}</h2>
<p>{post.excerpt}</p>
{/* Nur der Button ist clientseitig */}
<LikeButton postId={post.id} />
</article>
))}
</div>
);
}
// Client-Komponente – nur der interaktive
Teil'use client';
function LikeButton({ postId }: { postId: string }) {
const [liked, setLiked] = useState(false);
return <button onClick={() => setLiked(!liked)}>Like</button>;
}3. Bilder ohne „next/image“
Raw-Tags <img> laden das Bild in voller Größe. next/image passt die Größe automatisch an, konvertiert das Bild in WebP/AVIF und lädt es verzögert. Allein dadurch lässt sich der LCP um 40–60 % reduzieren.
// SCHLECHT: Lädt das vollständige 4-MB-Bild
<img src="/hero.jpg" />
// GUT: Next.js optimiert, lädt Bilder verzögert und liefert sie im
WebP-Format import Image from 'next/image';
<Image
src="/hero.jpg"
width={1200}
height={600}
priority // fĂĽr Bilder oberhalb der
Falz alt="Hero-Bild"
/>4. Datenabruf auf dem Client, wenn SSR möglich wäre
Wenn du useEffect + fetch verwendest, um bei jedem Seitenaufruf Daten zu laden, lässt du den Nutzer auf Folgendes warten: HTML-Download → JS-Parsing → React-Hydration → Fetch-Anfrage → Rendering. Bei Server-Komponenten erfolgt das Abrufen der Daten auf dem Server, und der Nutzer erhält sofort den vollständigen HTML-Code.
5. Fehlende Caching-Header bei statischen Assets
Die statischen Dateien von Next.js im Verzeichnis /_next/static/ sind inhaltsbasiert gehasht und unveränderlich. Ihr Nginx sollte sie intensiv zwischenspeichern:
# Nginx: Statische Next.js-Dateien fĂĽr 1 Jahr
zwischenspeichern location /_next/static {
proxy_pass http://frontend:3000;
add_header Cache-Control "public, max-age=31536000, immutable";
}6. Keine Verwendung von React.lazy fĂĽr ressourcenintensive Komponenten
Komponenten wie Rich-Text-Editoren, Diagramme und Karten sind sehr groß. Laden Sie sie erst bei Bedarf, damit sie das anfängliche Rendern nicht blockieren:
import dynamic from 'next/dynamic';
// Wird nur geladen, wenn die Komponente gerendert wird
const Chart = dynamic(() => import('@/components/Chart'), {
loading: () => <div className="h-64 animate-pulse bg-gray-800" />,
ssr: false, // Die Diagrammbibliothek benötigt das Fenster
});7. Keine Schriftoptimierung
Benutzerdefinierte Schriftarten verursachen Layoutverschiebungen (CLS), wenn sie erst spät geladen werden. Verwenden Sie „next/font“, um Schriftarten selbst zu hosten und vorab zu laden:
import { Inter } from 'next/font/google';
const inter = Inter({
subsets: ['latin'],
display: 'swap', // Fallback-Schriftart anzeigen, bis die Schriftart geladen ist
});
export default function Layout({ children }) {
return <body className={inter.className}>{children}</body>;
}Erwartete Ergebnisse
| Metrisch | Zuvor | Nach allen Korrekturen |
|---|---|---|
| Lighthouse-Leistung | 58 | 94 |
| Erstes rendern von Inhalten | 2,8 s | 0,8 s |
| Largest Contentful Paint | 4,2 s | 1,4 s |
| Gesamtes JS-Paket | 520 KB | 180 KB |
| Kumulative Layoutverschiebung | 0,25 | 0,02 |
