← Blog · 20 Haziran 2026
Three.js sahnesi SPA'da bellek sızdırır: dispose disiplini ve paylaşılan kaynak tuzağı
Bu sitenin anasayfasında gerçek bir 3D oda var: masa, klavyeye yüklenen bir karakter, halıda bir kedi. Blog’a geçince o sahne sökülüyor. İlk yazdığımda söküm “çalışıyordu” — ama her home → blog → home turunda Chrome’un bellek grafiği biraz daha yukarı tırmanıyordu. Sebep, Three.js’in en sinsi tarafı: sahneden çıkarmak, GPU’dan çıkarmak değildir.
scene.remove yetmiyor
scene.remove(obj) objeyi sahne grafiğinden alır, ama geometry, material ve texture hâlâ GPU belleğinde (VRAM) durur. GLB’lerin texture’ları MB’larca yer kaplar; her ziyarette yenisi yüklenip eskisi bırakılmazsa VRAM birikir de birikir. Tarayıcı bir noktada sekmeyi yavaşlatır ya da WebGL context’ini büsbütün kaybeder.
Üç kaynağı da elle bırakmak gerekiyor:
export function disposeObject(root: Object3D) {
root.traverse((child) => {
const mesh = child as Mesh;
if (!mesh.isMesh) return;
mesh.geometry?.dispose();
const materials = Array.isArray(mesh.material) ? mesh.material : [mesh.material];
for (const mat of materials) {
if (mat) disposeMaterial(mat);
}
});
}
Asıl tuzak: paylaşılan kaynağı dispose etme
İlk versiyonum her texture’ı gözü kapalı dispose ediyordu. Sonuç ters tepti: sahneye geri döndüğümde modeller bir an gri ve bozuk geliyordu. Sebep, matcap texture’larım modül-seviyesi singleton’dı — bir kez yaratılıp tüm sahnede paylaşılıyordu. Onları dispose edince, home → blog → home dönüşünde aynı Texture objesi GPU’ya tekrar upload edilmek zorunda kalıyordu (churn), üstelik global state kırılganlaşıyordu.
Çözüm: sahibi olmadığınız kaynağı bırakmayın. Paylaşılan texture’lar için bir atlama listesi tutuyorum:
const sharedTextures = new Set<Texture>([...Object.values(matcaps), blobTexture]);
export function disposeMaterial(mat: Material) {
for (const value of Object.values(mat)) {
const tex = value as Texture;
if (tex && tex.isTexture && !sharedTextures.has(tex)) tex.dispose();
}
mat.dispose();
}
Kural basit ama kolay kaçar: bir kaynağı yalnızca onu yaratan dispose etmeli. Per-obje yüklenen GLB texture’ları → bırak. Tüm sahnenin paylaştığı singleton → dokunma.
Söküm tek yerde
Bütün bunlar route değişiminde tetikleniyor. Vue tarafında onBeforeUnmount her şeyi sırayla söküyor:
onBeforeUnmount(() => {
destroyMotion(); // GSAP/scroll bağlarını kopar
destroyWorld(); // proceduralObjects → disposeObject
destroyRenderer(); // renderer.dispose()
});
Bağlandığınız her şeyin — animasyon döngüsü, scroll listener’ları, renderer — aynı yerde ve aynı disiplinle sökülmesi gerekiyor. Bir 3D sahneyi SPA içinde tutmanın bedeli bu; ama bedeli bir kez doğru ödersen, sahne bütün gün açılıp kapanır ve bellek düz çizgi kalır.
three.js’in obje dispose rehberi bunu açıkça söylüyor: geometry, material ve texture’lar elle bırakılmalı, çünkü three bunların yaşam döngüsünü sizin için yönetmez.