Sayfa hızı raporlarında en sık gördüğüm iki uyarıdan biri "Render engelleyen kaynakları kaldırın", diğeri "İlk giriş gecikmesini azaltın"dır ve her ikisinin de arkasında genellikle aynı şey vardır: head içine niteliksiz konmuş bir script etiketi. JavaScript defer ve async farkı tam olarak burada devreye girer; bu iki küçük nitelik, tarayıcının senin script'ini ne zaman indireceğini, ne zaman çalıştıracağını ve bu sırada sayfayı çizip çizmeyeceğini belirler.
Bu yazıda önce niteliksiz bir script etiketinin sayfayı neden tamamen durdurduğunu, sonra defer ve async niteliklerinin adım adım nasıl çalıştığını, hangi durumda hangisinin doğru olduğunu ve ikisini karıştırdığında ortaya çıkan "tanımsız değişken" hatalarını anlatacağım. Örneklerin hepsi doğrudan kopyalanabilir HTML; WordPress gibi hazır sistemlerde bu niteliklerin nasıl ekleneceğine de değineceğim.
Niteliksiz script Etiketi Sayfayı Neden Durdurur#
HTML ayrıştırıcısı belgeyi yukarıdan aşağı okur. Bir script src="..." etiketiyle karşılaştığında ve bu etikette defer veya async yoksa üç şey sırayla olur: ayrıştırma durur, dosya indirilir, dosya çalıştırılır ve ancak bundan sonra ayrıştırma kaldığı yerden devam eder. Bu davranış bir hata değil, HTML'in en eski günlerinden gelen bilinçli bir tasarımdır; çünkü script document.write() ile belgeye o anda içerik ekleyebilir ve tarayıcının bunu beklemesi gerekir.
Sonuç, kullanıcı açısından şudur: head içindeki 200 KB'lık bir kütüphane inip çalışana kadar ekranda hiçbir şey yoktur. Üstelik burada sadece indirme süresi değil, ayrıştırma ve çalıştırma süresi de vardır ve bu ikincisi orta seviye bir telefonda masaüstünün üç-dört katı sürer. Aşağıdaki tablo dört farklı yerleşimin davranışını karşılaştırıyor:
| Yerleşim | İndirme | Ayrıştırmayı durdurur mu | Çalışma anı |
|---|---|---|---|
head içinde, niteliksiz | Hemen | Evet, indirme + çalışma boyunca | Hemen, sırayla |
head içinde, defer | Paralel | Hayır | HTML bitince, sırayla |
head içinde, async | Paralel | Hayır (çalışırken durur) | İner inmez, sırasız |
body sonunda, niteliksiz | Geç başlar | Evet ama HTML zaten bitmiştir | Hemen, sırayla |
Yıllardır kullanılan "script'i body sonuna koy" tavsiyesi işe yarar ama optimal değildir: dosyanın indirilmesi, tarayıcı belgenin sonuna gelene kadar başlamaz. defer ise dosyayı en baştan indirmeye başlar, sadece çalıştırmayı erteler. Bu yüzden modern yaklaşım script'i head içinde bırakıp defer vermektir.
defer Nasıl Çalışır#
defer niteliği tarayıcıya şunu söyler: "Bu dosyayı şimdiden indirmeye başla, ama HTML ayrıştırması bitmeden çalıştırma." Dosya arka planda, ayrıştırmaya paralel olarak iner. Belge tamamen ayrıştırıldığında — yani DOM hazır olduğunda ama DOMContentLoaded olayı tetiklenmeden hemen önce — script'ler belgede yazıldıkları sırayla çalıştırılır.
Bu iki garanti defer'i son derece öngörülebilir kılar. Birincisi, script çalıştığında DOM tamamdır; document.querySelector ile aradığın öğe kesinlikle oradadır, DOMContentLoaded dinleyicisi sarmalamana gerek kalmaz. İkincisi, sıra korunur; jQuery'yi ondan sonra gelen eklentiden önce yüklemen garanti altındadır.
<head>
<meta charset="utf-8">
<title>Firmanız</title>
<!-- Üçü de paralel iner, ama bu sırayla çalışır -->
<script src="/js/kutuphane.js" defer></script>
<script src="/js/bilesenler.js" defer></script>
<script src="/js/uygulama.js" defer></script>
</head>
Yukarıdaki üç dosya aynı anda indirilir; bilesenler.js daha erken inse bile kutuphane.js çalışmadan çalıştırılmaz. Bu, bağımlılık zinciri olan tüm kodlar için doğru davranıştır. Pratik kural şudur: kendi uygulama kodun her zaman defer olmalı.
async Nasıl Çalışır#
async de dosyayı paralel indirir ama erteleme mantığı tamamen farklıdır: dosya iner inmez, HTML ayrıştırması hangi noktada olursa olsun hemen çalıştırılır. Çalıştığı süre boyunca ayrıştırma durur. Sıra garantisi yoktur; hangi dosya önce inerse o önce çalışır.
<head>
<!-- Bu iki dosyanın çalışma sırası tahmin edilemez -->
<script src="/js/analitik.js" async></script>
<script src="https://ornek-cdn.com/olcum.js" async></script>
</head>
Bu davranış, birbirinden ve senin kodundan tamamen bağımsız script'ler için doğrudur: analitik toplayıcıları, hata izleme kütüphaneleri, A/B test araçları. Bu script'ler ne DOM'a dokunur ne de başka bir dosyanın tanımladığı değişkeni kullanır; bu yüzden ne zaman çalıştıkları önemli değildir, mümkün olan en erken an en iyisidir.
async'in gizli maliyeti, çalıştığı anda ana iş parçacığını meşgul etmesidir. Sayfa çizilirken inen bir async script, o an ayrıştırmayı böler ve Total Blocking Time'a yazılır. Yani "async koydum, artık engellemiyor" cümlesi yalnızca yarı doğrudur: indirme engellemez, çalışma engeller.
Karşılaştırma ve Karar Rehberi#
İkisi arasındaki farkı tek tabloda görmek karar vermeyi kolaylaştırır:
| Özellik | defer | async |
|---|---|---|
| İndirme | Paralel | Paralel |
| Ayrıştırmayı indirirken durdurur mu | Hayır | Hayır |
| Çalıştırma anı | HTML bitince | Dosya iner inmez |
| Sıra garantisi | Var (belge sırası) | Yok |
| DOM hazır mı | Evet, garanti | Belki, garanti yok |
DOMContentLoaded öncesi mi | Evet | Değişken |
| Tipik kullanım | Uygulama kodu, kütüphaneler | Analitik, izleme, reklam |
Pratikte karar ağacı şu kadar basittir:
- Script başka bir script'e bağımlı mı ya da DOM'a dokunuyor mu? →
defer - Tamamen bağımsız, ölçüm/izleme amaçlı mı? →
async - Sayfanın ilk çiziminde mutlaka çalışması gerekiyor mu (tema anahtarı, düzen düzeltmesi)? → Niteliksiz inline script (dış dosya değil, çünkü ağ turu yok)
- Hiçbiri değilse ve emin değilsen →
defer
Dördüncü madde deneyimden gelen bir kısayoldur: defer yanlış seçildiğinde en kötü ihtimalle script biraz geç çalışır; async yanlış seçildiğinde site kırılır. Bu yüzden şüphedeyken defer her zaman daha güvenli taraftadır. Script'lerin toplam boyutunu da düşürmek istiyorsan bundle boyutunu küçültme yazısı bu konunun devamı niteliğindedir.
Modül Script'leri ve Dinamik Yükleme#
type="module" kullanan script'ler varsayılan olarak defer gibi davranır; ayrıca yazmana gerek yoktur. Modüllere async da verebilirsin, o zaman bağımlılık grafiği çözülür çözülmez çalışırlar.
<!-- Zaten defer davranışında, ek nitelik gerekmez -->
<script type="module" src="/js/app.js"></script>
<!-- Modül desteklemeyen eski tarayıcılar için yedek -->
<script nomodule src="/js/app-legacy.js" defer></script>
Bunun ötesinde, bir script'i sayfa yüklenirken hiç indirmemek çoğu zaman en iyi seçenektir. Kullanıcı bir düğmeye basmadan gerekmeyen bir kütüphaneyi (harita, zengin metin editörü, video oynatıcı) dinamik import() ile o an yüklersin:
// Harita kütüphanesi yalnızca kullanıcı sekmeye tıklayınca insin
document.querySelector("#harita-sekmesi").addEventListener("click", async () => {
const { haritayiKur } = await import("/js/harita.js");
haritayiKur(document.querySelector("#harita"));
}, { once: true });
Bu yaklaşımın tamamı code splitting başlığı altında incelenir ve defer/async seçiminden çok daha büyük kazanç sağlar; çünkü en hızlı script, hiç indirilmeyen script'tir.
WordPress ve Hazır Sistemlerde defer Eklemek#
WordPress script'leri wp_enqueue_script ile kaydeder ve varsayılan olarak nitelik koymaz. Modern sürümlerde kayıt sırasında strateji belirtebilirsin; daha eski kurulumlarda ise bir filtre ile eklersin:
// Belirli tanıtıcılara defer ekle
add_filter('script_loader_tag', function ($tag, $handle) {
$defer_edilecek = ['tema-main', 'slider', 'iletisim-formu'];
if (in_array($handle, $defer_edilecek, true)) {
return str_replace(' src=', ' defer src=', $tag);
}
return $tag;
}, 10, 2);
Burada dikkat edilecek nokta jQuery'dir. Birçok eski eklenti, kendi kodunu jQuery(document).ready(...) yerine doğrudan inline yazar; jQuery defer edilirse o inline kod jQuery tanımlanmadan çalışır ve konsolda $ is not defined hatası alırsın. Bu yüzden jQuery'yi defer etmeden önce tüm sayfa tiplerini konsol açık şekilde gezmen gerekir. Önbellek eklentilerinin "JS'i ertele" seçeneği de aynı işi yapar ve aynı riski taşır; açtıktan sonra formların, menülerin ve sepetin çalıştığını mutlaka doğrula.
Değişikliği Nasıl Doğrularsın#
Nitelikleri ekledikten sonra tahminle yetinme; tarayıcı zaten sana ne olduğunu gösteriyor. DevTools'un Network sekmesinde sütunları sağ tıklayıp Priority sütununu açtığında, her script'in hangi öncelikle indirildiğini görürsün. Niteliksiz bir head script'i "High", defer verilen "Low" ya da "Medium" olarak görünür; bu tek başına niteliğin gerçekten uygulandığının kanıtıdır.
Performance sekmesinde kayıt alıp ağ hızını "Slow 4G" seviyesine düşürdüğünde ise asıl fark ortaya çıkar. Niteliksiz script'lerde ilk boyama çizgisi, script'in çalışma bloğundan sonraya düşer; defer sonrası ilk boyama HTML ayrıştırmasının bitişine yapışır ve script çalışmaları onun sağında sıralanır. Aradaki mesafe, kullanıcının kazandığı süredir.
# Sayfadaki script etiketlerinin niteliklerini hızlıca listele
curl -s https://firmaniz.com/ | grep -o '<script[^>]*src=[^>]*>' | head -20
Bu komut, önbellek eklentisinin gerçekten defer eklediğini ya da bir güncellemeden sonra ayarın sıfırlandığını fark etmenin en hızlı yoludur. Lighthouse raporundaki "Render engelleyen kaynakları ortadan kaldırın" kalemi de doğrudan bu listeyi yansıtır; kalemin altında hâlâ bir JS dosyası varsa nitelik ya eklenmemiştir ya da o dosya inline'dır.
Sık Yapılan Hatalar#
Inline script'e defer vermek. defer ve async yalnızca src niteliği olan dış script'lerde çalışır. Etiketin içine kod yazıp defer eklersen nitelik sessizce yok sayılır ve script yine hemen çalışır. Inline kodu ertelemek istiyorsan onu bir dosyaya taşımalısın.
Bağımlı script'lere async vermek. En sık görülen kırılma budur: jQuery async, eklentisi de async verilir; eklenti önce inerse $ is not defined hatası alırsın ve hata her yüklemede değil, ağ koşullarına göre bazen ortaya çıkar. Bu tür hataları teşhis etmek saatler alır.
document.write kullanan script'i ertelemek. defer veya async ile çalışan bir script document.write çağırırsa tarayıcı bunu yok sayar veya belgeyi ezer. Eski reklam ve sayaç kodlarında hâlâ karşılaşılır; böyle bir kodu ertelemeden önce yerine modern bir sürüm bulmak gerekir.
Her şeyi async yapıp "hızlandırdım" sanmak. async dosyaları indirme sırasını da bozar; tarayıcının önceliklendirmesini engeller ve ana iş parçacığını rastgele anlarda meşgul eder. Ölçüm araçlarında TBT'nin arttığını görürsün.
Script'i erteleyip stili unutmak. JavaScript'i erteleyip CSS'i olduğu gibi bırakırsan render engeli devam eder; ikisini birlikte ele almak gerekir. CSS tarafı için critical CSS yazısındaki yöntem tamamlayıcıdır.
Sıkça Sorulan Sorular#
defer mi async mi daha hızlı#
Tek başına "daha hızlı" olan yok; ikisi de indirmeyi paralelleştirir. Fark, çalıştırma anındadır. async script'i daha erken çalıştırdığı için ölçüm kodlarının veri toplamaya erken başlamasını sağlar, ama sayfa çizilirken ana iş parçacığını böldüğü için görsel yüklenmeyi geciktirebilir. defer ise çalıştırmayı HTML bittikten sonraya aldığı için ilk boyamayı daha az etkiler. Kullanıcının algıladığı hız açısından çoğu durumda defer daha iyi sonuç verir.
defer kullanınca DOMContentLoaded dinlemem gerekir mi#
Gerekmez. defer script'leri, DOM tamamen kurulduktan sonra ve DOMContentLoaded olayı tetiklenmeden hemen önce çalışır; yani kodun içinde document.querySelector ile aradığın öğe kesinlikle vardır. Yine de dinleyici eklemek zarar vermez, çünkü olay henüz tetiklenmemiştir. Aynı kod async ile çalışıyorsa bu garanti yoktur ve dinleyici gerçekten gereklidir.
jQuery'yi defer edebilir miyim#
Teknik olarak evet, ama pratikte dikkatli olman gerekir. Sorun jQuery'nin kendisi değil, sayfaya doğrudan gömülmüş inline kodlardır; bunlar defer edilemez ve jQuery'den önce çalışır. Eğer temanız ve eklentileriniz tüm kodlarını dış dosyalarda tutuyorsa jQuery'yi defer etmek sorunsuz çalışır. Emin olmak için siteyi tarayıcı konsolu açık şekilde tüm sayfa tiplerinde gezip hata olup olmadığına bak.
Analitik kodunu async mı defer mi yapmalıyım#
Analitik ve hata izleme kodları async için ideal adaylardır: hiçbir şeye bağımlı değillerdir ve erken çalışmaları veri kalitesini artırır. Ancak sayfanın ilk çizimini geciktirmelerini istemiyorsan defer da kabul edilebilir bir seçimdir ve ölçüm kaybı çoğu senaryoda ihmal edilebilir düzeydedir. Bu script'leri üçüncü parti bir alan adından çekiyorsan preconnect ile bağlantıyı önceden kurmak, nitelik seçiminden daha fazla kazandırır.
script'i body sonuna koymak defer ile aynı şey mi#
Aynı değil, defer daha iyidir. body sonundaki bir script, tarayıcı belgenin sonuna gelene kadar indirilmeye bile başlamaz; yani indirme geç başlar. defer verilen bir script ise en baştan, HTML ayrıştırmasına paralel olarak inmeye başlar ve yalnızca çalıştırılması ertelenir. Sonuç davranış (DOM hazırken çalışma) aynıdır ama toplam süre defer ile daha kısadır.
defer ve async niteliği aynı anda kullanılırsa ne olur#
Modern tarayıcılarda async kazanır; defer yok sayılır. Bu kombinasyon eskiden bir uyumluluk hilesi olarak kullanılırdı çünkü çok eski tarayıcılar async'i tanımıyordu ve defer'e düşüyordu. Bugün böyle bir gerekliliğe rastlanmaz, dolayısıyla ikisini birlikte yazmak yalnızca kafa karışıklığı yaratır. Hangi davranışı istiyorsan sadece o niteliği yaz.
Kapanış#
defer ve async arasındaki farkı tek cümleye indirgersek: ikisi de dosyayı paralel indirir, defer çalıştırmayı HTML bitene kadar erteler ve sırayı korur, async ise dosya iner inmez sırasız çalıştırır. Akılda tutulacak dört alışkanlık şudur — kendi uygulama kodun ve bağımlılıkları için her zaman defer kullan, yalnızca gerçekten bağımsız ölçüm kodlarına async ver, inline script'e nitelik vermenin bir işe yaramadığını unutma ve şüphedeyken güvenli tarafta kalıp defer seç.
Bu niteliklerle kazandığın süreyi sunucunuz geri vermesin: script dosyalarının hızlı, sıkıştırılmış ve uzun süreli önbellek başlıklarıyla servis edilmesi en az yükleme stratejisi kadar önemlidir. HTTP/2 ve brotli desteğiyle gelen web hosting ve WordPress hosting paketlerimiz bunu varsayılan olarak sağlar; kendi Nginx veya LiteSpeed yapılandırmanı yazmak istersen VDS sunucularımız, tüm optimizasyonu bize bırakmak istersen sunucu yönetimi hizmetimiz uygun olur.