performanceFeatured

Next.js Performance-Optimierung: 15 Best Practices für 2025

Steigern Sie Ihren Lighthouse-Score auf 90+: Code-Splitting, Image Optimization, Caching und moderne Performance-Patterns für Next.js Anwendungen.

Onur Cirakoglu
12 min read
#Next.js#Performance#Optimization#Web Vitals#SEO
Performance Metriken und Optimierung Dashboard

Ein Lighthouse-Score von 90+ ist kein Zufall - es ist das Ergebnis konsequenter Optimierung. In diesem Guide zeigen wir Ihnen 15 bewährte Techniken, mit denen wir bei HEADON.pro durchschnittlich 60% Performance-Steigerung erreichen.

Warum Performance kritisch ist

Jede Sekunde Ladezeit kostet Sie Nutzer und Umsatz:

  • 53% der Mobile-Nutzer verlassen Seiten, die >3 Sekunden laden
  • 100ms schnellere Ladezeit = 1% mehr Conversions (Amazon-Studie)
  • Google-Ranking berücksichtigt Core Web Vitals seit 2021

Unsere Messungen aus Portfolio-Projekten zeigen: Optimierte Next.js-Apps erreichen LCP < 1.0s und FCP < 0.5s.

1. Dynamic Imports für Code-Splitting

Laden Sie nur Code, der wirklich benötigt wird:

// ❌ Schlecht: Alles im Initial Bundle
import { HeavyChart } from '@/components/charts'
import { ComplexModal } from '@/components/modal'
import { RarelyUsedWidget } from '@/components/widgets'
 
export default function Dashboard() {
  return (
    <div>
      <HeavyChart data={data} />
      <ComplexModal />
      <RarelyUsedWidget />
    </div>
  )
}
// ✅ Gut: Lazy Loading mit dynamic()
import dynamic from 'next/dynamic'
 
const HeavyChart = dynamic(() => import('@/components/charts/HeavyChart'), {
  loading: () => <ChartSkeleton />,
  ssr: false, // Nicht Server-Side rendern wenn nicht nötig
})
 
const ComplexModal = dynamic(() => import('@/components/modal/ComplexModal'))
 
export default function Dashboard() {
  return (
    <div>
      <HeavyChart data={data} />
      <ComplexModal />
    </div>
  )
}

Einsparung: Bis zu 40% kleinerer Initial Bundle.

Named Exports mit dynamic()

// ✅ Für Named Exports
const SpecificComponent = dynamic(
  () => import('@/components/library').then(mod => mod.SpecificComponent)
)

2. Image Optimization perfektionieren

Die next/image Komponente ist mächtig - wenn richtig konfiguriert:

import Image from 'next/image'
 
export default function Hero() {
  return (
    <Image
      src="/hero.jpg"
      alt="Hero Image"
      width={1920}
      height={1080}
      // ✅ Priority für Above-the-Fold Images
      priority
      // ✅ Sizes für Responsive Images
      sizes="(max-width: 768px) 100vw, (max-width: 1200px) 50vw, 33vw"
      // ✅ Optimale Quality (85 ist sweet spot)
      quality={85}
      // ✅ Placeholder mit Blur Effect
      placeholder="blur"
      blurDataURL="data:image/jpeg;base64,/9j/4AAQSkZJRg..."
    />
  )
}

Bildformat-Strategie

// next.config.js
module.exports = {
  images: {
    formats: ['image/avif', 'image/webp'],
    deviceSizes: [640, 750, 828, 1080, 1200, 1920, 2048, 3840],
    imageSizes: [16, 32, 48, 64, 96, 128, 256, 384],
    minimumCacheTTL: 60,
  },
}

Ergebnis:

  • AVIF: 30% kleiner als WebP
  • WebP: 25-35% kleiner als JPEG
  • Automatisches Format-Switching basierend auf Browser-Support

Mehr Details in unserem Image Optimization Guide.

3. Font-Loading optimieren

Google Fonts effizient laden mit next/font:

// app/layout.tsx
import { Inter, Playfair_Display } from 'next/font/google'
 
// ✅ Subset definieren spart Bytes
const inter = Inter({
  subsets: ['latin'],
  variable: '--font-inter',
  display: 'swap',
  preload: true,
})
 
const playfair = Playfair_Display({
  subsets: ['latin'],
  variable: '--font-playfair',
  weight: ['400', '700'], // Nur benötigte Weights
  display: 'swap',
})
 
export default function RootLayout({ children }) {
  return (
    <html lang="de" className={`${inter.variable} ${playfair.variable}`}>
      <body>{children}</body>
    </html>
  )
}

Vorteile:

  • ⚡ Fonts werden automatisch optimiert
  • 🚀 Self-hosted (kein externes Google-Request)
  • 🎯 Zero Layout Shift durch font-display: swap
  • 📦 CSS-Variablen für flexible Nutzung

4. Aggressive Caching-Strategie

Fetch-Caching konfigurieren

// ✅ Static Data - Cache unbegrenzt
const staticData = await fetch('https://api.example.com/config', {
  cache: 'force-cache', // Default in App Router
})
 
// ✅ Revalidate nach Zeitintervall
const posts = await fetch('https://api.example.com/posts', {
  next: { revalidate: 3600 }, // 1 Stunde
})
 
// ✅ Nie cachen für User-spezifische Daten
const user = await fetch('https://api.example.com/user', {
  cache: 'no-store',
})

Route-Segment Config

// app/blog/page.tsx
export const revalidate = 3600 // Revalidate every hour
export const dynamic = 'force-static' // Force static generation
export const fetchCache = 'default-cache'
 
export default async function BlogPage() {
  const posts = await getPosts()
  return <PostList posts={posts} />
}

Client-Side Caching

'use client'
 
import { cache } from 'react'
 
// ✅ React Cache für Request Deduplication
const getUser = cache(async (id: string) => {
  const res = await fetch(`/api/users/${id}`)
  return res.json()
})
 
export function UserProfile({ userId }) {
  // Wird nur 1x geladen, auch bei multiple calls
  const user = use(getUser(userId))
  return <div>{user.name}</div>
}

Tiefgehender in unserem Caching-Guide.

5. Bundle Analyzer nutzen

Visualisieren Sie Ihr Bundle:

# Installation
pnpm add -D @next/bundle-analyzer
 
# Konfiguration in next.config.js
const withBundleAnalyzer = require('@next/bundle-analyzer')({
  enabled: process.env.ANALYZE === 'true',
})
 
module.exports = withBundleAnalyzer({
  // Ihre Next.js Config
})
 
# Ausführen
ANALYZE=true pnpm build

Achten Sie auf:

  • Duplicate Dependencies (z.B. moment.js mehrfach)
  • Große Packages die lazy geladen werden könnten
  • Ungenutzte Exports (Tree-Shaking funktioniert nicht)

6. Server Components als Default

// ✅ Server Component (Default in App Router)
async function BlogPost({ slug }: { slug: string }) {
  const post = await db.post.findUnique({ where: { slug } })
 
  return (
    <article>
      <h1>{post.title}</h1>
      <div dangerouslySetInnerHTML={{ __html: post.content }} />
 
      {/* Nur interaktive Teile als Client Component */}
      <CommentSection postId={post.id} />
    </article>
  )
}
 
// components/CommentSection.tsx
'use client'
 
export function CommentSection({ postId }: { postId: string }) {
  const [comments, setComments] = useState([])
  // Interaktive Logik hier
}

Bundle-Reduktion: Server Components sind 0 KB im Client Bundle.

7. Parallele Datenladung

// ❌ Schlecht: Sequenziell (langsam!)
async function Dashboard() {
  const user = await fetchUser() // 200ms
  const posts = await fetchPosts() // 300ms
  const analytics = await fetchAnalytics() // 500ms
  // Total: 1000ms
}
 
// ✅ Gut: Parallel
async function Dashboard() {
  const [user, posts, analytics] = await Promise.all([
    fetchUser(),    // \
    fetchPosts(),   //  } Parallel - 500ms total
    fetchAnalytics(), // /
  ])
}
 
// ✅ Noch besser: Mit Suspense
export default function Dashboard() {
  return (
    <div>
      <Suspense fallback={<UserSkeleton />}>
        <UserProfile />
      </Suspense>
 
      <Suspense fallback={<PostsSkeleton />}>
        <PostsList />
      </Suspense>
 
      <Suspense fallback={<AnalyticsSkeleton />}>
        <Analytics />
      </Suspense>
    </div>
  )
}

8. Streaming mit Suspense

Progressive Rendering für bessere Perceived Performance:

import { Suspense } from 'react'
 
export default function Page() {
  return (
    <div>
      {/* Kritischer Content lädt sofort */}
      <Header />
 
      {/* Nicht-kritischer Content streamt nach */}
      <Suspense fallback={<Skeleton />}>
        <SlowComponent />
      </Suspense>
 
      <Suspense fallback={<Skeleton />}>
        <AnotherSlowComponent />
      </Suspense>
 
      <Footer />
    </div>
  )
}

Ergebnis:

  • FCP verbessert sich um 40-60%
  • 🎯 User sieht Content sofort
  • 🚀 Keine leeren Seiten mehr

9. Metadata für besseres SEO

// app/blog/[slug]/page.tsx
import type { Metadata } from 'next'
 
export async function generateMetadata({
  params,
}: {
  params: { slug: string }
}): Promise<Metadata> {
  const post = await getPost(params.slug)
 
  return {
    title: post.title,
    description: post.excerpt,
    openGraph: {
      title: post.title,
      description: post.excerpt,
      images: [{ url: post.image, width: 1200, height: 630 }],
      type: 'article',
      publishedTime: post.publishedAt,
      authors: [post.author.name],
    },
    twitter: {
      card: 'summary_large_image',
      title: post.title,
      description: post.excerpt,
      images: [post.image],
    },
    alternates: {
      canonical: `https://headon.pro/blog/${post.slug}`,
    },
  }
}

10. Route Handlers für API Optimization

// app/api/posts/route.ts
import { NextResponse } from 'next/server'
 
export async function GET(request: Request) {
  const { searchParams } = new URL(request.url)
  const limit = searchParams.get('limit') || '10'
 
  const posts = await db.post.findMany({
    take: parseInt(limit),
    select: {
      id: true,
      title: true,
      slug: true,
      // ✅ Selektiere nur benötigte Felder
      // NICHT: content (kann sehr groß sein)
    },
  })
 
  return NextResponse.json(posts, {
    headers: {
      'Cache-Control': 'public, s-maxage=3600, stale-while-revalidate=86400',
    },
  })
}

11. Third-Party Scripts optimieren

import Script from 'next/script'
 
export default function Page() {
  return (
    <>
      {/* ✅ Analytics mit afterInteractive */}
      <Script
        src="https://www.googletagmanager.com/gtag/js?id=GA_ID"
        strategy="afterInteractive"
      />
 
      {/* ✅ Chat Widget mit lazyOnload */}
      <Script
        src="https://widget.example.com/chat.js"
        strategy="lazyOnload"
      />
 
      {/* ✅ Critical Scripts mit beforeInteractive */}
      <Script
        src="/critical-polyfill.js"
        strategy="beforeInteractive"
      />
    </>
  )
}

Strategy-Guide:

  • beforeInteractive: Nur für kritische Polyfills
  • afterInteractive: Standard für Analytics
  • lazyOnload: Chat-Widgets, Social Media

12. Database Query Optimization

// ❌ N+1 Query Problem
async function getBlogPosts() {
  const posts = await db.post.findMany()
 
  for (const post of posts) {
    post.author = await db.user.findUnique({ where: { id: post.authorId } })
    post.comments = await db.comment.findMany({ where: { postId: post.id } })
  }
  // 1 + 2N Queries!
}
 
// ✅ Single Query mit Includes
async function getBlogPosts() {
  const posts = await db.post.findMany({
    include: {
      author: true,
      comments: {
        take: 5, // Nur neueste 5
        orderBy: { createdAt: 'desc' },
      },
    },
  })
  // Nur 1 Query!
}

13. Edge Runtime für globale Performance

// app/api/edge-example/route.ts
export const runtime = 'edge'
 
export async function GET(request: Request) {
  // Läuft auf Edge Runtime (CloudFlare Workers, etc.)
  // Näher am User = schnellere Response
  return new Response('Hello from the Edge!')
}

Vorteile:

  • 30-50% schnellere Response global
  • 🌍 Ausführung am nächsten Edge-Location
  • 📦 Kleinere Bundles (Edge-optimiert)

Limitations:

  • Kein Node.js APIs (fs, child_process)
  • Limitierte NPM-Packages
  • Max 1MB Code-Size

14. Monitoring & Messung

Web Vitals tracken

// app/layout.tsx
import Script from 'next/script'
 
export default function RootLayout({ children }) {
  return (
    <html>
      <body>
        {children}
        <Script
          src="https://analytics.yourdomain.com/script.js"
          data-website-id="your-umami-website-id"
          strategy="afterInteractive"
        />
      </body>
    </html>
  )
}

Custom Metrics

'use client'
 
import { useReportWebVitals } from 'next/web-vitals'
 
export function WebVitals() {
  useReportWebVitals((metric) => {
    console.log(metric)
 
    // An Analytics senden
    if (metric.label === 'web-vital') {
      window.gtag('event', metric.name, {
        value: Math.round(metric.value),
        event_label: metric.id,
        non_interaction: true,
      })
    }
  })
 
  return null
}

Mehr zu Core Web Vitals in unserem Guide.

15. Production-Build optimieren

// next.config.js
module.exports = {
  // ✅ Compression aktivieren
  compress: true,
 
  // ✅ React Production Mode
  reactStrictMode: true,
 
  // ✅ Source Maps für Production (optional)
  productionBrowserSourceMaps: false,
 
  // ✅ Compiler-Optimierungen
  compiler: {
    removeConsole: {
      exclude: ['error', 'warn'], // console.log entfernen
    },
  },
 
  // ✅ Experimental Features
  experimental: {
    optimizeCss: true, // CSS Optimization
    optimizePackageImports: ['lucide-react', 'date-fns'], // Package Imports
  },
 
  // ✅ Output-Optimierung
  output: 'standalone', // Für Docker
}

Performance Checklist

Vor jedem Production-Deployment:

  • [ ] Lighthouse Score 90+ (Desktop & Mobile)
  • [ ] LCP < 2.5s (idealerweise < 1.0s)
  • [ ] FID < 100ms
  • [ ] CLS < 0.1
  • [ ] All images mit next/image
  • [ ] Fonts mit next/font optimiert
  • [ ] Bundle Analyzer gecheckt
  • [ ] Keine console.logs in Production
  • [ ] Cache-Headers konfiguriert
  • [ ] Sitemaps generiert
  • [ ] Monitoring aktiviert

Real-World Ergebnisse

Bei unserem E-Commerce-Projekt:

Vor Optimierung:

  • LCP: 3.2s
  • FCP: 1.8s
  • Bundle: 456 KB
  • Lighthouse: 67

Nach Optimierung:

  • LCP: 0.9s ✅ (-72%)
  • FCP: 0.4s ✅ (-78%)
  • Bundle: 198 KB ✅ (-57%)
  • Lighthouse: 98 ✅ (+46%)

Business Impact:

  • +23% Conversion Rate
  • -35% Bounce Rate
  • +15% Average Session Duration

Häufige Performance-Killer

1. Unoptimierte Images

// ❌ Direktes <img> Tag
<img src="/large-image.jpg" />
 
// ✅ next/image
<Image src="/large-image.jpg" width={800} height={600} />

2. Blocking Scripts

// ❌ Synchrones Script
<script src="https://cdn.example.com/widget.js"></script>
 
// ✅ Next.js Script mit Strategy
<Script src="https://cdn.example.com/widget.js" strategy="lazyOnload" />

3. Große Dependencies

// ❌ Gesamte Library importieren
import _ from 'lodash' // 70 KB!
 
// ✅ Nur benötigte Funktionen
import debounce from 'lodash/debounce' // 2 KB

Tools für Performance-Testing

  • Lighthouse - Chrome DevTools
  • WebPageTest - webpagetest.org
  • PageSpeed Insights - pagespeed.web.dev
  • Umami Analytics - Real User Monitoring (selbstgehostet)
  • Bundle Analyzer - Webpack Bundle Visualisierung

Zusammenfassung

Next.js Performance-Optimierung ist systematisch:

  1. Images mit next/image optimieren
  2. Fonts mit next/font selbst hosten
  3. Code-Splitting mit dynamic imports
  4. Caching aggressiv nutzen
  5. Server Components als Default
  6. Parallele Datenladung implementieren
  7. Monitoring einrichten

Ergebnis: Lighthouse 90+, LCP < 1.0s, glücklichere User.

Performance-Audit gefällig?

Als Performance-Spezialisten analysieren wir Ihre Next.js-App:

  • 🔍 Detaillierter Performance-Report
  • 🎯 Konkrete Optimierungsvorschläge
  • 🚀 Implementierungs-Roadmap
  • 📊 Vorher/Nachher Metriken

Kostenlosen Audit anfragen

Weitere Ressourcen

Aktualisiert: September 2024

#Next.js#Performance#Optimization#Web Vitals#SEO