Améliorer les performances du site en livrant plus de CSS
Il y a un instinct ancré dans le travail frontend moderne qui dit que la page la plus rapide livre le moins de tout : moins de JavaScript, moins de polices, moins de requêtes. Alors quand l'équipe d'ingénierie de GitHub a écrit sur l'amélioration des performances du site en livrant plus de CSS, cela ressemblait à une contradiction. La solution pour une interface lente et lourde en JavaScript n'était pas de retirer le style de l'application. C'était d'en remettre davantage, sous forme de feuilles de style simples, statiques et cachable, et de supprimer le JavaScript qui existait uniquement pour générer des styles dans le navigateur.
La distinction est tout l'article. Livrer « plus de CSS » ne signifie pas une feuille de style plus grosse qui alourdit votre chemin critique. Cela signifie déplacer la génération de style hors de l'exécution et dans la construction, puis laisser le navigateur faire ce qu'il fait vraiment bien : analyser une feuille de style une fois, la mettre en cache et la réutiliser à travers chaque navigation.
Le coût caché du style à l'exécution
Les bibliothèques CSS-in-JS ont résolu de vrais problèmes. Elles ont offert aux auteurs de composants le scoping, la colocalisation et la capacité d'interpoler des propriétés dans les styles sans inventer une convention de nommage. Le compromis était architectural : elles ont déplacé un problème de temps de compilation dans le navigateur.
La génération de style est un travail JavaScript
Chaque page rendue par le serveur utilisant une bibliothèque CSS-in-JS à l'exécution demande au navigateur de faire le même travail deux fois. Le serveur sérialise les styles dans des balises <style>, puis la bibliothèque cliente se réveille, parcourt l'arbre des composants, hache à nouveau les noms de classe et réconcilie le résultat. Ce travail se produit sur le thread principal, en concurrence avec les étapes de mise en page et de peinture que vous essayez d'accélérer.
// 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;
`;
Le même composant exprimé sous forme de feuille de style au moment de la construction supprime à la fois la dépendance à l'exécution et le travail de sérialisation :
/* 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} />;
}
Rien de tout cela n'est nouveau. Ce qui est facile à sous-estimer, c'est la part d'un bundle qu'une pile CSS-in-JS mature possède silencieusement : la bibliothèque elle-même, les utilitaires d'interpolation de propriétés, le fournisseur de thème, le sérialiseur et le code de framework dupliqué qui croît lorsque chaque package dépend de sa propre copie.
Il se trouve directement sur le chemin critique de rendu
Un <link rel="stylesheet"> bloque le rendu, mais il le bloque une fois, et le navigateur peut analyser le fichier pendant que le flux HTML arrive encore. Le CSS à l'exécution fonctionne différemment : le navigateur ne peut même pas commencer à construire les styles du composant tant que le JavaScript n'a pas été téléchargé, analysé, compilé et exécuté. Sur un téléphone de milieu de gamme, cela représente une seconde ou plus de travail sur le thread principal avant qu'un seul bouton n'obtienne sa couleur d'arrière-plan.
Il fragmente votre cache
Les noms de classe générés changent à chaque modification du source du composant, ce qui signifie que la feuille de style s'invalide à presque chaque déploiement. Répartissez cela sur des dizaines de bundles au niveau des routes et vous obtenez un taux de succès de cache qui semble sain dans un tableau de bord mais se déclenche rarement lors des visites répétées. Une feuille de style statique avec hachage de contenu, en revanche, peut être servie avec Cache-Control: public, max-age=31536000, immutable et n'être effectivement jamais téléchargée deux fois.
Il double le travail pendant l'hydratation
Les décalages d'hydratation entre les styles injectés par le serveur et ceux générés par le client sont une source classique de scintillement. Le navigateur peint un ensemble de règles, puis la bibliothèque cliente les remplace en pleine interaction, forçant un recalcul de style et parfois un changement de mise en page. Les noms de classe statiques calculés au moment de la construction sont identiques des deux côtés, donc la première peinture est aussi la dernière.

Ce que GitHub a réellement changé
La migration est mieux comprise comme cinq mouvements architecturaux plutôt qu'un seul refactoring. Aucun d'eux n'exige d'abandonner un modèle de composant, et aucun n'exige une réécriture complète.

Les jetons de design sont devenus des propriétés personnalisées CSS
Les échelles de couleur, d'espacement, de rayon et de typographie ont été déplacées dans un bloc global :root en tant que propriétés personnalisées. Les composants référencent var(--color-bg-default) au lieu d'importer un objet de thème JavaScript. C'est ce qui rend le CSS statique suffisamment expressif pour remplacer les styles dynamiques dans la plupart des cas, et cela permet le thème à l'exécution (mode sombre, contraste élevé) avec zéro JavaScript.
Les styles des composants compilés à l'avance
Chaque composant a reçu une véritable feuille de style, émise au moment de la construction et délimitée par un hachage ou une convention de module. Le résultat est du CSS ordinaire que tout navigateur peut analyser à la vitesse d'analyse.
CSS critique intégré dans l'en-tête du document
Seules les règles nécessaires pour la première zone d'affichage sont intégrées dans le HTML. Cela représente généralement 8 à 14 Ko compressés en gzip, et cela signifie que la première peinture n'attend jamais un aller-retour réseau pour la feuille de style qui façonne la page visible.
<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>
Le balisage rendu par le serveur porte les noms de classe finaux
Parce que les noms de classe existent au moment de la construction, le HTML que le serveur renvoie est déjà stylisé. Il n'y a pas de deuxième passage, pas d'étape de réconciliation et pas de flash de contenu non stylisé sur les connexions lentes.
Les couches de cascade ont remplacé les astuces de spécificité
Un système de design partagé vit ou meurt selon un ordre de cascade prévisible. Déclarer l'ordre des couches une fois, dès le départ, rend les surcharges déterministes et supprime l'incitation à recourir à !important ou à des sélecteurs toujours plus profonds.
@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); }
}
Les chiffres : ce qui change quand vous livrez plus de CSS
Déplacer la génération de style au moment de la construction change la forme de ce que vous livrez, pas seulement le volume. Les octets qui restent sont peu coûteux : ils se compressent bien, ils sont partagés sur toutes les routes et ils sont mis en cache pendant un an.

Le bénéfice architectural apparaît à quatre endroits à la fois : moins de JavaScript à télécharger, moins de travail sur le thread principal avant la première peinture, une feuille de style cachable partagée entre les navigations et un balisage identique sur le serveur et le client.
| Approach | JS shipped (gzip) | CSS shipped (gzip) | Runtime style work | Cacheable across deploys |
|---|---|---|---|---|
| Runtime CSS-in-JS | ~180 KB | ~12 KB | High | No |
| Single monolithic stylesheet | ~96 KB | ~88 KB | None | Yes |
| Inline critical + split static sheets | ~78 KB | ~54 KB | None | Yes |
| Utility-first CSS | ~85 KB | ~40 KB | None | Yes |
La feuille de style la plus rapide est celle que le navigateur analyse une fois, met en cache pendant un an et n'a jamais à générer à nouveau.
Un guide pratique de migration
Vous n'avez pas besoin du codebase de GitHub pour appliquer le même raisonnement. La migration est incrémentale et chaque étape est livrable indépendamment.

1. Inventoriez ce qui est réellement rendu à l'exécution
Ouvrez DevTools, chargez une page et comptez document.styleSheets. Si vous trouvez des dizaines de balises <style> injectées avec des noms de classe hachés, vous avez trouvé votre cible. Recoupez avec le panneau Performance : recherchez les longues tâches entre DOMContentLoaded et le premier affichage de contenu.
2. Extrayez le CSS au-dessus de la ligne de flottaison
Commencez par le plus petit bloc en ligne possible. Une heuristique fiable consiste à intégrer la réinitialisation, les jetons et les règles utilisées par les deux premières hauteurs de zone d'affichage. Tout ce qui se trouve en dessous de la ligne de flottaison se charge à partir de la feuille de style différée.
3. Convertissez les composants d'exécution en classes statiques
Travaillez composant par composant, en commençant par ceux ayant le plus de trafic. Les variantes pilotées par des propriétés deviennent soit des classes de modification, soit des propriétés personnalisées définies sur un wrapper. Gardez l'API du composant identique afin que les sites d'appel ne changent pas.
// 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;
}
Ce motif est le cheval de bataille de toute la migration : la structure du style est statique et cachable, tandis que la valeur reste dynamique et ne coûte rien à mettre à jour.
4. Établissez l'ordre des couches tôt
Déclarez l'ordre @layer en haut de votre feuille de style d'entrée avant d'avoir un problème de spécificité, pas après. Adapter des couches à une cascade héritée est bien plus douloureux que de commencer avec elles.
5. Hachez les noms de fichiers et mettez en cache de manière immuableLe hachage de contenu associé à immutable est ce qui transforme « plus de CSS » en « CSS gratuit ». La deuxième vue d'une page dans une session devrait atteindre le cache HTTP pour chaque règle de style du site.
6. Gardez le style véritablement dynamique dynamique
Les valeurs liées aux transformations, à la position de défilement et aux mesures appartiennent aux styles en ligne, à requestAnimationFrame ou à l'API Web Animations. Ne réintroduisez pas un moteur de style d'exécution pour résoudre un problème que les propriétés personnalisées CSS résolvent déjà.
Comment mesurer l'amélioration
Mesurez les trois mêmes choses avant et après : le timing de peinture, les longues tâches sur le thread principal et le nombre de feuilles de style. Un petit script PerformanceObserver vous donne les trois sans ajouter de dépendance de surveillance.
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);
Attendez-vous au plus grand mouvement dans le temps jusqu'au premier affichage lors des visites répétées et sur les profils mobiles limités, où le coût d'analyse JavaScript éliminé domine. Le Largest Contentful Paint a tendance à s'améliorer plus progressivement, version après version.

Pièges à éviter
L'approche n'est pas sans risque, et la plupart des échecs viennent d'une application trop agressive.

- **CSS critique incomplet.**Si vous vous trompez sur le premier viewport, vous obtenez un reflow visible lorsque la feuille différée arrive. Générez le bloc critique automatiquement à partir d'un rendu réel plutôt qu'à la main. -**Tout intégrer en ligne.**Intégrer la feuille de style complète dans chaque réponse HTML annule la mise en cache et gonfle la taille du document. Intégrez le minimum, différez le reste. -**Les réflexes de tout regrouper.**Les vieux conseils de concaténer tout le CSS en un seul fichier géant sont antérieurs à HTTP/2. Diviser par route est souvent meilleur maintenant, tant que les primitives partagées sont mises en cache séparément. -Mélanger deux systèmes. Faire fonctionner côte à côte du CSS-in-JS d'exécution et des feuilles de style statiques trop longtemps double votre surface de style et rend le débogage de la cascade misérable. Fixez une date limite de migration par répertoire de composants.
L'écueil non technique le plus courant est de traiter cela comme une réécriture big-bang. Les équipes qui réussissent convertissent une poignée de composants à fort trafic, mesurent le delta et utilisent ces données pour justifier le lot suivant.
Utiliser DivMagic pour faire de la rétro-ingénierie d'une architecture CSS réelle
Une partie sous-estimée de cette migration est de voir comment les sites de production matures structurent réellement leurs feuilles de style. Lorsque vous inspectez un site qui livre bien du CSS statique, vous pouvez apprendre plus en cinq minutes qu'en une heure de lecture de documentation : comment ils organisent la cascade, comment ils délimitent les jetons, comment ils séparent le critique du différé, et comment ils nomment les modificateurs.
DivMagic est conçu exactement pour ce flux de travail. Au lieu de copier manuellement les styles d'éléments depuis DevTools une règle à la fois, vous sélectionnez n'importe quelle UI sur n'importe quelle page et vous récupérez le HTML et le CSS résultants directement dans votre éditeur, prêts à être adaptés. C'est véritablement utile dans deux situations : le prototypage d'un composant à partir d'un design de référence, et l'étude de la façon dont un site haute performance que vous admirez organise sa cascade afin de pouvoir refléter le modèle dans votre propre système de design.
Cela raccourcit également la partie ennuyeuse de l'étape trois ci-dessus. Lorsque vous convertissez un composant d'exécution en classes statiques, avoir les styles calculés finaux sous une forme modifiable, plutôt que des styles en ligne dispersés et des blocs <style> injectés, supprime la majeure partie du travail de transcription.
Points clés à retenir
- La génération de style d'exécution est un travail JavaScript sur le thread principal qui se produit après le téléchargement du JavaScript, directement sur votre chemin de rendu critique.
- Le CSS statique est analysé une fois, mis en cache pendant un an lorsqu'il est haché par contenu, et partagé sur chaque route et chaque navigation.
- Intégrez en ligne uniquement les règles dont le premier viewport a besoin ; différez le reste derrière un
<link>non bloquant et une politique de cache longue. - Les propriétés personnalisées CSS associées à
@layercouvrent la grande majorité des cas qui nécessitaient auparavant une interpolation de style basée sur les propriétés. - Migrez de manière incrémentale, composant par composant, et mesurez le timing de peinture p75 et les longues tâches plutôt que les moyennes.
Livrer plus de CSS semble être une régression jusqu'à ce que vous réalisiez que vous échangez une feuille de style mise en cache et analysée une fois contre des mégaoctets de JavaScript qui la reconstruisaient à chaque chargement de page.
La leçon se généralise bien au-delà de GitHub. Chaque abstraction qui s'exécute dans le navigateur est un coût payé par l'utilisateur, sur son appareil, sur son réseau, à chaque fois. Déplacer la génération de style au moment de la construction est l'un des échanges les plus efficaces disponibles pour un frontend piloté par les composants : vous livrez un fichier statique que le navigateur sait déjà gérer, et vous supprimez la machinerie qui lui barrait le passage.
