अधिक CSS शिप करके साइट परफ़ॉर्मेंस सुधारना
आधुनिक फ्रंटएंड काम में एक सहज प्रवृत्ति बैठी हुई है जो कहती है कि सबसे तेज़ पेज हर चीज़ में सबसे कम शिप करता है: कम JavaScript, कम फ़ॉन्ट, कम रिक्वेस्ट। इसलिए जब GitHub की इंजीनियरिंग टीम ने अधिक CSS शिप करके साइट परफ़ॉर्मेंस सुधारने के बारे में लिखा, तो यह विरोधाभास जैसा लगा। धीमे, JavaScript-भारी इंटरफ़ेस का समाधान ऐप से स्टाइलिंग हटाना नहीं था। समाधान यह था कि उसका अधिक हिस्सा वापस डाला जाए, सादी, स्टैटिक, कैशेबल स्टाइलशीट्स के रूप में, और उस JavaScript को हटाया जाए जो केवल ब्राउज़र में स्टाइल्स जनरेट करने के लिए मौजूद था।
यही अंतर पूरा लेख है। "अधिक CSS" शिप करने का अर्थ यह नहीं है कि आपके क्रिटिकल पाथ को बड़ा स्टाइलशीट फुला दे। इसका अर्थ है स्टाइल जनरेशन को रनटाइम से बाहर निकालकर बिल्ड में ले जाना, और फिर ब्राउज़र को वह करने देना जिसमें वह वास्तव में उत्कृष्ट है: स्टाइलशीट को एक बार पार्स करना, उसे कैश करना, और हर नेविगेशन पर उसका पुनः उपयोग करना।
रनटाइम स्टाइलिंग की छिपी लागत
CSS-in-JS लाइब्रेरीज़ ने वास्तविक समस्याएँ हल कीं। उन्होंने कंपोनेंट लेखकों को स्कोपिंग, कोलोकेशन, और बिना कोई नेमिंग कन्वेंशन बनाए प्रॉप्स को स्टाइल्स में इंटरपोलेट करने की क्षमता दी। ट्रेडऑफ़ आर्किटेक्चरल था: उन्होंने कंपाइल-टाइम समस्या को ब्राउज़र में ले जाकर रख दिया।
स्टाइल जनरेशन JavaScript का काम है
रनटाइम CSS-in-JS लाइब्रेरी का उपयोग करने वाला हर सर्वर-रेंडर किया गया पेज ब्राउज़र से वही काम दो बार करने को कहता है। सर्वर स्टाइल्स को <style> टैग में सीरियलाइज़ करता है, फिर क्लाइंट लाइब्रेरी जागती है, कंपोनेंट ट्री पर चलती है, क्लास नामों को फिर से हैश करती है, और परिणाम को रीकंसाइल करती है। वह काम मुख्य थ्रेड पर होता है, और उन्हीं लेआउट व पेंट चरणों से प्रतिस्पर्धा करता है जिन्हें आप तेज़ करने की कोशिश कर रहे हैं।
// 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;
`;
बिल्ड-टाइम स्टाइलशीट के रूप में व्यक्त वही कंपोनेंट रनटाइम डिपेंडेंसी और सीरियलाइज़ेशन कार्य दोनों को हटा देता है:
/* 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} />;
}
यहाँ कुछ भी नया नहीं है। जिस बात को कम आँकना आसान है, वह यह है कि एक परिपक्व CSS-in-JS स्टैक चुपचाप कितने बड़े बंडल का मालिक होता है: लाइब्रेरी स्वयं, प्रॉप-इंटरपोलेशन यूटिलिटीज़, थीम प्रोवाइडर, सीरियलाइज़र, और वह डुप्लिकेट फ्रेमवर्क कोड जो तब बढ़ता है जब हर पैकेज अपनी अलग कॉपी पर निर्भर होता है।
यह सीधे क्रिटिकल रेंडरिंग पाथ पर बैठता है
एक <link rel="stylesheet"> रेंडरिंग को ब्लॉक करता है, लेकिन यह इसे एक बार ब्लॉक करता है, और ब्राउज़र HTML स्ट्रीम के अभी आते रहते ही फ़ाइल को पार्स कर सकता है। रनटाइम CSS अलग तरह से काम करता है: जब तक JavaScript डाउनलोड, पार्स, कंपाइल और एक्ज़ीक्यूट नहीं हो जाता, ब्राउज़र कंपोनेंट स्टाइल्स बनाना शुरू भी नहीं कर सकता। एक मिड-टियर फ़ोन पर यह एक सेकंड या उससे अधिक का मुख्य-थ्रेड कार्य है, इससे पहले कि किसी एक बटन को उसका बैकग्राउंड रंग मिले।
यह आपका कैश फ्रैगमेंट करता है
जनरेट किए गए क्लास नाम तब बदलते हैं जब भी कंपोनेंट सोर्स बदलता है, जिसका अर्थ है कि स्टाइलशीट लगभग हर डिप्लॉय पर अमान्य हो जाती है। इसे दर्जनों रूट-लेवल बंडलों में बाँट दें, तो आपको ऐसी कैश हिट दर मिलती है जो डैशबोर्ड में स्वस्थ दिखती है, लेकिन दोबारा विज़िट पर शायद ही ट्रिगर होती है। इसके विपरीत, कंटेंट-हैश्ड स्टैटिक स्टाइलशीट को Cache-Control: public, max-age=31536000, immutable के साथ सर्व किया जा सकता है और व्यावहारिक रूप से इसे दोबारा कभी डाउनलोड नहीं करना पड़ता।
यह हाइड्रेशन के दौरान काम दोगुना करता है
सर्वर-इंजेक्टेड और क्लाइंट-जनरेटेड स्टाइल्स के बीच हाइड्रेशन मिसमैच फ्लिकर का एक क्लासिक स्रोत हैं। ब्राउज़र नियमों का एक सेट पेंट करता है, फिर क्लाइंट लाइब्रेरी इंटरैक्शन के बीच में उन्हें बदल देती है, जिससे स्टाइल की पुनर्गणना और कभी-कभी लेआउट शिफ्ट होता है। बिल्ड टाइम पर गणना किए गए स्टैटिक क्लास नाम दोनों तरफ़ एक जैसे होते हैं, इसलिए पहला पेंट ही अंतिम पेंट होता है।

GitHub ने वास्तव में क्या बदला
इस माइग्रेशन को एक रिफ़ैक्टर के बजाय पाँच आर्किटेक्चरल कदमों के रूप में समझना सबसे अच्छा है। इनमें से कोई भी कंपोनेंट मॉडल छोड़ने की माँग नहीं करता, और किसी के लिए पूरी तरह से रीराइट की ज़रूरत नहीं है।

डिज़ाइन टोकन CSS कस्टम प्रॉपर्टीज़ बने
रंग, स्पेसिंग, रेडियस और टाइपोग्राफ़ी स्केल कस्टम प्रॉपर्टीज़ के रूप में एक ग्लोबल :root ब्लॉक में चले गए। कंपोनेंट्स JavaScript थीम ऑब्जेक्ट इम्पोर्ट करने के बजाय var(--color-bg-default) का संदर्भ देते हैं। यही स्टैटिक CSS को इतना अभिव्यक्तिपूर्ण बनाता है कि वह अधिकांश मामलों में डायनामिक स्टाइल्स की जगह ले सके, और यह शून्य JavaScript के साथ रनटाइम थीमिंग (डार्क मोड, हाई कंट्रास्ट) को सक्षम करता है।
कंपोनेंट स्टाइल्स पहले से कंपाइल
हर कंपोनेंट को एक वास्तविक स्टाइलशीट मिली, जो बिल्ड टाइम पर बनाई गई और हैश या मॉड्यूल कन्वेंशन द्वारा स्कोप की गई। आउटपुट साधारण CSS है जिसे कोई भी ब्राउज़र पार्स गति पर पार्स कर सकता है।
क्रिटिकल CSS डॉक्यूमेंट हेड में इनलाइन
केवल पहले व्यूपोर्ट के लिए आवश्यक नियम HTML में इनलाइन किए जाते हैं। यह आमतौर पर gzip किए हुए 8–14 KB होता है, और इसका अर्थ है कि पहला पेंट उस स्टाइलशीट के लिए नेटवर्क राउंड ट्रिप का इंतज़ार कभी नहीं करता जो दिखने वाले पेज को आकार देती है।
<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>
सर्वर-रेंडर किया गया मार्कअप अंतिम क्लास नाम लेकर आता है
चूँकि क्लास नाम बिल्ड टाइम पर मौजूद होते हैं, सर्वर जो HTML लौटाता है वह पहले से स्टाइल किया हुआ होता है। कोई दूसरा पास नहीं, कोई रीकंसिलिएशन चरण नहीं, और धीमे कनेक्शन पर अनस्टाइल्ड कंटेंट का कोई फ्लैश नहीं।
कैस्केड लेयर्स ने स्पेसिफिसिटी हैक्स की जगह ली
एक साझा डिज़ाइन सिस्टम पूर्वानुमेय कैस्केड क्रम पर टिकता है या नहीं टिकता। लेयर क्रम को एक बार, शुरुआत में घोषित करना ओवरराइड्स को निर्धारक बनाता है और !important या और गहरे सेलेक्टर्स का सहारा लेने की प्रेरणा हटा देता है।
@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); }
}
आँकड़े: जब आप अधिक CSS शिप करते हैं तो क्या बदलता है
स्टाइल जनरेशन को बिल्ड टाइम पर ले जाना आप जो शिप करते हैं उसका आकार बदलता है, केवल मात्रा नहीं। जो बाइट्स बचते हैं वे सस्ते हैं: वे अच्छी तरह कंप्रेस होते हैं, हर रूट पर साझा होते हैं, और एक साल के लिए कैश होते हैं।

आर्किटेक्चरल लाभ एक साथ चार जगहों पर दिखता है: डाउनलोड करने के लिए कम JavaScript, पहले पेंट से पहले कम मुख्य-थ्रेड कार्य, नेविगेशन्स में साझा की जाने वाली कैशेबल स्टाइलशीट, और सर्वर व क्लाइंट पर एक जैसा मार्कअप।
| 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 |
सबसे तेज़ स्टाइलशीट वही है जिसे ब्राउज़र एक बार पार्स करता है, एक साल के लिए कैश करता है, और फिर कभी जनरेट नहीं करना पड़ता।
व्यावहारिक माइग्रेशन प्लेबुक
वही तर्क लागू करने के लिए आपको GitHub के कोडबेस की ज़रूरत नहीं है। माइग्रेशन इंक्रीमेंटल है और हर कदम स्वतंत्र रूप से शिप करने योग्य है।

1. रनटाइम पर वास्तव में क्या रेंडर होता है, इसकी सूची बनाएँ
DevTools खोलें, एक पेज लोड करें, और document.styleSheets गिनें। यदि आपको हैश किए गए क्लास नामों वाले दर्जनों इंजेक्टेड <style> टैग मिलते हैं, तो आपने अपना लक्ष्य पा लिया है। Performance पैनल के साथ क्रॉस-रेफ़रेंस करें: DOMContentLoaded और फ़र्स्ट कंटेंटफुल पेंट के बीच लंबे टास्क खोजें।
2. अबव-द-फोल्ड CSS निकालें
सबसे छोटे संभव इनलाइन ब्लॉक से शुरू करें। एक विश्वसनीय ह्यूरिस्टिक यह है कि रीसेट, टोकन और पहले दो व्यूपोर्ट हाइट्स द्वारा उपयोग किए जाने वाले नियमों को इनलाइन करें। फोल्ड के नीचे का सब कुछ डिफर्ड स्टाइलशीट से लोड होता है।
3. रनटाइम कंपोनेंट्स को स्टैटिक क्लासेस में बदलें
कंपोनेंट-दर-कंपोनेंट काम करें, सबसे अधिक ट्रैफ़िक वाले कंपोनेंट्स से शुरू करें। प्रॉप-चालित वेरिएंट्स या तो मॉडिफ़ायर क्लासेस बन जाते हैं या रैपर पर सेट की गई कस्टम प्रॉपर्टीज़। कंपोनेंट API को एक जैसा रखें ताकि कॉल साइट्स न बदलें।
// 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;
}
यह पैटर्न पूरे माइग्रेशन का वर्कहॉर्स है: स्टाइल की संरचना स्टैटिक और कैशेबल रहती है, जबकि मान डायनामिक रहता है और अपडेट करने में कुछ भी खर्च नहीं होता।
4. लेयर क्रम जल्दी स्थापित करें
स्पेसिफिसिटी समस्या आने से पहले, न कि बाद में, अपनी एंट्री स्टाइलशीट के शीर्ष पर @layer क्रम घोषित करें। लेगेसी कैस्केड पर लेयर्स को बाद में जोड़ना उनसे शुरू करने की तुलना में कहीं अधिक कष्टदायक है।
5. फ़ाइल नामों को हैश करें और इम्यूटेबल रूप से कैश करें## सुधार को कैसे मापें
पहले और बाद में तीन समान चीजों को मापें: पेंट टाइमिंग, मेन-थ्रेड लॉन्ग टास्क, और स्टाइलशीट काउंट। एक छोटा PerformanceObserver स्क्रिप्ट आपको बिना कोई मॉनिटरिंग डिपेंडेंसी जोड़े ये तीनों देता है।
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);
दोबारा विज़िट और थ्रॉटल किए गए मोबाइल प्रोफाइल पर टाइम-टू-फर्स्ट-पेंट में सबसे बड़ा बदलाव देखने की उम्मीद करें, जहां समाप्त किए गए JavaScript पार्स लागत का प्रभुत्व है। लार्जेस्ट कंटेंटफुल पेंट में रिलीज़ दर रिलीज़ अधिक धीरे-धीरे सुधार होता है।

बचने योग्य नुकसान
यह दृष्टिकोण जोखिम-मुक्त नहीं है, और अधिकांश विफलताएं इसे बहुत आक्रामक रूप से लागू करने से आती हैं।

- **अपूर्ण क्रिटिकल CSS।**यदि आप पहले व्यूपोर्ट के बारे में गलत अनुमान लगाते हैं, तो विलंबित शीट आने पर एक दृश्य रीफ्लो होता है। क्रिटिकल ब्लॉक को हाथ से नहीं बल्कि वास्तविक रेंडर से स्वचालित रूप से जनरेट करें। -**सब कुछ इनलाइन करना।**पूरी स्टाइलशीट को हर HTML रिस्पॉन्स में इनलाइन करना कैशिंग को विफल करता है और दस्तावेज़ के आकार को बढ़ाता है। न्यूनतम इनलाइन करें, बाकी को विलंबित करें। -**सब कुछ बंडल करने की प्रवृत्ति।**सभी CSS को एक विशाल फ़ाइल में संयोजित करने की पुरानी सलाह HTTP/2 से पहले की है। अब रूट के अनुसार विभाजित करना अक्सर बेहतर होता है, जब तक कि साझा प्रिमिटिव अलग से कैश किए जाते हैं। -दो प्रणालियों को मिलाना। रनटाइम CSS-in-JS और स्टैटिक स्टाइलशीट को बहुत लंबे समय तक साथ-साथ चलाने से आपकी स्टाइल सतह दोगुनी हो जाती है और कैस्केड डिबगिंग दयनीय बना देती है। प्रति कंपोनेंट निर्देशिका के लिए एक माइग्रेशन समय सीमा निर्धारित करें।
सबसे आम गैर-तकनीकी नुकसान इसे एक बड़े-बैंग पुनर्लेखन के रूप में मानना है। जो टीमें सफल होती हैं, वे कुछ उच्च-ट्रैफिक कंपोनेंट्स को कन्वर्ट करती हैं, डेल्टा को मापती हैं, और उस डेटा का उपयोग अगले बैच को उचित ठहराने के लिए करती हैं।
वास्तविक CSS आर्किटेक्चर को रिवर्स-इंजीनियर करने के लिए DivMagic का उपयोग करना
इस माइग्रेशन का एक कम आंका गया हिस्सा यह देखना है कि परिपक्व उत्पादन साइटें वास्तव में अपनी स्टाइलशीट को कैसे संरचित करती हैं। जब आप एक ऐसी साइट का निरीक्षण करते हैं जो स्टैटिक CSS को अच्छी तरह से भेजती है, तो आप पांच मिनट में दस्तावेज़ीकरण पढ़ने के एक घंटे से अधिक सीख सकते हैं: वे कैस्केड को कैसे स्तरित करते हैं, वे टोकन को कैसे स्कोप करते हैं, वे क्रिटिकल को डिफर्ड से कैसे अलग करते हैं, और वे मॉडिफायर्स को कैसे नाम देते हैं।
DivMagic ठीक उसी वर्कफ़्लो के लिए बनाया गया है। DevTools से एक-एक करके एलिमेंट स्टाइल को हाथ से कॉपी करने के बजाय, आप किसी भी पेज पर किसी भी UI का चयन करते हैं और परिणामी HTML और CSS को सीधे अपने एडिटर में खींच लेते हैं, जो अनुकूलन के लिए तैयार है। यह दो स्थितियों में वास्तव में उपयोगी है: एक संदर्भ डिज़ाइन से एक कंपोनेंट का प्रोटोटाइप बनाना, और यह अध्ययन करना कि आपकी प्रशंसा की एक उच्च-प्रदर्शन साइट अपने कैस्केड को कैसे व्यवस्थित करती है ताकि आप अपने स्वयं के डिज़ाइन सिस्टम में पैटर्न को प्रतिबिंबित कर सकें।
यह ऊपर चरण तीन के उबाऊ भाग को भी छोटा करता है। जब आप एक रनटाइम कंपोनेंट को स्टैटिक क्लासेस में कन्वर्ट कर रहे होते हैं, तो अंतिम गणना की गई शैलियों को एक संपादन योग्य रूप में रखना, बिखरी हुई इनलाइन शैलियों और इंजेक्ट किए गए <style> ब्लॉक के बजाय, अधिकांश ट्रांसक्रिप्शन कार्य को हटा देता है।
मुख्य निष्कर्ष
- रनटाइम स्टाइल जनरेशन मेन-थ्रेड JavaScript कार्य है जो JavaScript डाउनलोड के बाद, सीधे आपके क्रिटिकल रेंडरिंग पथ पर होता है।
- स्टैटिक CSS को एक बार पार्स किया जाता है, कंटेंट-हैश होने पर एक वर्ष के लिए कैश किया जाता है, और हर रूट और हर नेविगेशन में साझा किया जाता है।
- केवल उन नियमों को इनलाइन करें जिनकी पहले व्यूपोर्ट को आवश्यकता है; बाकी को एक नॉन-ब्लॉकिंग
<link>और एक लंबी कैश नीति के पीछे विलंबित करें। - CSS कस्टम प्रॉपर्टीज प्लस
@layerउन अधिकांश मामलों को कवर करते हैं जिनमें पहले प्रॉप-ड्रिवन स्टाइल इंटरपोलेशन की आवश्यकता होती थी। - वृद्धिशील रूप से, कंपोनेंट दर कंपोनेंट माइग्रेट करें, और औसत के बजाय p75 पेंट टाइमिंग और लॉन्ग टास्क को मापें।
अधिक CSS भेजना एक प्रतिगमन की तरह लगता है जब तक आपको यह एहसास नहीं होता कि आप एक कैश्ड, एक बार पार्स की गई स्टाइलशीट को मेगाबाइट्स JavaScript के लिए बदल रहे हैं जो इसे हर पेज लोड पर पुनर्निर्माण करती है।
यह सबक GitHub से परे अच्छी तरह से सामान्यीकृत होता है। ब्राउज़र में चलने वाला हर एब्स्ट्रैक्शन एक लागत है जो उपयोगकर्ता द्वारा, उनके डिवाइस पर, उनके नेटवर्क पर, हर बार चुकाई जाती है। स्टाइल जनरेशन को बिल्ड टाइम पर ले जाना एक कंपोनेंट-ड्रिवन फ्रंटएंड के लिए उपलब्ध सबसे उच्च-लाभ वाले ट्रेडों में से एक है, आप एक स्टैटिक फ़ाइल भेजते हैं जिसे ब्राउज़र पहले से जानता है कि कैसे संभालना है, और आप उस मशीनरी को हटा देते हैं जो इसके रास्ते में आती थी।
