DOM Boyutu Fazla Olursa Ne Olur?
Bir tablo bileşeni düşünün: 100 satır, her satırda 8 sütun, her hücrede metin ve bir simge. Bu tek bileşen 800'den fazla DOM düğümü üretir. Buna sayfa başlığı, gezinti menüsü, filtre paneli, sayfalama kontrolü ve birkaç açılır bileşen eklenince toplam 1.500'ü aşmak için özel bir çaba gerekmez. Sonsuz kaydırma uygulayan bir içerik akışı ise kullanıcı sayfayı indirdikçe bu sayıyı durmaksızın artırır; kaldırma mekanizması yoksa DOM büyümeye devam eder.
Bu büyüklük soyut bir sayı değildir. Tarayıcı, her rendering döngüsünde sayfadaki öğelerin tamamını hesap kapsamına dahil etmek zorundadır. CSS stillerini hesaplamak, düzeni belirlemek ve boyama komutları üretmek — bu üç aşamanın her biri DOM ağacının boyutundan doğrudan etkilenir. Ağaç büyüdükçe bu aşamaların süresi uzar. Küçük bir sayfada bu fark ihmal edilebilirdir; ancak birkaç bin öğe barındıran bir uygulamada her etkileşim, her kaydırma, her durum güncellemesi birikimli bir hesap maliyetiyle yürütülür.
Sorunun özelliği, uzun süre fark edilmemesidir. Geliştirici ortamında güçlü donanım bu yükü kolayca kaldırır; sayfa akıcı görünür. Gerçek kullanıcılar orta düzey mobil cihazlarda sayfayı açtığında ise tablo farklılaşır. PageSpeed Insights veya Lighthouse raporunda "Avoid an excessive DOM size" uyarısı göründüğünde, sayfa tek bir yavaş kaynak nedeniyle değil, her hesabın biraz daha uzun sürmesi nedeniyle geride kalmaktadır.
Bin öğeye ulaşmak sandığınızdan kolaydır
Modern web uygulamalarında DOM büyümesi genellikle fark edilmeden gerçekleşir. Her bileşen kendi başına makul görünür ama bir araya gelince birleşik etki hızla ağırlaşır.
Tipik bir e-ticaret ürün listesi sayfasını düşünün. Üst gezinti 50–80 öğe içerir; her ürün kartı resim, başlık, fiyat, değerlendirme yıldızları, stok etiketi ve iki butonla birlikte 15–20 öğeye ulaşır. Sayfa başına 24 kart gösterilirse bu bölüm tek başına 360–480 öğe üretir. Filtre paneli, altbilgi, bildirim alanı ve üçüncü parti sohbet widget'ı hesaba katıldığında 1.200'ü aşmak sıradan bir sonuç haline gelir.
"Div soup" sorunu bu büyümeye sessizce katkı sağlar. Stil düzenlemek için eklenen her ekstra sarmalayıcı <div>, aslında bir satır CSS ile çözülebilecek bir sorun için açılan yeni bir DOM düğümüdür. Büyük bileşen kütüphanelerinin bir kısmı da bu örüntüye dahil olabilir: bazı bileşenler görsel çıktı için beklenenden çok daha derin bir ağaç üretir. Bu sarmalayıcı katmanları silmek, bileşen sayısını azaltmadan DOM boyutunu düşürmek için en hızlı yollardan biridir.
Sonsuz kaydırma ise farklı bir dinamik taşır. Kullanıcı aşağı indikçe yeni öğeler DOM'a eklenir; görünüm dışına çıkan eski öğeler kaldırılmazsa DOM sürekli büyür. 200 öğeyle başlayan bir liste birkaç kaydırma hamlesi sonunda 2.000'e ulaşabilir. Bu noktada tarayıcı yalnızca ekranda görünen içeriği değil, sayfanın tamamını kendi hesap kapsamında tutar.
Büyük DOM'da style hesabı neden maliyetli hale gelir
Tarayıcı, sayfa ilk yüklendiğinde ve bir DOM değişikliği olduğunda CSS stillerini tüm öğelere uygular. Bu süreç "Recalculate Style" adıyla anılır ve her CSS seçicinin her DOM öğesiyle eşleştirilmesini kapsar.
Seçici eşleştirme sağdan sola çalışır. .kart .baslik span gibi bir seçici için tarayıcı önce tüm span öğelerini bulur, ardından her birinin bir .baslik içinde olup olmadığını kontrol eder, sonra bunun bir .kart içinde yer alıp almadığını doğrular. Sayfada 2.000 öğe varsa bu süreç binlerce eşleştirme işlemini kapsar. Karmaşık, derin iç içe seçiciler bu maliyeti katlar; hem her öğe için daha fazla adım atılır hem de eşleştirme başarısız olmadan önce daha uzun bir arama yapılır.
Bir style hesabının süresi küçük bir sayfada ölçülebilir bir iz bırakmayabilir. Ama her etkileşim potansiyel olarak yeni bir style hesabı tetikler: bir açılır menü açılır, bir form alanı odaklanır, bir CSS sınıfı JavaScript aracılığıyla eklenir. Bu olayların her biri sayfadaki tüm öğelerin style hesabını yeniden başlatabilir. DOM büyüdükçe bu hesapların kümülatif süresi uzar ve main thread üzerindeki baskı birikir. Bir düğmeye tıklamak yalnızca o düğmenin stilini değil, tarayıcının kararına bağlı olarak sayfanın tamamının stilini yeniden değerlendirmesini tetikleyebilir.
Layout ve paint aşamalarında DOM boyutunun izleri
Style hesabı tamamlandıktan sonra layout aşaması başlar: her öğenin boyutu ve konumu piksel düzeyinde hesaplanır. Reflow ve repaint maliyetinin DOM boyutuyla doğrudan ilişkisi bu aşamada somutlaşır.
Layout hesabı öğe sayısıyla birebir orantılı değildir; bağımlılık zincirinin uzunluğu ve karmaşıklığı belirleyicidir. Ama DOM büyüdükçe bu zincirler de uzar. Bir öğenin boyutu değiştiğinde bu değişiklik parent container'a, container'ın kardeş öğelerine ve onların alt öğelerine yayılabilir. 500 öğelik bir ağaçta bu yayılım sınırlı kalır; 3.000 öğelik bir ağaçta aynı değişiklik çok daha geniş bir hesap alanını kapsayabilir. Özellikle flex ve grid düzeni kullanan büyük ağaçlarda tek bir boyut değişikliği, container'ın tüm alt öğelerinin yeniden hesaplanmasını tetikleyebilir.
Paint aşamasında da benzer bir ilişki geçerlidir. Tarayıcı, hangi alanın yeniden boyanması gerektiğini belirlerken tüm render ağacını referans alır; bu ağacın büyüklüğü bu belirlemenin kapsamını etkiler. Büyük DOM'larda paint invalidation hesabı daha fazla düğümü tarar ve bu taramanın kendisi de ölçülebilir süre tüketir.
INP metriği bu süreçleri doğrudan yansıtır: kullanıcı bir etkileşim yaptıktan sonra görsel yanıt gelene kadar geçen süre. Büyük DOM, style hesabı ve layout aşamalarını uzattığı için bu süreyi artırır. INP değerinin 200 ms sınırını aşması çoğu zaman uzun layout veya style hesabı görevlerini işaret eder — ve bu görevlerin uzunluğunun yapısal nedenlerinden biri gereğinden büyük DOM olabilir.
Lighthouse'un "Avoid an excessive DOM size" uyarısı ne ölçer
Lighthouse bu uyarıyı üç farklı ölçütü aştığında tetikler. Toplam DOM düğüm sayısı 800'i geçtiğinde uyarı başlar; 1.400'ü geçtiğinde hata seviyesine yükselir. Bunun yanı sıra tek bir öğenin 60'tan fazla doğrudan alt öğesi varsa veya DOM ağacının derinliği 32 katmanı aşıyorsa bunlar da ayrıca işaretlenir.
Bu eşikler kümülatif maliyet hesabına dayanır. 800 düğümün altındaki sayfalar genellikle rendering hesabının anlamlı bir bölümünü DOM büyüklüğünden almaz. 1.400 üzerindeki sayfalarda ise özellikle style hesabı ve layout aşamalarında ölçülebilir süre artışı gözlemlenir. Derinlik ve genişlik uyarıları ayrı bir önemi taşır: 32 katmana ulaşan derin bir ağaç, toplam sayı makul görünse bile seçici eşleştirme maliyetini önemli ölçüde artırabilir.
Sayfanın anlık DOM boyutunu ölçmek için DevTools Console'da şu komutu çalıştırmak yeterlidir:
document.querySelectorAll('*').length
Bu komut sayfanın o anki toplam düğüm sayısını döndürür. Performance panelindeki "Recalculate Style" görevlerinin süresi incelendiğinde DOM boyutunun bu süreye katkısı görünür hale gelir. Lighthouse değerlendirmesi açısından bu uyarı Diagnostic kategorisinde yer alır; puana doğrudan yansımaz. Ancak INP, LCP ve TBT gibi metriklerin yavaş olmasının yapısal nedenini göstermesi bakımından tanısal değeri yüksektir.
DOM boyutunu azaltmanın pratik yolları
DOM boyutunu yönetmenin birkaç farklı düzeyi vardır. En köklü yaklaşım, gereksiz öğeleri hiç üretmemektir.
Gereksiz sarmalayıcı öğeleri temizlemek görece hızlı bir kazanım sağlar. Her ekstra <div> küçük görünür; ama bileşen ağacında yüzlerce kez tekrarlayan gereksiz sarmalayıcılar binlerce öğeye dönüşür. CSS Grid ve Flexbox'ın yaygınlaşmasıyla pek çok düzen sarmalayıcısı artık gereksizdir; aynı görsel sonuç çoğu durumda daha sığ bir DOM ağacıyla elde edilebilir. Bileşen çıktısının kaç düğüm ürettiğini DevTools Elements panelinde kontrol etmek, beklentiden fazla sarmalama katan bileşenleri bulmak için pratik bir yöntemdir.
Uzun listeler ve tekrarlayan kart yapıları için sanal liste tekniği en etkili çözümdür. Bu yaklaşım, yalnızca görünür öğeleri DOM'a ekler; görünüm dışına çıkan öğeleri kaldırır. Kullanıcı kaydırdıkça DOM'daki içerik değişir ama toplam öğe sayısı sabit kalır — genellikle 50 ile 100 arasında. Binlerce satırı olan bir tablo bileşeni, sanal liste kullanıldığında her an yalnızca ekranda görünen birkaç düzine öğeyi barındırır. Kitaplıklara bağımlı kalmak istemeyen ekipler için saf JavaScript'te IntersectionObserver ile benzer bir mekanizma kurulabilir.
Sonsuz kaydırma uygulamalarında DOM temizleme mekanizması yoksa sorun kaçınılmazdır. Yeni içerik eklenirken görünüm dışındaki eski içeriğin DOM'dan kaldırılması, toplam öğe sayısını kontrol altında tutar. Bu yöntem uygulama karmaşıklığı getirir — kaydırma konumunu, öğe yüksekliklerini ve yer tutucu boyutlarını yönetmek gerekir — ama büyük DOM boyutunun uzun vadeli rendering maliyetiyle karşılaştırıldığında yatırıma değer bir tercih olarak öne çıkar.
content-visibility: auto direktifi sanal liste kadar köklü bir çözüm değildir çünkü öğeleri DOM'dan kaldırmaz; yalnızca ekran dışındaki bloklar için style ve layout hesabını erteler. DOM düğüm sayısı değişmez ama bu sayıyı azaltmanın zor olduğu durumlarda — üçüncü parti bileşenler, mimari kısıtlamalar — anlık bir maliyet düşürme aracı olarak değer taşır. İki yöntemi birlikte kullanmak da mümkündür: sanal liste ile DOM boyutunu kontrol altında tutmak, content-visibility ile de görünür listede yer alan ama henüz ekrana girmemiş grupları ertelemek.
DOM boyutu, INP ve diğer metrikler arasındaki bağ
Büyük DOM, tek başına net bir belirti üretmek yerine diğer performans sorunlarını derinleştirir. Uzun style hesabı görevleri main thread'i bloke eder; uzun layout görevleri TBT'yi artırır; her ikisi birden INP'i olumsuz etkiler. Bu sorunlar üst üste bindiğinde köken bulmak güçleşir: bir etkileşim yavaş görünür ama nedenin uzun bir JavaScript bloğu mu, yavaş bir style hesabı mı yoksa geniş bir layout geçişi mi olduğu Performance paneli okunmadan bilinemez.
LCP üzerindeki dolaylı etki de göz ardı edilmemelidir. Sayfa ilk yüklendiğinde tüm DOM oluşturulur ve ilk style hesabı çalışır. Büyük bir DOM bu ilk hesabı uzatır ve sayfanın en büyük içerik öğesinin ekranda görünme süresini geciktirebilir. Basit sayfalarda bu gecikme ölçülemez düzeyde kalır; ama binlerce öğe barındıran ağır uygulamalarda birkaç yüz milisaniyelik bir fark oluşturabilir.
JavaScript taraflı performans da dolaylı biçimde etkilenir. querySelector, querySelectorAll, getElementsByClassName gibi DOM sorgulama yöntemleri büyük ağaçlarda daha uzun sürer. Sayfaya bağlı event listener sayısı artar; event delegation mekanizmaları daha fazla öğeyi kapsar. Her DOM düğümü bellekte yer kapladığından garbage collection baskısı da yükselir; bu baskı belirli eşikleri aştığında tarayıcı duraklamalarına yol açabilir.
DOM boyutu, çoğu performans sorununda olduğu gibi, ölçüldükten sonra anlamlı hale gelir. DevTools Console'da document.querySelectorAll('*').length komutu anlık düğüm sayısını verir. Sayfa başlangıçta 800'ün altındaysa çoğu durumda sorun yoktur; 1.400'ü geçiyorsa Lighthouse uyarısı beklenebilir ve Performance panelinde "Recalculate Style" görevlerinin ne kadar sürdüğü incelenmelidir. Sonsuz kaydırma veya arayüz etkileşimlerinden sonra bu sayıyı tekrar kontrol etmek, büyümenin nerede ve nasıl gerçekleştiğini ortaya koyar.
Pratik öncelik sırası genellikle şöyledir: önce gereksiz sarmalayıcı öğeleri temizlemek, ardından uzun listelerde sanal liste tekniğini uygulamak, son olarak ekran dışı içerik için content-visibility değerlendirmek. Her adım küçük görünür ama yüzlerce öğe barındıran bir bileşen ağacında kümülatif etkisi stil hesabı ve layout görevlerinin süresi olarak ölçüm sonuçlarına yansır. DOM boyutu bir köklü sorun değil, birikimli bir alışkanlık sorunudur — ve bu nedenle çözümü de tek bir değişiklikte değil, süreç boyunca verilen küçük kararlarda gizlidir.