Three.js From Zero · Article s8-07

S8-07 Error Handling

Season 8 · Article 07

Error Handling & Recovery

WebGL context loss. Network failures. GPU OOM. Model parse errors. Production 3D apps must survive all of them. Gracefully degrade, not crash.

1. WebGL context loss

Happens on: mobile backgrounding, laptop sleep, GPU driver crash, too many contexts open.

renderer.domElement.addEventListener('webglcontextlost', (e) => {
  e.preventDefault();    // Required — otherwise browser doesn't restore
  console.warn('Context lost');
  stopLoop();
});

renderer.domElement.addEventListener('webglcontextrestored', () => {
  console.log('Context restored');
  // Three.js will auto-recreate most GL resources
  // You may need to: re-upload textures, re-compile shaders, rebuild render targets
  reinit();
  startLoop();
});

2. Model load failures

loader.load(url,
  gltf => onSuccess(gltf),
  progress => onProgress(progress),
  error => {
    console.error('Failed to load', error);
    showFallback();
    reportToAnalytics(error);
  }
);

Fallback options: cached previous version, a placeholder cube, a "retry" button.

3. Network timeouts

async function fetchWithTimeout(url, ms = 10000) {
  const ctrl = new AbortController();
  const id = setTimeout(() => ctrl.abort(), ms);
  try {
    return await fetch(url, { signal: ctrl.signal });
  } finally {
    clearTimeout(id);
  }
}

4. GPU OOM

Symptoms: scene goes black, context lost repeatedly, console warns about texture upload failures.

Mitigations:

  • KTX2 textures (S6-03) — 8× less VRAM.
  • Drop mip levels when under memory pressure (anisotropy = 1, minFilter = Linear).
  • Unload offscreen assets aggressively (S6-09).
  • Fallback render target size (1080p → 720p).

5. Degrade gracefully

function detectCapabilities() {
  const canvas = document.createElement('canvas');
  const gl = canvas.getContext('webgl2') || canvas.getContext('webgl');
  if (!gl) return { webgl: false };
  return {
    webgl: true,
    webgl2: !!canvas.getContext('webgl2'),
    floatTextures: !!gl.getExtension('OES_texture_float'),
    instancing: !!gl.getExtension('ANGLE_instanced_arrays'),
    vertexArrays: !!gl.getExtension('OES_vertex_array_object'),
    maxTextureSize: gl.getParameter(gl.MAX_TEXTURE_SIZE),
    maxAnisotropy: gl.getExtension('EXT_texture_filter_anisotropic')?.MAX_TEXTURE_MAX_ANISOTROPY_EXT,
  };
}

Branch at load time. Low-end? Fewer particles, lower shadow resolution, disable post.

6. No-WebGL fallback

if (!WebGL.isWebGLAvailable()) {
  document.getElementById('viewer').innerHTML = `
    <div class="fallback">
      <img src="/product-hero.jpg" />
      <p>3D view unavailable. Showing preview.</p>
    </div>`;
}

7. Live demo — simulate and recover

8. Telemetry

window.addEventListener('error', (e) => {
  reportError(e.error, { context: 'global' });
});
window.addEventListener('unhandledrejection', (e) => {
  reportError(e.reason, { context: 'async' });
});

renderer.domElement.addEventListener('webglcontextlost', () => {
  reportEvent('webgl-context-lost', { userAgent, gpu: renderer.capabilities });
});

9. User-facing messaging

ErrorMessage
No WebGL"3D view needs a modern browser. Showing preview image."
Network fail"Couldn't load 3D model. [Retry] [Contact support]"
Context lost"3D view paused (GPU busy). Auto-resuming…"
OOM"Reduced detail for memory. Refresh for full quality."

10. Takeaways

  • Handle webglcontextlost/restored events.
  • Loaders have error callbacks — never ignore.
  • Timeout network requests.
  • Detect capabilities, branch by tier.
  • Always a fallback for no-WebGL.
  • Report errors + events to analytics.
  • User-facing messages, not stack traces.