styled-components ve Emotion'ın runtime stil üretimi hydration sürecini, INP'i ve TTFB'yi nasıl etkiler; vanilla-extract ve linaria hangi senaryolarda gerçek bir alternatif sunar.

CSS-in-JS Kütüphaneleri Gerçekte Ne Kadar Performans Maliyeti Çıkarıyor?

CSS-in-JS yaklaşımı, bileşen odaklı geliştirmenin yaygınlaşmasıyla birlikte React ekosisteminin temel bir parçası haline geldi. styled-components ve Emotion, kapsüllenmiş stiller, dinamik temalar ve TypeScript desteğiyle geliştirici deneyimini ciddi ölçüde iyileştirdi. Ancak bu kolaylıkların arka planda tarayıcıya ne kadar iş yüklediği, çoğu zaman proje büyüyene kadar fark edilmiyor.

Runtime CSS-in-JS kütüphaneleri, stilleri JavaScript içinde hesaplayıp CSSOM'a çalışma zamanında enjekte eder. Bu süreç, bileşen ağacı her render olduğunda yeniden işlenir. Statik bir sitede ya da seyrek güncellenen sayfalarda bu maliyet tolere edilebilir düzeyde kalabilirken, yüksek etkileşimli uygulamalarda veya büyük SSR sayfalarında ana iş parçacığı üzerinde ölçülebilir bir yük oluşturur.

vanilla-extract ve linaria gibi zero-runtime alternatifleri, aynı geliştirici deneyimini build aşamasına taşıyarak bu maliyeti ortadan kaldırır. İki yaklaşım arasındaki fark yalnızca bir araç tercihinden ibaret değil; projenizin etkileşim süreleri, hydration stratejisi ve CSS boyutu doğrudan bu seçimden etkilenir.

Runtime kütüphanelerde stil üretimi nasıl işler

styled-components ve Emotion, her bileşen instance'ı için benzersiz sınıf adları üretir ve bu sınıflara ait CSS kurallarını tarayıcının CSSOM'una enjekte eder. Bileşene geçilen prop değiştiğinde yeni bir CSS kuralı hesaplanır, stil etiketi güncellenir. Her adım ana iş parçacığında çalışır ve paralel işlenemez.

İşlem birkaç aşamadan geçer: önce JavaScript'te template literal ya da nesne interpolasyonu değerlendirilir, ardından bir hash algoritmasıyla sınıf adı türetilir, sonra CSSStyleSheet.insertRule ya da benzeri bir mekanizmayla kural CSSOM'a işlenir. Bileşen sayısı onlarla sınırlıyken bu zincir pratikte sorun çıkarmaz. Yüzlerce bileşenin eş zamanlı render edildiği sayfalarda ise her bileşen bu zinciri kendi payına düşen prop kombinasyonları için ayrı ayrı tetikler.

Önemli bir ayrım: Emotion, @emotion/cache katmanıyla aynı stil kuralını ikinci kez enjekte etmez; memoizasyon bu maliyeti kısmen azaltır. styled-components v6 da benzer bir önbellekleme mekanizmasına sahiptir. Ama memoizasyon yalnızca daha önce görülen prop değerlerini kapsar. Dinamik değerler (kullanıcıya özgü renkler, hesaplanan boyutlar) her seferinde yeni bir kural üretir.

SSR ve hydration sırasındaki maliyet: TTFB ve kritik CSS

Sunucu taraflı render (SSR) kullanan uygulamalarda CSS-in-JS kütüphanelerinin getirdiği en büyük yük, hydration sürecinde ortaya çıkar. Sayfa sunucuda render edilirken kütüphane hangi bileşenlerin hangi stillere ihtiyaç duyduğunu hesaplar ve bu stilleri ya inline <style> bloklarına ya da ayrı bir CSS çıktısına dönüştürür. Tarayıcı sayfayı aldığında hem HTML'i parse eder hem de JavaScript bundle yüklenip çalıştığında bu stil hesaplamayı istemcide tekrar yapar.

İkili hesaplama zorunlu. Sunucu ve istemcinin aynı sınıf adlarını üretmesi için kütüphanenin iç sayacı sıfırlanmış olmalı; küçük bir konfigürasyon hatası bileşen sınıf adlarında uyumsuzluğa, dolayısıyla görsel flaşa (FOUC) yol açar. Next.js ile styled-components kullanırken `_document.js` içindeki ServerStyleSheet kurulumu tam olarak bu sorunu çözmek için gereklidir.

TTFB açısından değerlendirildiğinde, sunucudaki CSS hesaplama süresi genellikle milisaniyelerin altında kalır. Asıl sorun kritik CSS boyutudur. Runtime kütüphaneler sayfada görünen her bileşenin stilini inline olarak HTML'e gömer. Sayfa büyüdükçe bu inline CSS bloğu da büyür; HTML boyutunun artması hem ağ transferini hem de parse süresini etkiler. Statik CSS dosyalarında bu içerik ayrı bir dosyada olur, tarayıcı onu önbelleğe alır; inline stillerde her sayfa yüklemesinde aynı veriler yeniden indirilir.

INP üzerindeki etki: etkileşim başına style recalculation

INP (Interaction to Next Paint), bir kullanıcı etkileşiminden sonra tarayıcının bir sonraki frame'i boyamasına kadar geçen süreyi ölçer. Runtime CSS-in-JS'nin INP'e etkisi doğrudan şu zincirden kaynaklanır: kullanıcı bir düğmeye tıklar, React state güncellenir, etkilenen bileşenler yeniden render edilir, kütüphane yeni stilleri hesaplayıp CSSOM'a yazar, tarayıcı style recalculation ve layout işlemlerini çalıştırır, ardından paint gelir.

Bu zincirde her halka ana iş parçacığını kullanır. Özellikle CSSOM'a kural ekleme ve tarayıcının style recalculation aşaması, etkilenen bileşen sayısıyla doğru orantılı olarak büyür. Yoğun tema geçişleri, accordion açma-kapama, filtreleme sonucu çok sayıda kartın yeniden render edilmesi gibi senaryolar bu maliyetin en belirgin olduğu durumlardır.

Ortalama bir e-ticaret ürün listeleme sayfasında onlarca kart bileşeni aynı anda render ediliyorsa ve her kart props bazında farklı stiller alıyorsa, bu maliyet 50-100ms'nin üzerine çıkabilir. INP eşiği 200ms olduğundan bu gecikme tek başına kritik sınıra yaklaştırabilir; başka script maliyetleriyle birleştiğinde sınırı aşar.

Mobil cihazlarda durum daha belirgindir. Düşük uçlu Android cihazlarda JavaScript çalıştırma hızı masaüstüne kıyasla 4-5 kat yavaş olabilir. Runtime CSS-in-JS'nin hesaplama maliyeti bu cihazlarda orantısız biçimde büyür.

vanilla-extract ve linaria: build zamanında CSS üretimi

Zero-runtime yaklaşımının özü şudur: stil hesaplaması tarayıcıya ulaşmadan, build sürecinde tamamlanır. vanilla-extract, TypeScript'le yazılan stil tanımlarını derleme sırasında statik CSS dosyalarına dönüştürür. Linaria ise styled-components benzeri bir sözdizimi kullanır; dinamik olmayan değerleri build aşamasında çözer, dinamik kısımları CSS custom property olarak dışarı açar.

vanilla-extract'ın temel farkı tip güvenliğidir. Stil tanımları .css.ts uzantılı dosyalarda yazılır ve TypeScript derleyicisi bunları doğrular. Runtime'da sıfır JavaScript çalışmaz; tarayıcıya yalnızca sınıf adları ve statik bir CSS dosyası ulaşır. Temasız uygulamalar için bu yaklaşım hem performans hem de bakım açısından güçlüdür.

Linaria biraz farklı bir denge kurar. Dinamik değerleri CSS custom property aracılığıyla işler; bu nedenle "zero-runtime" tanımı teknik olarak "near-zero" şeklinde değerlendirilir. Şöyle düşünün: bir düğmenin rengi kullanıcı tercihine göre değişiyorsa linaria bunu --button-color: red gibi bir inline style değişkeniyle karşılar. Asıl CSS hesaplaması hâlâ build zamanındadır; yalnızca değer atama çalışma zamanına kalır. Bu, JavaScript'te tam stil kuralı hesaplamaktan çok daha ucuz bir işlemdir.

Her iki kütüphane de Webpack, Vite ve esbuild eklentileriyle çalışır. Next.js için vanilla-extract'ın resmi entegrasyonu mevcuttur; linaria ise babel tabanlı dönüşüm gerektirir ve bazı RSC (React Server Component) senaryolarında yapılandırma karmaşıklığı artar.

Ne zaman runtime CSS-in-JS mantıklı, ne zaman değil

Runtime kütüphanelerin gerçek bir avantaj sağladığı durumlar vardır. Kullanıcıya özgü ve önceden bilinemeyen renk paletleri, kullanıcının anlık girişiyle şekillenen stiller veya son derece karmaşık tema sistemleri bunların başında gelir. Bir tasarım aracı, kod editörü ya da canvas tabanlı uygulama gibi sayfada zaten yoğun JavaScript çalışan uygulamalarda CSS-in-JS'nin ek maliyeti genel yük içinde küçük kalır.

Ters etki en sık şu durumda görülür: içerik ağırlıklı, görece statik sayfalarda (blog, dokümantasyon, ürün detay, açılış sayfası) runtime CSS-in-JS kullanmak. Bu sayfalarda dinamik stil ihtiyacı minimumdur; kütüphanenin sağladığı geliştirici kolaylığı runtime maliyetiyle dengelenmez, çünkü bu kolaylık build-time çözümlerle de elde edilebilir.

Eşik noktası genellikle şudur: eğer sayfanızdaki bileşenlerin büyük çoğunluğunun stilleri kullanıcı etkileşimine göre değişmiyorsa, runtime hesaplama gereksiz bir yüktür. Tema değiştirme gibi bir durum bile çoğu zaman CSS custom property ve bir data-attribute ile çözülebilir; bunu için bir runtime kütüphaneye ihtiyaç duyulmaz.

Geçiş maliyeti ve pratik karar kriterleri

Mevcut bir projede styled-components veya Emotion'dan zero-runtime bir alternatife geçmek, sıfırdan başlamaktan farklı bir hesap gerektirir. Her bileşenin yeniden yazılması gerekir; otomatik kod dönüşüm araçları varlıkta bile dinamik prop interpolasyonları elle çözülmelidir. Büyük bir kod tabanında bu iş haftalar alabilir.

Geçiş kararını verirken birkaç soruyu yanıtlamak yeterlidir. Sayfanın INP değeri şu an sorunlu mu? Chrome UX Report veya alan ölçümü bu konuda gerçek veri verir. Hydration sonrası JavaScript zamanlamasında style recalculation ne kadar yer tutuyor? Tarayıcı DevTools'un Performance panelinde "Recalculate Style" satırlarının toplamına bakmak yeterlidir. Projedeki dinamik stil kullanımının oranı ne kadar?

Tam geçiş yerine karma bir strateji de uygulanabilir. Kritik yoldaki bileşenler (header, hero, ürün kartları) zero-runtime kütüphaneye taşınırken yönetim paneli veya az kullanılan özellik sayfaları mevcut kütüphaneyle bırakılabilir. Bu yaklaşım riski azaltır ve performans kazanımını en fazla ihtiyaç duyulan yerde toplar.

Yeni bir proje başlatıyorsanız tercih daha net: eğer dinamik stil ihtiyacı sınırlıysa ya da CSS custom property ile karşılanabiliyorsa, baştan vanilla-extract veya linaria seçmek daha uzun vadeli bir karar olur. Proje ileride büyüdüğünde performans borcuyla yüzleşmek yerine, stiller statik tutulmuş olur.

styled-components ile Emotion arasındaki performans farkı

Her ikisi de runtime kütüphanedir; temel maliyet kaynağı aynıdır. Ancak uygulamalar arasında ölçülebilir farklar vardır. Emotion, @emotion/react ve @emotion/styled paketlerini ayrı tutar; yalnızca css prop kullanılıyorsa styled API'nin maliyeti devre dışı kalır. Bu ayrım bundle boyutunu küçültebilir.

styled-components v6, Emotion'a kıyasla daha hızlı bir hash algoritması ve daha iyi memoizasyon içerdiğini iddia eder. Pratik fark çoğu uygulamada küçüktür; her iki kütüphane de benzer mimari kararları paylaşır. Seçimi performans değil, ekibin alışkanlığı ve ekosistem uyumu belirlemeli; performans kaygısı varsa zero-runtime tarafa geçmek daha anlamlı bir adımdır.

Emotion'un css prop özelliği kullanılırken Babel veya TypeScript dönüşüm eklentisi olmadan çalışması mümkün değildir; bu yapılandırma adımı atlandığında inline stil ya da beklenmedik davranışlarla karşılaşılır. styled-components bu açıdan daha az yapılandırma gerektiren bir API sunar. İkisi arasında karar verirken bu kurulum maliyeti de hesaba katılmalıdır.

CSS-in-JS araçları geliştirici deneyimini gerçekten iyileştirir. Sorun bu iyileştirmenin ücretsiz olmadığını unutmaktır. Runtime kütüphanelerin maliyeti, küçük projelerde veya zaten JavaScript yoğun uygulamalarda kabul edilebilir sınırda kalabilir. Ama içerik ağırlıklı, SSR kullanan veya INP hassasiyeti yüksek projelerde bu maliyet ölçüme yansır.

Araç seçimini kullanım senaryosuna göre yapmak, sonradan performans borcu ödemekten daha az iş gerektirir. vanilla-extract ve linaria, styled-components ile Emotion'un sağladığı yapısal avantajların büyük bölümünü build zamanına taşıyarak sunar; geçiş kararı için gerçek ölçüm verisi en sağlam başlangıç noktasıdır.