divmagic Make design
SimpleNowLiveFunMatterSimple
Verbesserung der Website-Leistung durch Auslieferung von mehr CSS
Blogs›Leistung›Verbesserung der Website-Leistung durch Auslieferung von mehr CSS
Leistung

Verbesserung der Website-Leistung durch Auslieferung von mehr CSS

DivMagic
DivMagic TeamNovember 2, 2026
12 min read

Verbesserung der Site-Performance durch Auslieferung von mehr CSS

In der modernen Frontend-Arbeit ist der Instinkt verankert, dass die schnellste Seite am wenigsten von allem ausliefert: weniger JavaScript, weniger Schriftarten, weniger Anfragen. Als das GitHub-Engineering-Team also darüber schrieb, die Site-Performance durch die Auslieferung von mehr CSS zu verbessern, las sich das wie ein Widerspruch. Die Lösung für ein langsames, JavaScript-lastiges Interface bestand nicht darin, das Styling aus der App zu entfernen. Es bestand darin, mehr davon wieder einzubauen, als einfache, statische, cache-fähige Stylesheets, und das JavaScript zu löschen, das nur dazu diente, Styles im Browser zu generieren.

Der Unterschied ist der gesamte Artikel. Mehr CSS auszuliefern bedeutet nicht ein größeres Stylesheet, das Ihren kritischen Pfad aufbläht. Es bedeutet, die Style-Generierung aus der Laufzeit in den Build zu verlagern und dann den Browser das tun zu lassen, was er wirklich hervorragend kann: ein Stylesheet einmal parsen, es cachen und bei jeder Navigation wiederverwenden.

Die versteckten Kosten von Laufzeit-Styling

CSS-in-JS-Bibliotheken haben echte Probleme gelöst. Sie gaben Komponentenautoren Scoping, Colocation und die Möglichkeit, Props in Styles zu interpolieren, ohne eine Namenskonvention erfinden zu müssen. Der Kompromiss war architektonisch: Sie verlagerten ein Compile-Zeit-Problem in den Browser.

Style-Generierung ist JavaScript-Arbeit

Jede serverseitig gerenderte Seite, die eine Laufzeit-CSS-in-JS-Bibliothek verwendet, verlangt vom Browser, dieselbe Arbeit zweimal zu erledigen. Der Server serialisiert Styles in <style>-Tags, dann wacht die Client-Bibliothek auf, durchläuft den Komponentenbaum, hasht Klassennamen erneut und gleicht das Ergebnis ab. Diese Arbeit findet im Hauptthread statt und konkurriert mit genau den Layout- und Paint-Schritten, die Sie beschleunigen möchten.

// Runtime CSS-in-JS: this stylesheet does not exist until hydration runs
const Button = styled.button`
  background: ${(p) => (p.primary ? "#1f883d" : "transparent")};
  border: 1px solid #d0d7de;
  padding: 6px 16px;
  border-radius: 6px;
  font: inherit;
`;

Dieselbe Komponente, ausgedrückt als Build-Zeit-Stylesheet, entfernt sowohl die Laufzeitabhängigkeit als auch die Serialisierungsarbeit:

/* button.module.css, parsed once, cached forever */
.button {
  padding: 6px 16px;
  border: 1px solid #d0d7de;
  border-radius: 6px;
  font: inherit;
  background: transparent;
}
.buttonPrimary {
  background: #1f883d;
  color: #ffffff;
}
import styles from "./button.module.css";

export function Button({ primary, ...props }) {
  const className = primary
    ? `${styles.button} ${styles.buttonPrimary}`
    : styles.button;

  return <button className={className} {...props} />;
}

Nichts davon ist neu. Was leicht zu unterschätzen ist, ist wie viel von einem Bundle ein ausgereifter CSS-in-JS-Stack leise besitzt: die Bibliothek selbst, die Prop-Interpolations-Hilfsprogramme, den Theme-Provider, den Serialisierer und den doppelten Framework-Code, der wächst, wenn jedes Paket von seiner eigenen Kopie abhängt.

Es liegt direkt auf dem kritischen Rendering-Pfad

Ein <link rel="stylesheet"> blockiert das Rendering, aber es blockiert es einmal, und der Browser kann die Datei parsen, während der HTML-Stream noch eintrifft. Laufzeit-CSS funktioniert anders: Der Browser kann nicht einmal damit beginnen, die Komponenten-Styles zu erstellen, bis das JavaScript heruntergeladen, geparst, kompiliert und ausgeführt wurde. Auf einem Mittelklasse-Telefon bedeutet das eine Sekunde oder mehr Hauptthread-Arbeit, bevor ein einzelner Button seine Hintergrundfarbe erhält.

Es fragmentiert Ihren Cache

Generierte Klassennamen ändern sich, wenn sich die Komponentenquelle ändert, was bedeutet, dass das Stylesheet bei nahezu jedem Deployment ungültig wird. Verteilen Sie das auf Dutzende von route-level Bundles, und Sie erhalten eine Cache-Trefferquote, die in einem Dashboard gesund aussieht, aber bei wiederholten Besuchen selten greift. Ein inhaltsgehashtes statisches Stylesheet kann dagegen mit Cache-Control: public, max-age=31536000, immutable ausgeliefert werden und effektiv nie zweimal heruntergeladen werden.

Es verdoppelt die Arbeit während der Hydration

Hydrationsinkonsistenzen zwischen serverseitig injizierten und clientseitig generierten Styles sind eine klassische Quelle von Flackern. Der Browser malt einen Satz von Regeln, dann ersetzt die Client-Bibliothek sie mitten in der Interaktion, was eine Neuberechnung des Styles und manchmal eine Layoutverschiebung erzwingt. Statische Klassennamen, die zur Build-Zeit berechnet werden, sind auf beiden Seiten identisch, sodass der erste Paint auch der endgültige Paint ist.

Main-thread work before first contentful paint

Was GitHub tatsächlich geändert hat

Die Migration lässt sich am besten als fünf architektonische Schritte verstehen, nicht als eine einzige Refaktorisierung. Keiner von ihnen erfordert die Aufgabe eines Komponentenmodells, und keiner erfordert eine vollständige Neuschreibung.

web design, laptop, html, design, computer, internet, web, technology, website, development, office, business, web design, web design, web design, web design, web design, html, html, website, website, website, England, United Kingdom, Appledore

Design-Tokens wurden zu CSS Custom Properties

Farb-, Abstands-, Radius- und Typografie-Skalen wurden in einen globalen :root-Block als Custom Properties verschoben. Komponenten referenzieren var(--color-bg-default) anstatt ein JavaScript-Theme-Objekt zu importieren. Dadurch wird statisches CSS ausdrucksstark genug, um dynamische Styles in den meisten Fällen zu ersetzen, und es ermöglicht Laufzeit-Theming (Dark Mode, hoher Kontrast) mit null JavaScript.

Komponenten-Styles wurden vorab kompiliert

Jede Komponente erhielt ein echtes Stylesheet, das zur Build-Zeit ausgegeben und durch einen Hash oder eine Modulkonvention begrenzt wurde. Die Ausgabe ist gewöhnliches CSS, das jeder Browser mit Parsing-Geschwindigkeit parsen kann.

Kritisches CSS wurde in den Dokumentenkopf eingebettet

Nur die Regeln, die für den ersten Viewport benötigt werden, werden in das HTML eingebettet. Das sind typischerweise 8–14 KB gzipped, und es bedeutet, dass der erste Paint nie auf eine Netzwerk-Roundtrip für das Stylesheet wartet, das die sichtbare Seite formt.

<head>
  <!-- Critical: exactly what the first viewport needs, nothing more -->
  <style>
    /* reset + tokens + header + hero + above-the-fold grid */
  </style>

  <!-- Everything else: one hashed, immutable, long-cached file -->
  <link
    rel="stylesheet"
    href="/assets/app.8f3a1c2d.css"
    media="print"
    onload="this.media='all'"
  />
  <noscript>
    <link rel="stylesheet" href="/assets/app.8f3a1c2d.css" />
  </noscript>
</head>

Serverseitig gerendertes Markup trägt endgültige Klassennamen

Da Klassennamen zur Build-Zeit existieren, ist das HTML, das der Server zurückgibt, bereits gestylt. Es gibt keinen zweiten Durchlauf, keinen Abgleichsschritt und keinen Flash von ungestyltem Inhalt bei langsamen Verbindungen.

Kaskadenschichten ersetzten Spezifitäts-Hacks

Ein gemeinsames Designsystem lebt oder stirbt von einer vorhersagbaren Kaskadenreihenfolge. Die einmalige Deklaration der Schichtenreihenfolge im Voraus macht Überschreibungen deterministisch und beseitigt den Anreiz, zu !important oder immer tieferen Selektoren zu greifen.

@layer reset, tokens, base, components, utilities;

@layer components {
  .card {
    background: var(--color-canvas-default);
    border: 1px solid var(--color-border-default);
    border-radius: var(--radius-2);
    padding: var(--space-4);
  }
}

@layer utilities {
  .mt-4 { margin-top: var(--space-4); }
}

Die Zahlen: Was sich ändert, wenn Sie mehr CSS ausliefern

Die Verlagerung der Style-Generierung in die Build-Zeit verändert die Form dessen, was Sie ausliefern, nicht nur das Volumen. Die verbleibenden Bytes sind günstig: Sie komprimieren gut, werden über alle Routen hinweg geteilt und sind für ein Jahr gecacht.

Bytes shipped vs bytes executed

Der architektonische Gewinn zeigt sich an vier Stellen gleichzeitig: weniger herunterzuladendes JavaScript, weniger Hauptthread-Arbeit vor dem ersten Paint, ein cache-fähiges Stylesheet, das über Navigationen hinweg geteilt wird, und identisches Markup auf Server und Client.

0
JavaScript bytes executed to apply a static stylesheet at runtime
~40%
of client-side JavaScript in a typical CSS-in-JS app is style generation code and its dependencies
ApproachJS shipped (gzip)CSS shipped (gzip)Runtime style workCacheable across deploys
Runtime CSS-in-JS~180 KB~12 KBHighNo
Single monolithic stylesheet~96 KB~88 KBNoneYes
Inline critical + split static sheets~78 KB~54 KBNoneYes
Utility-first CSS~85 KB~40 KBNoneYes

Das schnellste Stylesheet ist das, das der Browser einmal parst, ein Jahr lang cached und nie wieder generieren muss.

Ein praktischer Migrationsleitfaden

Sie benötigen nicht die Codebasis von GitHub, um dieselbe Logik anzuwenden. Die Migration ist inkrementell und jeder Schritt ist unabhängig auslieferbar.

html, css, responsive, site design, themplate design, website development, html code, layout sites, programming, code, html, css, programming, programming, programming, programming, programming, code

1. Bestandsaufnahme, was tatsächlich zur Laufzeit rendert

Öffnen Sie DevTools, laden Sie eine Seite und zählen Sie document.styleSheets. Wenn Sie Dutzende von injizierten <style>-Tags mit gehashten Klassennamen finden, haben Sie Ihr Ziel gefunden. Vergleichen Sie mit dem Performance-Panel: Suchen Sie nach langen Aufgaben zwischen DOMContentLoaded und dem ersten inhaltsreichen Paint.

60%
of total page bytes can often be moved off the critical path by deferring non-critical CSS

2. Above-the-Fold-CSS extrahieren

Beginnen Sie mit dem kleinstmöglichen Inline-Block. Eine zuverlässige Heuristik ist es, Reset, Tokens und die Regeln, die von den ersten beiden Viewport-Höhen verwendet werden, inline zu setzen. Alles unterhalb der Falte wird aus dem verzögerten Stylesheet geladen.

3. Laufzeitkomponenten in statische Klassen umwandeln

Arbeiten Sie Komponente für Komponente, beginnend mit den verkehrsreichsten. Prop-getriebene Varianten werden entweder zu Modifikator-Klassen oder zu Custom Properties, die auf einem Wrapper gesetzt werden. Behalten Sie die Komponenten-API identisch, damit sich die Aufrufstellen nicht ändern.

// Genuinely dynamic values still belong in an inline style, not a generated sheet
<div
  className={styles.progressBar}
  style={{ "--progress": `${percent}%` }}
  role="progressbar"
  aria-valuenow={percent}
/>
.progressBar {
  width: var(--progress, 0%);
  transition: width 200ms ease-out;
}

Dieses Muster ist das Arbeitstier der gesamten Migration: Die Struktur des Styles ist statisch und cache-fähig, während der Wert dynamisch bleibt und nichts kostet, um aktualisiert zu werden.

4. Schichtenreihenfolge frühzeitig festlegen

Deklarieren Sie die @layer-Reihenfolge am Anfang Ihres Einstiegs-Stylesheets, bevor Sie ein Spezifitätsproblem haben, nicht danach. Das Nachrüsten von Schichten auf eine alte Kaskade ist weitaus schmerzhafter, als mit ihnen zu beginnen.

5. Dateinamen hashen und unveränderlich cachenContent-Hashing plus immutable ist das, was aus "mehr CSS" "kostenloses CSS" macht. Der zweite Seitenaufruf in einer Sitzung sollte den HTTP-Cache für jede Stilregel auf der Website treffen.

6. Dynamisches Styling dynamisch halten

Transform-, Scrollpositions- und messwertgesteuerte Werte gehören in Inline-Styles, requestAnimationFrame oder die Web Animations API. Führen Sie keine Laufzeit-Style-Engine wieder ein, um ein Problem zu lösen, das CSS Custom Properties bereits lösen.

So messen Sie die Verbesserung

Messen Sie vorher und nachher dieselben drei Dinge: Paint-Timing, Main-Thread-Long-Tasks und die Anzahl der Stylesheets. Ein kleines PerformanceObserver-Skript liefert Ihnen alle drei, ohne eine Monitoring-Abhängigkeit hinzuzufügen.

new PerformanceObserver((list) => {
  for (const entry of list.getEntries()) {
    console.log(entry.name, Math.round(entry.startTime));
  }
}).observe({ type: "paint", buffered: true });

new PerformanceObserver((list) => {
  const long = list.getEntries().filter((e) => e.duration > 50);
  console.log(`${long.length} long tasks after load`);
}).observe({ type: "longtask", buffered: true });

console.log("stylesheets:", document.styleSheets.length);

Erwarten Sie die größte Bewegung bei der Time-to-First-Paint bei wiederholten Besuchen und bei gedrosselten Mobilprofilen, wo die eliminierten JavaScript-Parse-Kosten dominieren. Der Largest Contentful Paint verbessert sich tendenziell gradueller, von Release zu Release.

p75 Largest Contentful Paint across releases

Fallstricke, die es zu vermeiden gilt

Der Ansatz ist nicht risikofrei, und die meisten Fehler entstehen durch eine zu aggressive Anwendung.

coding, computer, hacker, hacking, html, programmer, programming, script, scripting, source code, coding, coding, coding, coding, computer, computer, hacker, hacker, hacker, hacker, hacker, hacking, hacking, programming, programming

  • **Unvollständiges kritisches CSS.**Wenn Sie das erste Viewport falsch einschätzen, kommt es zu einem sichtbaren Reflow, sobald das verzögerte Blatt eintrifft. Generieren Sie den kritischen Block automatisch aus einem echten Render und nicht von Hand. -**Alles inlinen.**Das vollständige Stylesheet in jede HTML-Antwort zu inlinen, hebt das Caching auf und bläht die Dokumentgröße auf. Inlinen Sie das Minimum, verschieben Sie den Rest. -**Bundle-alles-Instinkte.**Der alte Ratschlag, alles CSS in eine riesige Datei zu konkatenieren, stammt aus der Zeit vor HTTP/2. Eine Aufteilung nach Route ist heute oft besser, solange gemeinsame Primitive separat gecacht werden. -Zwei Systeme mischen. Wenn Sie Laufzeit-CSS-in-JS und statische Stylesheets zu lange nebeneinander betreiben, verdoppeln Sie Ihre Style-Oberfläche und machen das Cascade-Debugging zur Qual. Setzen Sie eine Migrationsfrist pro Komponentenverzeichnis.

Die häufigste nicht-technische Falle ist es, dies als Big-Bang-Rewrite zu behandeln. Die Teams, die erfolgreich sind, konvertieren eine Handvoll stark frequentierter Komponenten, messen das Delta und nutzen diese Daten, um den nächsten Batch zu rechtfertigen.

Mit DivMagic reale CSS-Architekturen nachvollziehen

Ein unterschätzter Teil dieser Migration ist zu sehen, wie ausgereifte Produktionsseiten tatsächlich ihre Stylesheets strukturieren. Wenn Sie eine Seite inspizieren, die statisches CSS gut ausliefert, können Sie in fünf Minuten mehr lernen als in einer Stunde Dokumentation: wie sie die Kaskade schichten, wie sie Tokens scopen, wie sie kritisches von verzögertem CSS trennen und wie sie Modifier benennen.

DivMagic ist genau für diesen Workflow gebaut. Anstatt Element-Styles Regel für Regel manuell aus den DevTools zu kopieren, wählen Sie ein beliebiges UI auf einer beliebigen Seite aus und ziehen das resultierende HTML und CSS direkt in Ihren Editor, bereit zur Anpassung. Das ist in zwei Situationen wirklich nützlich: beim Prototyping einer Komponente aus einem Referenzdesign und beim Studieren, wie eine von Ihnen bewunderte Hochleistungsseite ihre Kaskade organisiert, damit Sie das Muster in Ihrem eigenen Designsystem spiegeln können.

Es verkürzt auch den langweiligen Teil von Schritt drei oben. Wenn Sie eine Laufzeitkomponente in statische Klassen konvertieren, entfällt durch die endgültigen berechneten Styles in einer bearbeitbaren Form – anstatt verstreuter Inline-Styles und injizierter <style>-Blöcke – der größte Teil der Transkriptionsarbeit.

Die wichtigsten Erkenntnisse

  • Die Generierung von Laufzeit-Styles ist Main-Thread-JavaScript-Arbeit, die nach dem JavaScript-Download direkt in Ihrem kritischen Rendering-Pfad stattfindet.
  • Statisches CSS wird einmal geparst, bei Content-Hashing ein Jahr lang gecacht und über jede Route und jede Navigation hinweg geteilt.
  • Inlinen Sie nur die Regeln, die das erste Viewport benötigt; verschieben Sie den Rest hinter ein nicht-blockierendes <link> und eine lange Cache-Richtlinie.
  • CSS Custom Properties plus @layer decken die überwältigende Mehrheit der Fälle ab, die früher eine prop-gesteuerte Style-Interpolation erforderten.
  • Migrieren Sie inkrementell, Komponente für Komponente, und messen Sie p75-Paint-Timing und Long Tasks anstatt Durchschnitte.

Mehr CSS auszuliefern klingt nach einer Regression, bis Ihnen klar wird, dass Sie ein gecachtes, einmal geparstes Stylesheet gegen Megabyte an JavaScript eintauschen, das es bei jedem Seitenaufbau neu erstellt.

Die Lektion lässt sich gut über GitHub hinaus verallgemeinern. Jede Abstraktion, die im Browser läuft, sind Kosten, die der Nutzer auf seinem Gerät, in seinem Netzwerk, jedes einzelne Mal bezahlt. Die Style-Generierung in die Build-Zeit zu verlegen, ist einer der wirkungsvollsten Trades, die einem komponentengesteuerten Frontend zur Verfügung stehen: Sie liefern eine statische Datei aus, mit der der Browser bereits umzugehen weiß, und Sie löschen die Maschinerie, die ihm früher im Weg stand.

Beginnen Sie noch heute mit der Erstellung mit DivMagic

Schließen Sie sich über 10.000 Entwicklern, Designern und Geschäftsinhabern an, um Code von jeder Website zu kopieren und ihn in ihren eigenen Projekten zu verwenden.

Get DivMagic for 42% off

Limited time deal for 22:45