CSS Modules, Tailwind ve Vanilla CSS Performansı Karşılaştırması
CSS yazım biçimi performansı etkiler mi? Soru doğru sorulduğunda cevap hem evet hem hayır. Hangi yaklaşımı tercih ettiğiniz, tarayıcıya son olarak teslim edilen CSS dosyasının boyutunu, yapısını ve önbelleğin davranışını şekillendirir; ancak bu etki her proje için aynı ölçüde belirgin değildir. Tailwind'in utility-first felsefesiyle CSS Modules'ün bileşen izolasyonu ve Vanilla CSS'in sıfır araç yükü, farklı senaryolarda farklı trade-off'lar üretir.
Asıl soru şu: Hangi CSS yaklaşımı, production'da tarayıcıya daha az iş bırakır ve ilk yüklemede kritik yolu daha kısa tutar? Bu sorunun yanıtı tek bir yaklaşımı işaret etmiyor; proje ölçeğine, bileşen mimarisine ve bakım önceliklerine göre değişiyor. Ama bazı mekanizmalar ölçülebilir farklar üretiyor ve bu farkların nereden geldiğini anlamak doğru tercihi kolaylaştırır.
Üç yaklaşımın da olgunlaşmış ekosistemi var ve her biri büyük ölçekli üretim projelerinde kullanılıyor. Performans perspektifinden değerlendirmek için iki farklı ölçeğe bakmak gerekir: build maliyeti ve kullanıcıya teslim edilen son çıktının kalitesi. İkincisi doğrudan ölçülebilir ve kullanıcı deneyimini etkiler; asıl odak burasıdır. Tarayıcıya ulaşan CSS'in boyutu, selector karmaşıklığı ve önbellekleme davranışı bu üç yaklaşımda belirgin biçimde farklılaşır.
Production CSS boyutu: Tailwind'in purge mekanizması ve sınırları
Tailwind, geliştirme ortamında tüm yardımcı sınıfları içeren tam bir CSS dosyası üretir; bu dosya sıkıştırılmadan birkaç MB'a ulaşabilir. Production build sırasında devreye giren purge — ya da modern Tailwind'de JIT modu — yalnızca kullanılan sınıfları çıktıya dahil eder. Doğru yapılandırılmış bir Tailwind projesi genellikle 5–20 kB aralığında sıkıştırılmış CSS üretir. Bu rakam, büyük bileşen tabanlı projelerin ürettiği CSS boyutlarıyla kıyaslandığında oldukça rekabetlidir.
Ancak purge mekanizması statik analiz üzerinde çalışır. Dinamik olarak oluşturulan sınıf adları — JavaScript ile birleştirilen string ifadeler, template literal içindeki koşullu sınıflar — purge tarafından görülmez ve çıktıdan düşülür. Pratikte bu durum, geliştirme ortamında çalışan stillerin production'da kaybolmasına yol açabilir. Çözüm genellikle safelist yapılandırmasıdır; ancak safelist gereksiz büyümesiyle birlikte output boyutunu düzensiz olarak artırır.
CSS Modules yaklaşımında ise dosya boyutu bileşen sayısıyla doğrusal bir ilişki içindedir. Her bileşen kendi CSS dosyasını taşır. Build sırasında bu dosyalar bir araya getirilir; tekrarlanan kurallar otomatik olarak birleştirilmez. Büyük bir uygulamada — örneğin yüzlerce bileşen içeren bir dashboard — CSS Modules çıktısı Tailwind'in purge edilmiş çıktısından belirgin biçimde büyük olabilir. Vanilla CSS'te ise boyut tamamen yazım disiplinine bağlıdır. İyi organize edilmiş bir Vanilla CSS tabanı küçük kalabilir; sistematik temizlik yapılmadan büyüyen eski projelerde yüzlerce kullanılmayan kural birikmesi olağdışı değildir.
Parse maliyeti ve critical path üzerindeki etki
Tarayıcı bir CSS dosyasını aldığında ayrıştırır, CSSOM ağacını oluşturur ve render'ı bekletir. Bu süreç render engelleyen kaynaklar konusunun özüdür. Dosya küçüksı parse süresi milisaniyelerle ölçülür; büyük ve karmaşık bir dosyada bu süre onlarca milisaniyeye uzayabilir ve LCP üzerinde doğrudan baskı oluşturur.
Tailwind'ın utility sınıfları, selector karmaşıklığı açısından son derece düzdür. Her kural genellikle tek bir özellik içerir: .flex { display: flex }, .mt-4 { margin-top: 1rem }. Bu basitlik CSSOM oluşturmayı hızlandırır; tarayıcının stil hesaplama aşamasında eşleşen kuralları bulmak daha az iş gerektirir. Vanilla CSS'te de bu avantaj yakalanabilir — özellikle gereksiz specificity yükseltilmemiş, selector zincirleri kısa tutulmuşsa. CSS Modules ise içerik açısından Vanilla CSS'e benzer; fark yalnızca derleme sırasında sınıf adlarının hash'lenmesidir, runtime'da tarayıcı açısından herhangi bir ayrımı yoktur.
Critical CSS çıkarma araçlarıyla çalışırken yaklaşımlar arasındaki fark daha belirgin hale gelir. Vanilla CSS ve CSS Modules'te hangi kuralların ilk görünür içerik için gerekli olduğunu tespit etmek görece kolaydır; araçlar selector eşleşmesi yaparak kritik alt kümeyi ayırabilir. Tailwind'de utility sınıflarının büyük bölümü HTML'de bulunduğundan, critical CSS çıkarmak zaten sınırlandırılmış bir dosya üzerinde çalışmak anlamına gelir ve çoğu durumda ek bir kritik alt küme ayırmaya gerek kalmaz.
Build çıktısı ve önbellekleme stratejisi açısından yapısal fark
Vanilla CSS ile yazılan projeler genellikle az sayıda dosya üretir. Bu dosyalar değişmediçe uzun süreli önbelleğe alınabilir. Bir sayfaya ait tüm stiller tek bir style.css içinde toplandığında, değişiklik olmayan sayfalarda bu dosya tarayıcıdan hiç çekilmez — tarayıcı önbellekleme açısından ideal bir profil oluşur. Öte yandan bu dosyanın herhangi bir bölümü değiştiğinde tüm dosyanın yeniden indirilmesi gerekir.
CSS Modules build çıktısında pratikte benzer bir yapı oluşur; araçlar tüm modülleri tek bir chunk'ta birleştirir. Bileşen bazlı bölünme yapılandırmayla mümkündür ancak varsayılan değildir. Tailwind'in tek dosya çıktısı da benzer şekilde önbelleğe alınabilir; ancak içerik hash'i Tailwind projesinde değişmediği sürece sabit kalır ve bu, sık yapılan küçük HTML değişikliklerinin CSS önbelleğini bozmadığı anlamına gelir.
Versioned asset stratejisi açısından değerlendirildiğinde her üç yaklaşım da content hash ile çalışabilir. Webpack ve Vite her ikisinde de output dosyalarını hash'leyebilir. Buradaki asıl fark, hangi değişikliklerin CSS hash'ini değiştirdiğidir. Tailwind'de yalnızca yeni utility sınıflarının eklenmesi veya Tailwind yapılandırmasının değişmesi hash'i etkiler; mevcut HTML bileşenlerinde sınıf ekleme veya çıkarma CSS çıktısını küçük ölçüde değiştirir. CSS Modules veya Vanilla CSS'te ise stil dosyasındaki herhangi bir değişiklik doğrudan hash değişimine yol açar.
Bileşen izolasyonu ile global çakışma riski
CSS'in doğal yapısı globaldir. Bir selector tanımladığınızda, eğer kapsam sınırlaması yoksa bu kural sayfanın tamamında eşleşme potansiyeli taşır. Büyük projelerde bu durum beklenmedik stil çakışmalarına ve bakımı güleşen bir kod tabanına dönüşür. CSS Modules bu soruna derleme zamanında bir çözüm getirir: her sınıf adı, ilgili bileşene özgü bir hash ile yeniden adlandırılır. İki farklı bileşenin aynı sınıf adını kullanması artık global çakışmaya yol açmaz.
Bu izolasyonun performansla doğrudan bir bağlantısı vardır. Çakışma riski taşıyan büyük projelerde geliştirici savunma içgüdüsüyle specificity'yi yükseltir: uzun selector zincirleri, !important kullanımı, iç içe geçmiş kurallar. Bu alışkanlıklar CSSOM'u karmaşıklaştırır ve tarayıcının stil hesaplama maliyetini artırır. CSS Modules'ün derleme zamanı izolasyonu bu baskıyı azaltır; geliştirici sınıf adı çakışmasından korkmadan düz, özgüllüğü düşük kurallar yazabilir. Sonuç olarak bileşen başına üretilen CSS genellikle daha sade ve hesaplanması daha ucuz olur.
Tailwind bu sorunu farklı bir yolla çözer. Utility sınıfları zaten atomik ve tek amaçlıdır; .flex her zaman display: flex demektir, başka bir anlam taşımaz. Bileşenler arasında çakışma yaşanmaz çünkü hiçbir bileşen kendine özgü bir selektor tanımlamaz. Özelleştirilmiş stiller için @apply direktifi veya özel bileşen sınıfları gerektiğinde, global çakışma riski tekrar devreye girer.
Vanilla CSS'te bu sorun tamamen geliştiricinin disiplinine bağlıdır. BEM metodolojisi, utility-first yaklaşım ya da CSS custom properties ile kapsam landırma bu riski azaltabilir. Ancak araç desteği olmadan büyük ekiplerde tutarlılığı sürdürmek güçtür. Kullanılmayan CSS temizleme araçları da Vanilla CSS projelerinde düzensiz olarak devreye alınırsa, zamanla ölü kurallar birikir ve dosya boyutu şişer.
Runtime performansı: hangi yaklaşım tarayıcıya ne kadar iş bırakır?
CSS-in-JS çözümleri — styled-components, Emotion gibi kütüphaneler — bu karşılaştırmanın dışında tutulmuştur; ancak zıt bir referans noktası olarak anmak gerekir. Bu kütüphaneler stilleri runtime'da oluşturur ve tarayıcının JavaScript'i çalıştırmasını, stil bloklarını dinamik olarak DOM'a eklemesini zorunlu kılar. Bu yaklaşım, statik CSS dosyalarına kıyasla ölçülebilir bir runtime yük getirir ve özellikle ilk render üzerindeki etkisi belirgindir. Bu bağlamda JavaScript parse ve execute maliyeti doğrudan devreye girer; kütüphane kodunun bundle'a eklediği ağırlık ve runtime'daki stil üretimi özellikle düşük güçlü cihazlarda main thread'i zorlayabilir.
CSS Modules, Tailwind ve Vanilla CSS'ın üçü de statik CSS dosyaları üretir. Tarayıcı açısından runtime'da herhangi bir fark yoktur; hepsi aynı parse ve CSSOM oluşturma sürecinden geçer. Fark yalnızca teslim edilen dosyanın boyutunda ve yapısındadır. Tailwind çıktısının düz ve tekrarlayan selector yapısı, style hesaplama aşamasında hafif bir avantaj sağlayabilir; ancak bu avantaj pratikte Core Web Vitals ölçümlerinde nadiren belirgin bir fark yaratır. Asıl etki çıktı boyutundan ve dosyanın önbellekleme profilinden gelir.
Hangi yaklaşımın seçileceği, sonuç olarak tek bir performans metriğiyle değil; proje büyüklüğü, ekip boyutu, bileşen mimarisinin karmaşıklığı ve bakım öncelikleriyle birlikte değerlendirilmelidir. Küçük ve içerik ağırlıklı bir sitede iyi yazılmış Vanilla CSS, herhangi bir araçtan daha az dosya boyutu ve daha kolay önbellekleme profili üretebilir. Yüzlerce bileşeni olan bir uygulama için Tailwind'in purge edilmiş çıktısının kompaktlığı rekabetçi bir avantaj sunar. CSS Modules ise bileşen izolasyonunu ön planda tutan, orta ve büyük ölçekli projelerde hem yönetilebilirliği hem de makul bir performans profilini bir arada sunar.
Üç yaklaşımın ortak paydaşı şudur: production'a gönderilen CSS dosyası ne kadar küçük ve ne kadar düz selektor içeriyorsa, tarayıcının critical path üzerindeki yükü o kadar hafiftir. Bu hedefe hangi araçla ulaştığınız, son kullanıcıya teslim edilen dosyanın kalitesinden daha az önemlidir.