Next.js schneller machen: Bundle schrumpfen, Vitals verbessern

Next.js schneller machen: Bundle schrumpfen, Vitals verbessern

Datum
20.2.2026

Du öffnest die DevTools, klickst auf Network, lädst deine Seite neu und siehst einen kleinen Schockmoment. Da sind nicht zwei oder drei JavaScript Dateien, sondern zwanzig, dreißig oder noch mehr. Und obwohl Next.js doch als Performance Framework gilt, fühlt sich die Seite beim ersten Laden zäh an, der Hero kommt spät und wenn du direkt klicken willst, wirkt alles kurz wie eingefroren. Wenn deine Seite langsamer lädt, fühlt sie sich schlechter an. Wenn sie sich schlechter anfühlt, springen mehr Nutzer ab. Und wenn mehr Nutzer abspringen, verlieren Rankings und Conversions ziemlich schnell an Boden.

Der wichtige Gedanke gleich zu Beginn ist, dass viele JavaScript Dateien nicht automatisch bedeuten, dass dein Setup kaputt ist. Next.js splittet Code in Chunks, damit nicht jeder Nutzer immer alles herunterladen muss. Mehrere Dateien können sogar ein Zeichen von sinnvoller Aufteilung sein. Das Problem beginnt dann, wenn die Summe aus Dateigröße, Parsing, Kompilierung und Ausführung zu hoch wird, oder wenn die falschen Dateien zu früh geladen werden. Dann leidet der Largest Contentful Paint, weil wichtige Inhalte später sichtbar werden. Dann leidet die Interaction to Next Paint, weil der Main Thread zu viel zu tun hat. Und dann leidet am Ende dein SEO, weil Core Web Vitals Next.js Projekte genauso treffen können wie jede andere React App.

In diesem Artikel gehen wir das Thema so an, wie du es in einem echten Projekt lösen würdest. Erst messen wir richtig, dann schauen wir uns die Bundle Analyse an, dann entschärfen wir Third Party Scripts, dann nutzen wir Dynamic Imports und Code Splitting gezielt, dann holen wir mit App Router und React Server Components richtig viel raus und am Ende nehmen wir Bilder und Fonts mit, weil sie oft der versteckte LCP Killer sind. Ganz zum Schluss bekommst du eine praktische Ziel-Linie im Kopf, wie du bei einem typischen Marketing oder SaaS Landing Setup auf unter zehn wirklich relevante Requests für den Start kommst, ohne dass du dabei deine ganze Seite kaputt optimieren musst.

Trainiere dein Dev-Brain

Trainiere dein Dev-Brain

Wenn du gerade an deiner Next.js-Performance schraubst, kennst du das Gefühl, wenn der Kopf nach einer Stunde Debugging langsam matschig wird. Genau dafür ist Cyberskamp Quiz perfekt. Du bekommst kurze, knackige Code-Snippets und musst den Konsolen-Output erraten. Wenn du Lust hast, probier’s aus!

Cyberskamp Quiz kennenlernen

Wenn viele JS-Dateien wirklich ein Problem sind

Ein häufiger Einwand lautet, dass viele Requests heute nicht mehr so schlimm seien, weil HTTP2 und HTTP3 mehrere Dateien parallel übertragen können. Das stimmt in Teilen, aber es löst nicht das Kernproblem. Das Kernproblem ist nicht nur das Herunterladen. Das Kernproblem ist, dass JavaScript im Browser Arbeit erzeugt. Jede Datei muss geholt, entpackt, geparst, kompiliert und ausgeführt werden. Wenn du viel Client JavaScript auslieferst, blockierst du den Main Thread und damit genau die Dinge, die Nutzer als schnell wahrnehmen. Sichtbarkeit und Interaktion.

Für Core Web Vitals ist das besonders relevant. LCP wird häufig vom Rendern eines großen Elements bestimmt, oft ein Hero Bild, ein großer Textblock, oder ein Produktbild. Wenn dein Browser vorher noch sehr viel JavaScript verarbeiten muss, oder wenn Rendering durch Skripte und Layout Berechnungen verzögert wird, kommt dieses Element später. INP ist noch direkter mit JavaScript verbunden. Wenn dein Main Thread mit langen Tasks beschäftigt ist, fühlt sich jeder Klick träge an, weil Eingaben nicht sofort verarbeitet werden. Das ist der Grund, warum reine Byte Reduktion manchmal weniger bringt als das Verschieben von Arbeit weg vom Client.

Viele kleine Dateien können auch ein indirektes Problem verursachen. Wenn du sehr viele Chunks hast, die durch eine Kette von Imports voneinander abhängen, kann dein Browser in einer Art Wasserfall landen. Dann ist nicht die Anzahl an sich der Killer, sondern die Reihenfolge. Erst kommt Runtime, dann ein Shared Chunk, dann ein Page Chunk, dann ein Feature Chunk, dann erst die Komponente. Du siehst das in Network als eine Sequenz, die dich wundert, weil du dachtest, alles lädt parallel. In der Praxis gibt es aber häufig Abhängigkeiten, die parallel laden verhindern.

Es lohnt sich auch, kurz über Hydration zu sprechen, weil genau hier Next.js Projekte oft Zeit verlieren, ohne dass es in der Oberfläche sofort erkennbar ist. Wenn du eine Seite serverseitig renderst, sieht der Nutzer schnell HTML. Das ist gut. Aber sobald das JavaScript kommt, muss React dieses HTML mit interaktiven Event Handlern verbinden. Diese Arbeit kostet Zeit. Je größer dein Client Bundle ist, desto länger dauert Hydration. Wenn du viele Komponenten als Client Components markierst, kann die Seite zwar optisch da sein, aber interaktiv wird sie erst später. Das fühlt sich dann so an, als würde alles kurz hängen, obwohl du eigentlich serverseitig schnell gerendert hast.

Erst messen, dann optimieren

Bevor du in Refactoring versinkst, brauchst du ein klares Bild davon, was gerade wirklich passiert. Du willst nicht nur wissen, dass 20 Dateien geladen werden, sondern warum. Du willst wissen, welche davon kritisch sind, welche nur später kommen, welche du selbst kontrollierst und welche von Drittanbietern kommen. Und du willst unterscheiden, ob du ein Download Problem hast, ein CPU Problem oder ein Layout Problem.

In Chrome DevTools hilft dir dafür ein einfacher Ablauf. Du lädst die Seite mit deaktiviertem Cache, idealerweise im Incognito Modus und schaust dir den Network Wasserfall an. Du suchst nach großen Dateien, aber genauso nach späten Dateien, die erst nach dem ersten Render kommen und trotzdem wichtig sind. Danach wechselst du in den Performance Tab und nimmst ein Profil auf. Wenn du dort lange gelbe Blöcke siehst, ist das JavaScript Ausführung. Wenn du lange violette Blöcke siehst, ist das Rendering. Wenn du lange grüne Blöcke siehst, ist es Paint und Composite. Diese Farblogik ist simpel, aber extrem hilfreich, um nicht blind nur an Bytes herumzuschrauben.

Lighthouse ist nützlich, aber du solltest es als Richtung verstehen, nicht als absolute Wahrheit. Viel wichtiger ist, dass du ein Vorher und Nachher hast. Einmal messen, dann eine Maßnahme, dann wieder messen. Wenn du wirklich zuverlässig sein willst, testest du mindestens einmal mit einer mobilen Drosselung, weil viele Performance Probleme erst dort sichtbar werden. In der Praxis heißt das, dass ein Setup, das auf deinem Dev Rechner okay ist, auf einem Mid Range Android plötzlich komplett in die Knie geht. Genau dort verlieren Core Web Vitals ihre Unschuld.

Wenn du Real User Monitoring nutzt, bekommst du ein noch besseres Bild. Viele Teams wundern sich, warum Lighthouse gut aussieht, aber Nutzer trotzdem meckern. Das liegt daran, dass die echte Welt langsamere Geräte, instabilere Netze und mehr Third Party Overhead hat. Gerade Analytics, Consent und Ads zeigen ihre hässliche Seite eher im Feld als im Labor.

Bundle Analyzer richtig nutzen

Wenn du die Next.js bundle size reduzieren willst, ist der erste harte Schritt, das eigene Bauchgefühl zu killen. In fast jedem Projekt gibt es eine Library, die man irgendwann eingeführt hat, weil sie bequem war, die aber jetzt den Großteil des Client Bundles frisst. Und solange du das nicht sichtbar machst, diskutierst du im Nebel.

Der Next Bundle Analyzer gibt dir genau diese Sichtbarkeit. Du baust damit deine App und bekommst eine Visualisierung der Chunks. Du siehst, welche Abhängigkeiten in welchem Chunk landen und wie groß sie sind. Noch wichtiger ist, dass du siehst, ob ein Paket nur auf einer Seite landet oder in Shared Chunks, die praktisch jeder Nutzer lädt. Wenn eine schwere Library im Shared Chunk sitzt, ist sie für Performance fast immer Gift, weil sie den Einstieg für alle Besucher verschlechtert.

Die typische Installation ist schnell gemacht, aber wichtiger ist die Interpretation. Stell dir die Visualisierung wie eine Landkarte vor. Große Blöcke sind große Pakete. Wenn du dort etwa ein riesiges Icon Set siehst, ist das meist kein Next.js Problem, sondern ein Import Problem. Viele Icon Libraries sind so aufgebaut, dass ein falscher Import plötzlich alles mitzieht. Oder du importierst aus einem Index File, das Side Effects hat und Tree Shaking greift nicht so, wie du denkst.

Ein Klassiker sind auch Date Libraries. In alten Projekten steht noch häufig Moment und das ist berüchtigt für Größe. In neueren Projekten ist es oft ein Charts Paket oder ein Rich Text Editor. Gerade Editoren sind schwer, weil sie viel Code für Cursor, Selection, Plugins und Rendering mitbringen. Wenn du so etwas im Public Marketing Bereich lädst, ist das fast immer ein Fehler. Das gehört in Admin Bereiche und selbst dort oft nur dynamisch.

Wenn du die Karte vor dir hast, ist der nächste Schritt, nicht sofort alles rauszuwerfen, sondern die richtige Frage zu stellen. Braucht die Startseite dieses Paket wirklich beim ersten Paint, oder reicht es, wenn es erst beim Scrollen oder bei einer Interaktion kommt. Kannst du es auf eine Route begrenzen, sodass nur Nutzer dieser Route es bekommen. Kannst du es ersetzen durch eine kleinere Alternative. Oder kannst du es serverseitig lösen, sodass der Client nur HTML bekommt.

Ein unterschätzter Hebel ist auch, wie du Daten und Logik strukturierst. Wenn du Datenformatierung, Filterlogik oder Sorting im Client machst, ziehst du oft Utility Libraries nach, die du serverseitig gar nicht bräuchtest. Wenn du das in eine Server Component verschiebst, sparst du häufig nicht nur eigene Logik, sondern auch die Abhängigkeiten, die dafür eingeführt wurden.

Wenn du den Analyzer aktivieren willst, kannst du das Build einmal mit einer Analyse Flag laufen lassen. Das ist ein echter Befehl, den du ausführen kannst, daher gehört er in einen Codeblock.

ANALYZE=true npm run build

Wenn du im Artikel an Stellen wie dieser etwas siehst, das wie Code wirkt, aber eigentlich nur ein Begriff ist, dann behandle ich das im Fließtext als kursiven Begriff. Genau so solltest du auch in deinem Kopf denken. Ein Begriff wie ANALYZE=true ist eine Schalter Idee, nicht der Kern der Optimierung. Der Kern ist, dass du danach eine Landkarte hast und Entscheidungen treffen kannst.

Imports, die dein Bundle aufblasen

Viele Next.js Projekte laden zu viele JavaScript Dateien, weil sie unbewusst Import Muster verwenden, die die Bundler Logik aushebeln. Ein Beispiel ist der Unterschied zwischen gezieltem Import und Sammel Import. Wenn du aus einem Paket immer aus dem Haupt Entry importierst, kann es passieren, dass die Bundler mehr mitziehen als nötig. Wenn du dagegen gezielt importierst, kann Tree Shaking besser greifen. Das gilt besonders bei Libraries, die nicht sauber als ESM gebaut sind oder Side Effects in ihren Entry Files haben.

Ein zweites Muster ist die Wiederverwendung von Komponenten. Das klingt erst mal gut, aber wenn du eine Komponente, die eine schwere Abhängigkeit braucht, in einen globalen Layout Bereich ziehst, landet diese Abhängigkeit schnell in Shared Chunks. In Next.js App Router ist der Layout Bereich extrem mächtig, aber auch gefährlich. Alles, was du dort einbindest, wirkt wie eine Basislast. Das gilt für UI Libraries, Animationen, Icons und sogar für Client State. Wenn du also sagst, du willst JavaScript Dateien reduzieren, dann ist ein Teil der Antwort nicht ein Tool, sondern Architektur. Was ist global, was ist lokal.

Ein drittes Muster ist, dass man versehentlich Server und Client vermischt. Sobald du in einer Komponente Interaktivität brauchst, markierst du sie als Client Component. Und dann passiert etwas, das viele nicht sehen. Alles, was diese Client Component importiert, muss potenziell in den Client Bundle. Wenn du dort plötzlich eine große Utility Library importierst, oder einen Editor, oder eine Chart Library, dann ist sie drin. Wenn du dagegen eine klare Grenze setzt und nur ein kleines interaktives Stück als Client baust, während der Rest serverseitig bleibt, sinkt die Bundle Größe wie von selbst.

Genau hier spielt das Verständnis von use client eine riesige Rolle. Viele setzen es aus Bequemlichkeit sehr weit oben, etwa im Layout oder in einem Page File, weil dann alles einfach funktioniert. Performance mäßig ist das aber oft der Worst Case. Du willst use client so tief wie möglich setzen, damit nur die Komponenten JavaScript bekommen, die es wirklich brauchen.

Third-Party Scripts entschärfen

Es gibt ein Muster, das ich in fast jedem Performance Audit sehe. Das eigene Bundle ist okay, aber die Seite ist trotzdem langsam. Dann schaust du in Network und siehst Requests zu Analytics, Consent, Heatmaps, Chat Widgets, AB Testing und manchmal zu fünf verschiedenen Pixeln. Diese Scripts kommen oft von fremden Domains, haben zusätzliche Requests im Gepäck und laufen zu ungünstigen Zeitpunkten.

Der Trick ist nicht nur, weniger Third Party zu nutzen. Der Trick ist, sie anders zu laden. Next.js gibt dir dafür die Script Komponente und damit kannst du definieren, wann ein Skript geladen wird. Begriffe wie strategy="afterInteractive" und lazyOnload sind hier Gold wert, weil sie dir eine einfache Sprache geben, um den kritischen Pfad zu schützen. Der kritische Pfad ist der Teil, der nötig ist, damit dein Nutzer schnell etwas Sinnvolles sieht und sofort interagieren kann.

Die größte Falle ist, dass Third Party Scripts oft vor allem für dein Business wichtig wirken, weil sie Tracking liefern. Aber wenn sie LCP und INP verschlechtern, kosten sie dich am Ende Conversions, die du eigentlich messen wolltest. Du trackst dann sehr genau, wie Nutzer abspringen, weil du sie vorher ausgebremst hast. Das ist kein Witz, das passiert täglich.

Ein guter Weg ist, Scripts nach dem ersten Render zu laden. Du kannst ein Analytics Script mit Next Script einbinden und es erst nach der Interaktivität laden lassen. Das ist echter Code, den du einfügen kannst, daher ein Codeblock.

import Script from "next/script"

export function Analytics() {
  return (
    <Script
      src="https://www.googletagmanager.com/gtag/js?id=G-XXXXXXX"
      strategy="afterInteractive"
    />
  )
}

Dieses Muster schützt deinen ersten Eindruck. Der Nutzer bekommt die Seite und erst dann kommt Tracking. Wenn du noch weiter gehen willst, lädst du bestimmte Tools erst nach einer Interaktion. Ein Chat Widget muss nicht beim Seitenstart da sein. Es reicht oft, wenn es nach dem Klick auf einen Chat Button geladen wird. Genau so kannst du auch AB Testing Tools behandeln, wenn sie nicht für den ersten Paint nötig sind.

Es lohnt sich außerdem, Third Party zu auditieren wie Code. Du schaust nicht nur, ob das Tool cool ist, sondern ob es deinen Main Thread blockiert. Viele dieser Tools führen beim Laden direkt Arbeit aus, hängen Event Listener an Scroll und Click, sammeln Informationen und triggern zusätzliche Requests. Das alles erhöht INP Risiken. Und wenn du Pech hast, sorgt ein Script für Layout Shifts, weil es nachträglich Elemente in die Seite injiziert. Das ist ein direkter CLS Killer.

Wenn du Consent Management nutzt, wird es noch spannender. Viele Consent Tools laden selbst große Scripts und blockieren Rendering. Hier lohnt sich eine radikale Frage. Brauchst du die schwerste Lösung, oder kannst du das schlanker lösen, ohne dein Rechtliches zu ignorieren. In vielen Fällen ist ein sauber implementierter Consent Flow möglich, der Tools erst nach Einwilligung lädt und vorher nichts ausführt. Das hilft nicht nur Datenschutz, sondern auch Performance.

Dynamic Imports und Code-Splitting

Next.js macht bereits Route based Code Splitting. Das heißt, jede Route bekommt ihre eigenen Chunks. Trotzdem landen viele Features im Start Bundle, weil sie irgendwo in einer globalen Komponente importiert werden. Das ist der Moment, in dem Dynamic Imports richtig Spaß machen, weil du das Gewicht von Features aus dem initialen Bundle ziehen kannst.

Die Denkweise ist einfach. Was muss beim ersten Besuch wirklich da sein. Alles andere wird später geladen. Das betrifft besonders Dinge wie Charts, Syntax Highlighting, große Animationen, Editoren, Maps, Social Embeds und manchmal sogar ganze UI Bereiche, die nur nach dem Scrollen relevant werden.

In Next.js kannst du dafür Dynamic Imports nutzen. Der Begriff next/dynamic ist hier der Schlüssel. Du importierst eine Komponente nicht direkt, sondern dynamisch und Next.js erstellt dafür einen eigenen Chunk, der erst nachgeladen wird, wenn die Komponente wirklich gerendert werden soll. Ein Minimalbeispiel ohne Optionen sieht so aus.

import dynamic from "next/dynamic"

const Chart = dynamic(() => import("./Chart"))

export default function Page() {
  return (
    <div>
      <h2>Auswertung</h2>
      <Chart />
    </div>
  )
}

Dieses Muster wird richtig stark, wenn du es mit echter Nutzerführung kombinierst. Stell dir vor, du hast auf einer Landing Page eine Sektion mit Testimonials und darunter eine interaktive Demo. Wenn die Demo ein schweres Paket braucht, dann lade sie erst, wenn der Nutzer dahin scrollt oder einen Button klickt. Du kannst die Demo sogar in ein separates Segment packen, sodass der Rest der Seite superschnell wird und nur die Nutzer, die wirklich interessiert sind, den Extra Chunk bekommen.

Das hat zwei Effekte. Erstens sinkt dein initialer Download und deine Parsing Zeit. Das hilft LCP und häufig auch TBT, also die Zeit, in der der Main Thread blockiert ist. Zweitens sinkt das Risiko, dass Interaktionen früh auf der Seite träge sind, weil weniger Arbeit gleichzeitig im Browser passiert. Genau hier greift Lazy Loading in der Praxis nicht nur bei Bildern, sondern bei Logik.

Eine häufige Frage ist, ob viele kleinere Chunks nicht wieder zu vielen Requests führen. Das kann passieren, wenn du es übertreibst. Aber wenn du es sinnvoll machst, verschiebst du Requests weg vom Start in den späteren Verlauf. Das ist für Core Web Vitals oft genau richtig, weil die Vitals sich stark auf den frühen Nutzer Moment konzentrieren. Du willst die ersten Sekunden gewinnen.

Ein zusätzlicher Vorteil ist, dass du damit auch AB Testing und Feature Flags sauberer bauen kannst. Wenn ein Feature nur für einen Teil der Nutzer aktiv ist, kannst du das Feature auch nur für diese Nutzer laden. Das ist eine Art Performance Personalisierung.

App Router und Server Components

Wenn du wirklich den Unterschied machen willst, führt kaum ein Weg daran vorbei, den App Router und React Server Components ernst zu nehmen. Viele Teams verwenden Next.js, aber programmieren es wie eine klassische Client React App, nur mit SSR vorne drauf. Das funktioniert, aber es verschenkt das, was Next.js heute so stark macht.

Die Idee von Server Components ist, dass Komponenten standardmäßig auf dem Server laufen. Sie können Daten laden, sie können HTML generieren und sie schicken nicht automatisch JavaScript an den Client. Das ist eine radikale Veränderung, weil es bedeutet, dass du große Teile deiner UI als reines Ergebnis auslieferst. Der Browser muss dann nicht mehr alles selbst ausrechnen. Er bekommt fertig gerenderten Content, der schnell sichtbar wird und nur kleine Inseln von Interaktivität brauchen Client Code.

Genau hier ist use client die Grenze. Sobald du es setzt, sagst du, dass diese Komponente im Browser laufen muss. Das ist manchmal nötig, weil du State, Effects, Event Handler und Browser APIs brauchst. Aber wenn du use client zu früh setzt, wird dein Client Bundle groß. Wenn du es spät setzt, bleibt dein Client Bundle klein. Ein guter App Router Stil ist, dass du Server Components für Struktur, Daten und Darstellung nutzt und Client Components nur für Interaktion.

Nimm ein praktisches Szenario. Du hast eine Blog Seite mit einem Artikel, einem Inhaltsverzeichnis, einer Newsletter Box und einem Like Button. Der Artikeltext ist statisch oder kommt aus einer CMS API. Das kann alles serverseitig gerendert werden. Das Inhaltsverzeichnis kann serverseitig aus dem Artikel generiert werden. Die Newsletter Box kann serverseitig gerendert werden, nur das Formular braucht Interaktion. Der Like Button braucht Interaktion. Wenn du das sauber trennst, ist der Client Code vielleicht nur ein Formular Handler und ein Like Handler. Das ist winzig. Das Ergebnis ist ein viel kleineres Bundle, weniger Hydration Aufwand und oft bessere INP.

Viele Performance Probleme in Next.js entstehen, weil alles in einer riesigen Client Component landet. Dann wird das ganze Layout hydriert, auch wenn nur ein Button interaktiv ist. Das ist wie mit einem Truck Brötchen holen fahren. Es funktioniert, aber es ist absurd.

Der App Router bringt außerdem Streaming. Das bedeutet, dass der Server HTML in Teilen schicken kann. Der Nutzer sieht früh etwas, während weitere Teile nachkommen. Du kannst Suspense nutzen, um Bereiche mit Daten später einzublenden, ohne den Rest zu blockieren. Das fühlt sich für Nutzer schnell an, weil die Seite sofort Struktur zeigt. Und es hilft LCP, wenn der größte Content Bereich früh kommt.

Ein weiterer Punkt ist Caching. Wenn du Daten serverseitig lädst, kannst du sie auch serverseitig cachen. Du kannst statische Generation nutzen oder ISR, je nachdem wie frisch der Content sein muss. Das reduziert TTFB und macht die Seite stabiler. Und stabilere TTFB führt oft zu besserem LCP, weil der Browser früher loslegen kann.

Wenn du das zusammen nimmst, merkst du, warum Core Web Vitals nicht nur ein Thema von Minify und Gzip ist. Es ist ein Architektur Thema. Wie viel Arbeit passiert im Browser und wie viel Arbeit passiert auf dem Server.

Hydration und INP verstehen

INP ist für viele Teams schwer greifbar, weil man es nicht so einfach sieht wie ein großes Bild. Aber es ist oft der Grund, warum eine Seite sich träge anfühlt. Und JavaScript ist hier der Hauptverdächtige.

Wenn die Seite lädt, passiert im Browser sehr viel. Er lädt das HTML, baut den DOM, lädt CSS, berechnet Layout, malt den ersten Paint. Dann kommen Scripts. Diese Scripts müssen geparst und ausgeführt werden. React muss Hydration machen und Event Handler verbinden. Und während das passiert, kann der Main Thread blockiert sein. Wenn der Nutzer in diesem Moment scrollt oder klickt, dauert es länger, bis eine Reaktion sichtbar wird. Das ist INP.

Wenn du Bundles reduzierst, reduzierst du nicht nur Download. Du reduzierst CPU Arbeit. Und CPU Arbeit ist bei modernen Apps oft der echte Bottleneck. Auf schnellen Geräten merkst du es weniger, auf schwächeren Geräten brutal.

Du kannst Hydration reduzieren, indem du weniger Client Components hast. Du kannst auch Interaktivität später aktivieren. Ein Muster ist, dass du zunächst eine statische Version einer Komponente renderst und erst nach einer Interaktion oder nach dem Scrollen die interaktive Version lädst. Das ist wie progressive Enhancement, nur modern.

Dynamic Imports helfen hier wieder. Wenn ein Widget nicht sofort gebraucht wird, muss es nicht sofort hydriert werden. Und Server Components helfen noch mehr, weil sie gar keine Hydration brauchen. Dadurch wird INP oft deutlich besser, ohne dass du jeden Event Handler micro optimierst.

Bilder für LCP optimieren

Viele denken bei JavaScript Dateien reduzieren sofort an Bundles. Aber LCP wird sehr häufig von einem Bild bestimmt. Das klassische Beispiel ist das Hero Image auf einer Landing Page. Wenn dieses Bild groß ist, unkomprimiert, oder in einem ungünstigen Format, kommt es spät. Wenn es spät kommt, ist LCP schlecht, egal wie klein dein JavaScript ist.

Next.js gibt dir mit der Image Komponente ein starkes Werkzeug. Sie hilft dir, die richtige Größe zu liefern, moderne Formate zu nutzen und Lazy Loading zu machen. Wichtig ist, dass du dem Browser klare Dimensionen gibst, damit er Platz reservieren kann. Sonst entstehen Layout Shifts und CLS wird schlechter.

Ein Beispiel für ein Hero Bild, das du wirklich so einbauen kannst, sieht so aus.

import Image from "next/image"

export function Hero() {
  return (
    <Image
      src="/hero.jpg"
      alt="Hero Bild"
      width={1400}
      height={800}
      priority
    />
  )
}

Das priority Attribut ist in vielen Fällen sinnvoll für das wichtigste Bild oberhalb der Falz. Du sagst dem Browser damit, dass dieses Bild früh geladen werden soll. Das kann deinen LCP spürbar verbessern. Du solltest aber nicht alles auf priority setzen, sonst hebelst du den Vorteil wieder aus.

Ein weiterer Hebel ist, dass du Bilder nicht einfach riesig einbindest und dann per CSS runter skalierst. Wenn du eine 4000 Pixel breite Datei lädst und sie auf 1200 Pixel darstellst, verbrennst du Bandbreite und Zeit. Next.js kann responsive Varianten liefern, aber du musst auch sinnvoll mit Layout arbeiten.

Auch Hintergrundbilder sind tückisch. Viele Hero Sektionen nutzen CSS Background Images. Diese werden nicht immer so priorisiert wie ein echtes Bild Element. Wenn dein LCP Element ein Background Image ist, kann das LCP verschlechtern. In solchen Fällen ist es oft besser, ein echtes Image Element zu verwenden und es via CSS passend zu positionieren.

Wenn du sehr visuelle Seiten hast, lohnt sich auch ein Blick auf das Rendering. Große Blur Effekte, Filter und Overlays können Paint teuer machen. Das ist nicht JavaScript, aber es blockiert die Hauptwahrnehmung. Performance ist immer ein Zusammenspiel.

Trainiere dein Dev-Brain

Trainiere dein Dev-Brain

Wenn du gerade an deiner Next.js-Performance schraubst, kennst du das Gefühl, wenn der Kopf nach einer Stunde Debugging langsam matschig wird. Genau dafür ist Cyberskamp Quiz perfekt. Du bekommst kurze, knackige Code-Snippets und musst den Konsolen-Output erraten. Wenn du Lust hast, probier’s aus!

Cyberskamp Quiz kennenlernen

Fonts ohne CLS und Verzögerung

Fonts wirken harmlos, aber sie können gleich zwei Vitals beeinflussen. Erstens können sie Text Rendering verzögern, wenn der Browser auf die Font Datei wartet. Zweitens können sie Layout Shifts erzeugen, wenn zuerst ein Fallback Font gerendert wird und später ein anderer Font kommt, der andere Metriken hat.

Die gute Nachricht ist, dass Next.js hier sehr solide Möglichkeiten hat. Das Font Modul erlaubt dir, Fonts effizient einzubinden und Self Hosting zu nutzen. Der Begriff next/font ist hier der Startpunkt. Self Hosting reduziert externe DNS und TLS Kosten und du hast mehr Kontrolle über Caching. Dazu kommt, dass moderne Font Setups oft automatisch auf swap setzen, sodass Text sofort sichtbar ist.

Wichtig ist auch, dass du nur das lädst, was du wirklich brauchst. Viele laden mehrere Schriftschnitte, Italic, Bold, Extra Bold und dann noch zwei Familien, obwohl am Ende vielleicht nur Regular und SemiBold genutzt werden. Jede zusätzliche Datei ist ein Request und jede zusätzliche Datei ist Render Risiko.

Wenn du variable Fonts nutzen kannst, bekommst du oft mehrere Gewichte in einer Datei. Das kann Requests reduzieren. Gleichzeitig musst du aufpassen, dass variable Fonts nicht riesig sind. Auch hier hilft messen.

Ein praktischer Ansatz ist, deine Fonts als Performance Budget zu behandeln. Du entscheidest bewusst, wie viele Dateien du maximal dafür zulässt. Und du testest, ob die Ästhetik den Preis wert ist. Wenn deine Seite auf Conversions optimiert ist, ist Geschwindigkeit häufig wichtiger als ein perfekter Custom Font.

Zielbild Unter 10 Requests

Jetzt kommen wir zu dem Teil, den viele sich als Checkliste wünschen, aber ohne Stichpunkte. Stell dir einfach das Zielbild vor. Ein Nutzer lädt deine Landing Page und der Browser muss nur wenige Dinge holen, um den ersten sinnvollen Eindruck zu liefern. Ein HTML Dokument, ein CSS Bundle oder eine kleine Menge CSS, ein oder zwei JavaScript Chunks, ein Hero Bild und vielleicht eine Font Datei. Das ist das Ideal.

Der erste Schritt in Richtung dieses Zielbilds ist, dass du Third Party konsequent aus dem kritischen Pfad entfernst. Das heißt nicht, dass du auf Analytics verzichten musst. Es heißt, dass du es später lädst. Sobald du das machst, verschwinden oft schon mehrere Requests aus den ersten Sekunden und das ist genau das, was Core Web Vitals belohnt.

Der zweite Schritt ist, dass du globale Abhängigkeiten reduzierst. Wenn du eine UI Library nutzt, ist das nicht per se schlecht. Aber wenn sie dein Shared Chunk sprengt, wird es ein Problem. Oft kannst du Komponenten selektiv nutzen oder du kannst ein leichteres Setup bauen. Gerade bei Icons lohnt es sich, nicht die ganze Welt zu importieren. Wenige SVG Icons direkt oder eine gut tree shakable Library machen einen riesigen Unterschied.

Der dritte Schritt ist, dass du große Features isolierst. Charts, Editor, Map, Syntax Highlighting, Video Player. Das sind typische Feature Blöcke, die man dynamisch laden sollte. Wenn du diese aus dem Start entfernst, sinkt nicht nur die Bundle Größe. Du reduzierst auch die CPU Last in den ersten Sekunden und das verbessert INP.

Der vierte Schritt ist, dass du App Router und Server Components wirklich nutzt. Wenn dein Marketing Bereich zu großen Teilen serverseitig ist und nur kleine Inseln interaktiv sind, brauchst du im Start weniger JavaScript. Das ist die sauberste Art, Next.js auszuspielen. Du wirst dabei merken, dass du plötzlich weniger über JavaScript Dateien reduzieren nachdenken musst, weil es automatisch passiert.

Der fünfte Schritt ist, dass du Bilder und Fonts als first class Performance Assets behandelst. Das Hero Bild richtig dimensionieren und priorisieren, Fonts schlank halten und Layout Shifts vermeiden. Wenn du das ignorierst, kannst du Bundles optimieren wie du willst und LCP bleibt trotzdem schlecht.

Der sechste Schritt ist, dass du Caching nicht vergisst. Viele Requests sind beim ersten Besuch unvermeidbar, aber beim zweiten Besuch willst du praktisch nichts mehr laden. Next.js hashed Assets und das ist gut. Sorge dafür, dass dein Hosting und deine Header das auch ausnutzen. Wenn ein Nutzer wiederkommt und trotzdem alles neu lädt, verlierst du einen Teil des Performance Vorteils, der eigentlich gratis wäre.

Wenn du diese sechs Schritte als Story in deinem Kopf hast, kommst du fast automatisch in die Nähe von unter zehn wirklich relevanten Start Requests. Und wenn du mal drüber liegst, ist das nicht schlimm. Wichtig ist, dass die kritischen Requests früh sind und der Main Thread frei bleibt.

Praxis Ablauf im Projekt

In der Praxis willst du nicht ewig optimieren. Du willst schnelle Wins und du willst keine Regressionen. Ein guter Ablauf ist, dass du zuerst den Status quo misst und Screenshots von Wasserfall und Performance Trace machst. Dann machst du Bundle Analyse und identifizierst die zwei oder drei größten Brocken. Danach entscheidest du für jeden Brocken, ob du ihn entfernst, ersetzt oder isolierst.

Parallel machst du ein Third Party Audit. Du listest mental alle Scripts auf und fragst dich, welches wirklich im Start sein muss. In den meisten Projekten ist die Antwort, dass fast keines im Start sein muss. Du stellst die Lade Strategie um und misst erneut.

Danach gehst du an die Architektur Grenze. Du suchst nach großen Client Boundaries, die durch use client entstehen und du ziehst sie auseinander. Du machst den Server Teil groß und den Client Teil klein. Das ist oft der größte Hebel für die Next.js bundle size und für Core Web Vitals, weil es nicht nur Symptome kurieren, sondern die Ursache reduziert.

Zum Schluss nimmst du dir die sichtbaren LCP Elemente vor. Ist es ein Bild, ein Font, ein großer Textblock, ein Slider. Du optimierst das Element so, dass es früh kommt und stabil bleibt. Du prüfst CLS und sorgst dafür, dass Dimensionen reserviert sind. Wenn du hier sauber bist, fühlt sich die Seite plötzlich deutlich schneller an, selbst wenn sie nicht komplett minimalistisch ist.

Und dann kommt der wichtigste Schritt. Du baust dir eine kleine Routine ein, damit das nicht wieder kaputt geht. Du kannst ein Performance Budget definieren, du kannst in Pull Requests kurz checken, ob neue Libraries dazu kommen und du kannst regelmäßig einen Lighthouse Lauf machen. Performance ist kein einmaliges Projekt, Performance ist Hygiene.

Fazit und nächste Schritte

Wenn du das nächste Mal siehst, dass Next.js 20 oder mehr JavaScript Dateien lädt, denk nicht nur in Dateien. Denk in Arbeit. Welche Arbeit zwingst du dem Browser in den ersten Sekunden auf. Welche Arbeit kannst du verschieben. Welche Arbeit kannst du ganz vom Client wegnehmen. Genau so reduzierst du Bundles, genau so verbesserst du Core Web Vitals und genau so erreichst du am Ende bessere Rankings und bessere Conversions.

Der schnellste Weg zu mehr Speed ist fast immer eine Kombination aus Bundle Analyse, smarter Third Party Einbindung, konsequentem Lazy Loading Next.js über Dynamic Imports, sauberer Nutzung von App Router und Server Components und einem ehrlichen Blick auf Bilder und Fonts. Wenn du diese Hebel einmal verstanden hast, wird NextJs JavaScript Dateien reduzieren von einem frustrierenden Symptom zu einer lösbaren Aufgabe, die du in klaren Schritten angehen kannst.

Wenn du solche praxisnahen Performance Guides magst und regelmäßig konkrete Next.js Tipps willst, dann trag dich in meinen Newsletter ein. Du bekommst Updates, Checklisten und echte Projekt Patterns, die dir helfen, schneller zu bauen und schneller zu ranken, ohne dass du jedes Mal bei null anfängst.

Kommentare

Bitte melde dich an, um einen Kommentar zu schreiben.