Back to Blog
February 8, 20261,000 views

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

// BAD: imports entire library (70KB)
import _ from 'lodash';
const result = _.debounce(fn, 300);

// GOOD: imports only what you need (2KB)
import debounce from 'lodash/debounce';
const result = debounce(fn, 300);

// BAD: imports all 1000+ icons (200KB)
import { ArrowRight } from 'lucide-react';

// GOOD: tree-shakeable import
import 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).

// Server Component (default) - sends HTML, zero JS
async 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>
          {/* Only the button is client-side */}
          <LikeButton postId={post.id} />
        </article>
      ))}
    </div>
  );
}

// Client Component - only the interactive part
'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.

// BAD: loads full 4MB image
<img src="/hero.jpg" />

// GOOD: Next.js optimizes, lazy loads, serves WebP
import Image from 'next/image';
<Image
  src="/hero.jpg"
  width={1200}
  height={600}
  priority  // for above-the-fold images
  alt="Hero image"
/>

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: cache Next.js static files for 1 year
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';

// Only loads when component is rendered
const Chart = dynamic(() => import('@/components/Chart'), {
  loading: () => <div className="h-64 animate-pulse bg-gray-800" />,
  ssr: false, // chart library needs window
});

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',  // show fallback font until loaded
});

export default function Layout({ children }) {
  return <body className={inter.className}>{children}</body>;
}

Erwartete Ergebnisse

MetrischZuvorNach allen Korrekturen
Lighthouse-Leistung5894
Erstes rendern von Inhalten2,8 s0,8 s
Largest Contentful Paint4,2 s1,4 s
Gesamtes JS-Paket520 KB180 KB
Kumulative Layoutverschiebung0,250,02