Three.js Resource Tracker Pattern
The Problem
While developing Three.js scenes within an Astro Island Architecture, I ran into a persistent issue: GPU memory kept climbing after page transitions. Chrome DevTools’ Performance Monitor showed steady JS heap growth. Three.js does not automatically release GPU resources once created.
I initially tried manual cleanup in dispose(), releasing each geometry, material, and texture individually. As scene complexity grew, missing a resource was inevitable. I switched to a centralized tracking approach instead.
The ResourceTracker Pattern
The core idea is straightforward: create a tracker object, register every GPU resource through it, and call tracker.dispose() once to release everything.
class ResourceTracker {
private resources = new Set<{ dispose: () => void }>();
track<T extends { dispose: () => void }>(resource: T): T {
this.resources.add(resource);
return resource;
}
dispose(): void {
for (const resource of this.resources) {
resource.dispose();
}
this.resources.clear();
}
}
Usage:
const tracker = new ResourceTracker();
const geometry = tracker.track(new THREE.BoxGeometry(1, 1, 1));
const texture = tracker.track(new THREE.TextureLoader().load('/textures/diffuse.webp'));
const material = tracker.track(new THREE.MeshStandardMaterial({ map: texture }));
const mesh = new THREE.Mesh(geometry, material);
// On page leave
tracker.dispose();
Integration with Astro Lifecycle
Under Astro’s View Transitions, page navigation fires the astro:before-swap event. That is where I run full cleanup:
document.addEventListener('astro:before-swap', () => {
tracker.dispose();
renderer.dispose();
renderer.forceContextLoss();
});
Handling webglcontextlost is also necessary. The browser may reclaim the WebGL context under memory pressure. This is not an error but a normal condition that needs graceful handling.
Results
After adopting ResourceTracker, navigating between pages 50 times showed stable GPU memory with no cumulative growth. The pattern works for any SPA or MPA architecture that dynamically creates and destroys Three.js scenes.
Open Questions
- Whether
InstancedMeshbuffer tracking needs special handling - Optimal timing for render target cleanup
- How to automatically track all internal resources from models loaded via
GLTFLoaderin the Blender to Web pipeline