← Back to garden

Three.js Resource Tracker Pattern

Budding Software Read in Chinese
Created September 20, 2025 Updated December 1, 2025
three.js webgl memory-management

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 InstancedMesh buffer tracking needs special handling
  • Optimal timing for render target cleanup
  • How to automatically track all internal resources from models loaded via GLTFLoader in the Blender to Web pipeline

Linked from