CLS (Cumulative Layout Shift) kullanıcı’nın page okurken “bu content neden hareket etti?” hissi. Tıklamaya gelirken buton aşağı kayması. Article okurken ad yüklenip content’i aşağı itmesi. Frustrating, trust-eroding.
Google Core Web Vitals’te measurable metric. 0.1 altı “good”, 0.25 üstü “poor”. SEO impact var.
Bu yazıda CLS’i kaynaklandıran 7 ana sebep ve her birini nasıl fix edeceğinizi anlatacağım.
CLS nedir, nasıl hesaplanıyor?
Formula: Impact fraction × Distance fraction = Layout shift score
- Impact fraction: Viewport’un kaç %’si shift etti?
- Distance fraction: Element viewport yüksekliğinin kaç %’sini hareket etti?
Cumulative: page load boyunca tüm shift’ler toplamı.
Example:
– Element viewport’un %50’sini kaplıyor, %25 aşağı kaydı
– Layout shift: 0.5 * 0.25 = 0.125
0.125 “needs improvement” range.
Kaynak 1: Images without dimensions
En yaygın CLS sebebi. Image yükleniyor, space allocate ediliyor, content aşağı kayıyor.
Bad:
<img src="hero.jpg" alt="...">Browser image yüklenene kadar 0 height. Image load olunca expand, shift.
Good:
<img src="hero.jpg" width="800" height="400" alt="...">Browser dimensions’ı görüyor, space pre-allocate. Image yüklense de boşluk değişmiyor.
CSS responsive:
img {
width: 100%;
height: auto;
aspect-ratio: 2 / 1;
}aspect-ratio modern browsers’da widely supported. Dimensions’ı maintain.
Kaynak 2: Web fonts (FOUT)
Web font yükleniyor, fallback font’tan custom font’a değişiyor. Font metrics farklı = text reflow.
Problem:
– Fallback font daha dar, text 3 satır
– Custom font daha geniş, text 4 satır
– Reflow sırasında below content aşağı kayıyor
Solutions:
1. font-display: optional
@font-face{
font-family: 'Custom';
src: url('custom.woff2');
font-display:swap;
}Custom font 100ms içinde gelmezse, asla yüklenmiyor. No swap, no shift. UX compromise (bazen custom font görünmüyor).
2. size-adjust CSS (newer browsers):
@font-face{ font-display:swap;
font-family: 'Custom';
src: url('custom.woff2');
size-adjust: 95%;
ascent-override: 90%;
descent-override: 20%;
line-gap-override: 0%;
}Fallback font metrics’ini custom font’a match. Swap’ta shift minimal.
3. Preload critical fonts
<link rel="preload" href="/fonts/custom.woff2" as="font" type="font/woff2" crossorigin>Critical font earlier load, swap time kısa.
Kaynak 3: Ads ve iframe’ler
Reklam unit’leri unknown size. Yüklendiğinde space açıyor, content aşağı itiliyor.
Solution: Reserve space.
.ad-slot {
min-height: 250px; /* Expected ad height */
min-width: 300px;
}HTML:
<div class="ad-slot">
<!-- Ad script injects here -->
</div>Ad yüklenmese bile space reserved. Shift yok.
Multiple ad sizes: Ad network’un multiple size dönme ihtimali varsa, average size’ı min-height olarak ayarla. Smaller ads extra whitespace leaves, larger shift prevents.
Kaynak 4: Dynamically injected content
User scroll ediyor, banner injection, cookie consent popup, newsletter subscribe. Yeni content DOM’a ekleniyor = existing content shift.
Rules:
- Never inject above existing content. New content always below or overlay.
- User interaction triggered. “Close” butonuna bastığında shift OK (CLS’e dahil değil 500ms window).
- Overlay/modal pattern. Content existing layout’u değil, üzerine lay.
.modal {
position: fixed;
top: 0;
left: 0;
right: 0;
bottom: 0;
z-index: 1000;
}Fixed positioning, flow etkilemez.
Kaynak 5: CSS animations
Bad CSS animations layout trigger eden property’leri animate ediyor:
.el {
transition: top 0.3s, left 0.3s, width 0.3s, height 0.3s;
}Bu property değişikliği layout recalc tetikliyor. Paint + composite expensive.
Good:
.el {
transition: transform 0.3s, opacity 0.3s;
}Transform ve opacity compositor-only. GPU-accelerated. No layout reflow.
CLS’e contribute etmiyor (aynı coord’da kalıyor visually), genel performance iyileşiyor.
Kaynak 6: Embed’ler (video, tweet, etc.)
YouTube embed, Twitter embed, Instagram embed. Her biri async loading, unknown initial size.
Solution: Explicit dimensions veya aspect ratio:
<div style="aspect-ratio: 16/9; background: #eee;">
<iframe src="https://youtube.com/embed/..." width="100%" height="100%"></iframe>
</div>Video 16:9, container aspect-ratio ile pre-allocate.
Twitter/Instagram widget’ları için reasonable min-height.
Kaynak 7: Skeleton loaders done wrong
Skeleton loader pattern trendy. Ama doğru implement edilmezse CLS yaratıyor.
Bad skeleton:
<div class="loading">
<div class="spinner"></div>
</div>
<!-- User data loads, replaces with -->
<div class="user-profile">
<h1>...</h1>
<img ...>
<p>...</p>
</div>Spinner size ≠ profile size. Huge shift.
Good skeleton:
<div class="skeleton">
<div class="skeleton-title"></div>
<div class="skeleton-avatar"></div>
<div class="skeleton-text"></div>
<div class="skeleton-text"></div>
</div>.skeleton-title { height: 30px; width: 80%; background: #eee; }
.skeleton-avatar { width: 60px; height: 60px; border-radius: 50%; background: #eee; }
.skeleton-text { height: 20px; width: 100%; background: #eee; }Skeleton actual layout’a benziyor. Data load sırasında minimal shift.
Measuring CLS
Lab tools:
– Lighthouse (single page)
– PageSpeed Insights
– WebPageTest
Field tools:
– Chrome DevTools Performance Insights
– Google Search Console Core Web Vitals
– web-vitals library (custom analytics)
import {onCLS} from 'web-vitals';
onCLS((metric) => {
console.log('CLS:', metric.value);
// Send to analytics
gtag('event', 'web_vital', {
metric_name: 'CLS',
value: Math.round(metric.value * 1000)
});
});Field data production’da gerçek user deneyimini yansıtıyor.
Debugging process
CLS issue debug:
- Chrome DevTools Performance tab. Recording yap. “Experience” track’inde layout shift events.
- Layout Shift Regions visualize. DevTools Rendering > Layout Shift Regions. Shift oluyor, kırmızı flash.
- Identify shifting element. DevTools shift nodes’u gösteriyor. Hangi element hareket ediyor?
- Root cause find. Why shifting? Image dim missing? Font loaded? Ad injected?
- Apply fix.
- Verify. Re-measure. CLS düştü mü?
WordPress-specific
WordPress’te CLS common sources:
1. Plugin ad’s. Gereksiz plugin’lerin injection’ı.
2. Theme slider. Slider JS late loading, space reservation yok.
3. Lazy loaded images. WP 5.5+ lazy load default. Hero image için kapatın.
4. Font loading. Google Fonts direct link = external request. Self-host optimize.
5. Comment forms. Heavy comment forms below-fold genişleyebilir.
Optimization strategies aynı, context WP-specific.
CLS vs interactivity
Bazen CLS “accepted” tolerance içinde. User-initiated shifts OK:
- Accordion expand
- Tab switch
- Show more button
Browser bu shift’leri CLS’e dahil etmiyor (500ms window after user interaction).
Passive shifts (page load, ad inject) sayılıyor. Active shifts (user caused) sayılmıyor.
Sonuç
CLS visual stability’nin metric’i. 7 yaygın kaynak: image dimensions, fonts, ads, dynamic injection, animations, embeds, bad skeletons.
Her kaynak için specific fix: width/height attributes, font-display optional + size-adjust, reserved space for ads, transform animations, aspect-ratio containers.
Measurement lab + field. Ongoing monitoring production’da.
CLS fix genelde cheap (CSS changes). Big UX improvement, SEO benefit. Low-effort high-value optimization.