Meningkatkan Performa Situs dengan Mengirimkan Lebih Banyak CSS
Ada insting yang tertanam dalam pekerjaan frontend modern yang mengatakan bahwa halaman tercepat mengirimkan sesedikit mungkin dari segala hal: lebih sedikit JavaScript, lebih sedikit font, lebih sedikit permintaan. Jadi ketika tim engineering GitHub menulis tentang meningkatkan performa situs dengan mengirimkan lebih banyak CSS, itu terdengar seperti kontradiksi. Perbaikan untuk antarmuka yang lambat dan berat JavaScript bukanlah dengan mencopot styling dari aplikasi. Perbaikannya adalah memasukkan lebih banyak styling kembali, sebagai stylesheet biasa, statis, dan dapat di-cache, serta menghapus JavaScript yang hanya ada untuk menghasilkan style di browser.
Perbedaan inilah inti seluruh artikel. Mengirimkan "lebih banyak CSS" tidak berarti stylesheet yang lebih besar yang membengkakkan critical path Anda. Artinya memindahkan pembuatan style keluar dari runtime dan ke dalam proses build, lalu membiarkan browser melakukan apa yang benar-benar dikuasainya: mem-parsing stylesheet sekali, menyimpannya dalam cache, dan menggunakannya kembali di setiap navigasi.
Biaya Tersembunyi dari Styling Runtime
Library CSS-in-JS memecahkan masalah nyata. Library tersebut memberi penulis komponen scoping, colocation, dan kemampuan untuk menginterpolasi props ke dalam style tanpa menciptakan konvensi penamaan. Tradeoff-nya bersifat arsitektural: library tersebut memindahkan masalah waktu kompilasi ke dalam browser.
Pembuatan style adalah pekerjaan JavaScript
Setiap halaman yang dirender server yang menggunakan library CSS-in-JS runtime meminta browser melakukan pekerjaan yang sama dua kali. Server menyerialkan style ke dalam tag <style>, lalu library klien bangun, menelusuri pohon komponen, melakukan hash nama class lagi, dan merekonsiliasi hasilnya. Pekerjaan itu terjadi di main thread, bersaing dengan langkah layout dan paint yang justru ingin Anda percepat.
// 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;
`;
Komponen yang sama yang diekspresikan sebagai stylesheet waktu build menghilangkan dependensi runtime sekaligus pekerjaan serialisasi:
/* 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} />;
}
Tidak ada yang baru di sini. Yang mudah diremehkan adalah seberapa besar bagian bundle yang diam-diam dimiliki oleh stack CSS-in-JS yang matang: library itu sendiri, utilitas interpolasi prop, penyedia tema, serializer, dan kode framework duplikat yang tumbuh ketika setiap paket bergantung pada salinannya sendiri.
Ia berada langsung di critical rendering path
Sebuah <link rel="stylesheet"> memblokir rendering, tetapi memblokirnya sekali, dan browser dapat mem-parsing file tersebut saat aliran HTML masih datang. CSS runtime bekerja berbeda: browser bahkan tidak dapat mulai membangun style komponen sampai JavaScript telah diunduh, di-parse, dikompilasi, dan dieksekusi. Di ponsel kelas menengah, itu berarti satu detik atau lebih pekerjaan main thread sebelum satu tombol pun mendapatkan warna latarnya.
Ia memecah cache Anda
Nama class yang dihasilkan berubah setiap kali sumber komponen berubah, yang berarti stylesheet menjadi tidak valid pada hampir setiap deploy. Pecah hal itu ke puluhan bundle tingkat route dan Anda mendapatkan cache hit rate yang terlihat sehat di dashboard tetapi jarang terpicu pada kunjungan berulang. Sebaliknya, stylesheet statis dengan hash konten dapat disajikan dengan Cache-Control: public, max-age=31536000, immutable dan secara efektif tidak akan pernah diunduh dua kali.
Ia menggandakan pekerjaan selama hydration
Ketidakcocokan hydration antara style yang disuntikkan server dan yang dihasilkan klien adalah sumber flicker klasik. Browser melukis satu set aturan, lalu library klien menggantinya di tengah interaksi, memaksa perhitungan ulang style dan terkadang pergeseran layout. Nama class statis yang dihitung pada waktu build identik di kedua sisi, sehingga paint pertama juga merupakan paint terakhir.

Apa yang Sebenarnya Diubah GitHub
Migrasi ini paling baik dipahami sebagai lima langkah arsitektural, bukan satu refactor. Tidak satu pun dari langkah tersebut mengharuskan Anda meninggalkan model komponen, dan tidak satu pun mengharuskan penulisan ulang total.

Token desain menjadi properti kustom CSS
Skala warna, spacing, radius, dan tipografi dipindahkan ke blok :root global sebagai properti kustom. Komponen merujuk ke var(--color-bg-default) alih-alih mengimpor objek tema JavaScript. Inilah yang membuat CSS statis cukup ekspresif untuk menggantikan style dinamis dalam sebagian besar kasus, dan memungkinkan tema runtime (mode gelap, kontras tinggi) tanpa JavaScript sama sekali.
Style komponen dikompilasi lebih awal
Setiap komponen mendapatkan stylesheet sungguhan, yang dihasilkan pada waktu build dan di-scope berdasarkan hash atau konvensi modul. Output-nya adalah CSS biasa yang dapat di-parse browser mana pun dengan kecepatan parsing.
Critical CSS di-inline di head dokumen
Hanya aturan yang dibutuhkan untuk viewport pertama yang di-inline ke dalam HTML. Biasanya berukuran 8–14 KB setelah gzip, dan itu berarti paint pertama tidak pernah menunggu perjalanan bolak-balik jaringan untuk stylesheet yang membentuk halaman terlihat.
<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>
Markup yang dirender server membawa nama class final
Karena nama class sudah ada pada waktu build, HTML yang dikembalikan server sudah memiliki style. Tidak ada proses kedua, tidak ada langkah rekonsiliasi, dan tidak ada flash konten tanpa style pada koneksi lambat.
Cascade layer menggantikan hack specificity
Sistem desain bersama hidup atau mati berdasarkan urutan cascade yang dapat diprediksi. Mendeklarasikan urutan layer sekali di awal membuat override deterministik dan menghilangkan dorongan untuk menggunakan !important atau selector yang semakin dalam.
@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); }
}
Angka-Angkanya: Apa yang Berubah Saat Anda Mengirimkan Lebih Banyak CSS
Memindahkan pembuatan style ke waktu build mengubah bentuk dari apa yang Anda kirimkan, bukan hanya volumenya. Byte yang tersisa itu murah: terkompresi dengan baik, dibagikan di setiap route, dan di-cache selama satu tahun.

Hasil arsitekturalnya muncul di empat tempat sekaligus: lebih sedikit JavaScript yang perlu diunduh, lebih sedikit pekerjaan main thread sebelum paint pertama, stylesheet yang dapat di-cache dan dibagikan di seluruh navigasi, serta markup yang identik di server dan klien.
| 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 |
Stylesheet tercepat adalah stylesheet yang di-parse browser sekali, di-cache selama satu tahun, dan tidak pernah harus dibuat ulang.
Playbook Migrasi yang Praktis
Anda tidak memerlukan basis kode GitHub untuk menerapkan penalaran yang sama. Migrasinya bertahap dan setiap langkah dapat dikirim secara independen.

1. Inventarisasi apa yang benar-benar dirender saat runtime
Buka DevTools, muat halaman, dan hitung document.styleSheets. Jika Anda menemukan puluhan tag <style> yang disuntikkan dengan nama class ber-hash, Anda telah menemukan target Anda. Cocokkan dengan panel Performance: cari tugas panjang antara DOMContentLoaded dan first contentful paint.
2. Ekstrak CSS above-the-fold
Mulailah dengan blok inline sekecil mungkin. Heuristik yang andal adalah meng-inline reset, token, dan aturan yang digunakan oleh dua tinggi viewport pertama. Semua yang berada di bawah lipatan dimuat dari stylesheet yang ditunda.
3. Ubah komponen runtime menjadi class statis
Kerjakan komponen demi komponen, dimulai dari yang paling sering digunakan. Varian yang digerakkan prop menjadi class modifier atau properti kustom yang diatur pada wrapper. Pertahankan API komponen tetap identik agar lokasi pemanggilan tidak berubah.
// 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;
}
Pola ini adalah andalan seluruh migrasi: struktur style bersifat statis dan dapat di-cache, sementara nilainya tetap dinamis dan tidak memakan biaya untuk diperbarui.
4. Tetapkan urutan layer lebih awal
Deklarasikan urutan @layer di bagian atas stylesheet entri Anda sebelum Anda memiliki masalah specificity, bukan sesudahnya. Menambahkan layer secara retroaktif ke cascade lama jauh lebih menyakitkan daripada memulainya dengan layer.
5. Hash nama file dan cache secara immutableHashing konten ditambah immutable adalah yang mengubah "lebih banyak CSS" menjadi "CSS gratis". Tampilan halaman kedua dalam satu sesi harus mengenai cache HTTP untuk setiap aturan gaya di situs.
6. Pertahankan gaya yang benar-benar dinamis tetap dinamis
Transformasi, posisi gulir, dan nilai yang digerakkan oleh pengukuran termasuk dalam gaya inline, requestAnimationFrame, atau Web Animations API. Jangan memperkenalkan kembali mesin gaya runtime untuk menyelesaikan masalah yang sudah dipecahkan oleh properti kustom CSS.
Cara Mengukur Peningkatan
Ukur tiga hal yang sama sebelum dan sesudah: waktu paint, tugas panjang thread utama, dan jumlah stylesheet. Skrip PerformanceObserver kecil memberi Anda ketiganya tanpa menambahkan ketergantungan pemantauan.
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);
Harapkan pergerakan terbesar dalam waktu hingga paint pertama pada kunjungan berulang dan pada profil seluler yang dibatasi, di mana biaya parsing JavaScript yang dihilangkan mendominasi. Largest contentful paint cenderung meningkat lebih bertahap, rilis demi rilis.

Jebakan yang Perlu Dihindari
Pendekatan ini tidak bebas risiko, dan sebagian besar kegagalan berasal dari penerapannya yang terlalu agresif.

- **CSS kritis yang tidak lengkap.**Jika Anda salah menebak viewport pertama, Anda akan mendapatkan reflow yang terlihat saat lembar yang ditangguhkan tiba. Hasilkan blok kritis secara otomatis dari render nyata, bukan dengan tangan. -**Menanamkan semuanya.**Menanamkan stylesheet lengkap ke dalam setiap respons HTML mengalahkan caching dan memperbesar ukuran dokumen. Tanamkan yang minimum, tunda sisanya. -**Naluri menggabungkan semuanya.**Saran lama untuk menggabungkan semua CSS menjadi satu file raksasa mendahului HTTP/2. Memisahkan berdasarkan rute seringkali lebih baik sekarang, selama primitif bersama di-cache secara terpisah. -Mencampur dua sistem. Menjalankan runtime CSS-in-JS dan stylesheet statis berdampingan terlalu lama akan menggandakan permukaan gaya Anda dan membuat debugging kaskade menjadi menyebalkan. Tetapkan tenggat waktu migrasi per direktori komponen.
Jebakan non-teknis yang paling umum adalah memperlakukan ini sebagai penulisan ulang besar-besaran. Tim yang berhasil mengonversi segelintir komponen dengan lalu lintas tinggi, mengukur delta, dan menggunakan data itu untuk membenarkan batch berikutnya.
Menggunakan DivMagic untuk Rekayasa Balik Arsitektur CSS Nyata
Salah satu bagian yang diremehkan dari migrasi ini adalah melihat bagaimana situs produksi yang matang benar-benar menyusun stylesheet mereka. Saat Anda memeriksa situs yang mengirimkan CSS statis dengan baik, Anda dapat belajar lebih banyak dalam lima menit daripada satu jam membaca dokumentasi: bagaimana mereka melapisi kaskade, bagaimana mereka membatasi token, bagaimana mereka memisahkan kritis dari yang ditangguhkan, dan bagaimana mereka menamai pengubah.
DivMagic dibangun untuk alur kerja itu. Alih-alih menyalin gaya elemen dari DevTools satu per satu aturan, Anda memilih UI apa pun di halaman mana pun dan menarik HTML dan CSS yang dihasilkan langsung ke editor Anda, siap untuk diadaptasi. Itu benar-benar berguna dalam dua situasi: membuat prototipe komponen dari desain referensi, dan mempelajari bagaimana situs berkinerja tinggi yang Anda kagumi mengatur kaskadenya sehingga Anda dapat mencerminkan pola tersebut dalam sistem desain Anda sendiri.
Ini juga mempersingkat bagian membosankan dari langkah ketiga di atas. Saat Anda mengonversi komponen runtime menjadi kelas statis, memiliki gaya akhir yang dihitung dalam bentuk yang dapat diedit, daripada sebagai gaya inline yang tersebar dan blok <style> yang disuntikkan, menghilangkan sebagian besar pekerjaan transkripsi.
Poin Penting
- Pembuatan gaya runtime adalah pekerjaan JavaScript thread utama yang terjadi setelah unduhan JavaScript, langsung di jalur rendering kritis Anda.
- CSS statis diurai sekali, di-cache selama setahun ketika di-hash konten, dan dibagikan di setiap rute dan setiap navigasi.
- Tanamkan hanya aturan yang dibutuhkan viewport pertama; tunda sisanya di belakang
<link>yang tidak memblokir dan kebijakan cache yang panjang. - Properti kustom CSS ditambah
@layermencakup sebagian besar kasus yang dulu memerlukan interpolasi gaya berbasis prop. - Migrasikan secara bertahap, komponen demi komponen, dan ukur waktu paint p75 dan tugas panjang daripada rata-rata.
Mengirimkan lebih banyak CSS terdengar seperti kemunduran sampai Anda menyadari bahwa Anda menukar stylesheet yang di-cache dan diurai sekali dengan megabita JavaScript yang membangunnya kembali pada setiap pemuatan halaman.
Pelajaran ini berlaku secara umum jauh melampaui GitHub. Setiap abstraksi yang berjalan di browser adalah biaya yang dibayar oleh pengguna, di perangkat mereka, di jaringan mereka, setiap saat. Memindahkan pembuatan gaya ke waktu build adalah salah satu pertukaran leverage tertinggi yang tersedia untuk frontend berbasis komponen, Anda mengirimkan file statis yang sudah diketahui cara menanganinya oleh browser, dan Anda menghapus mesin yang dulu menghalangi jalannya.
