Quick answer: Three.js games push the GPU hard, so their crashes cluster around WebGL: lost contexts, shader compile and link failures, and out-of-memory errors from large textures or geometry. Listen for the canvas contextlost event, check WebGLRenderer info and program diagnostics, and hook the global error handlers. Attach the GPU strings and renderer state, then group duplicates so one hardware family does not bury the rest.

Three.js gives indie developers real 3D in the browser, but real 3D means leaning on a GPU you do not control, behind a WebGL context the browser can revoke at any moment. A Three.js game can crash from a shader that compiles on your machine but not on a player's integrated chip, from a lost context when the OS resets the GPU, or from sheer memory pressure when a scene loads too much at once. This post covers the WebGL-specific failure modes, the diagnostics Three.js and the GL context expose, and how to package a report that names the GPU and the moment it gave out.

Why Three.js crashes are mostly GPU crashes

A 2D game can limp along on a slow GPU, but a Three.js game lives and dies by the GPU pipeline. The most common hard failures are context loss, when the browser revokes the WebGL context after a driver reset or adapter switch, and shader failures, when a material's compiled program fails to build on a particular driver. There are also out-of-memory situations when textures, render targets, and geometry exceed what the device can hold, which the browser may answer by killing the context outright.

What makes these tricky is how silent they are. A lost context does not throw; it just stops drawing, and your animation loop keeps running against a dead renderer. A shader that fails to compile may leave an object invisible rather than crash the page. Players report a black screen or missing graphics, not an error, so you have to instrument the renderer deliberately to learn what actually happened on their hardware.

Catching context loss and recovering

Three.js renders into a canvas, and that canvas emits the standard webglcontextlost and webglcontextrestored events. Add a listener for the loss, call preventDefault, and stop your animation loop so you are not issuing draw calls into a dead context. On restore, you need to rebuild GPU-side resources, because Three.js textures, geometries, and programs uploaded to the old context are gone. Failing to handle restore is a common reason a recovered tab still shows nothing.

Report every loss even when you recover. The WebGLRenderer exposes a getContext you can query, and the WEBGL_lose_context extension lets you test the path during development. A single loss on a flaky driver is tolerable, but repeated losses on a specific GPU family signal that your scene is too heavy for that hardware and needs a quality fallback. Logging each event with the GPU strings is what separates an occasional flake from a systemic problem you can actually act on.

Surfacing shader and program failures

Shader failures are quieter than context loss but just as damaging. When a material's program fails to compile or link, the GL driver records an info log, and the object using that material simply will not render correctly. To catch these, check the WebGLRenderer info and listen for the renderer's program diagnostics during development, and in production wrap risky custom ShaderMaterial creation so a compile failure is reported with the shader name and the driver's info log rather than swallowed.

Driver variance is the real enemy here. GLSL that compiles cleanly on a desktop NVIDIA card can fail on a mobile or integrated GPU with stricter limits on uniforms, varyings, or precision. Because of this, the GPU vendor and renderer strings belong on every shader-failure report. They let you see that a given custom material only breaks on one chipset, which usually points at a precision qualifier or an array size that exceeds that device's limit.

The context that makes a WebGL report actionable

For any Three.js crash, capture the renderer envelope: WebGL version, the unmasked GPU vendor and renderer strings from the debug info extension, the maximum texture size and max combined texture units, and the supported extensions you depend on. Add the WebGLRenderer info counts for geometries, textures, and draw calls at the moment of failure, since those numbers often reveal that a scene quietly grew past what the device can sustain.

Then add the browser envelope and your game state: user agent, OS, viewport, pixel ratio, current level or scene, and build version. The combination is powerful. A report showing context loss on an integrated GPU with a high draw-call count and a 4K render target practically diagnoses itself as a memory and fill-rate problem, and points you at a resolution clamp or an LOD system rather than a wild hunt through minified renderer internals.

Setting it up with Bugnet

Bugnet offers a small web SDK and an in-game report button that fit a Three.js project cleanly. Wire your contextlost handler, your shader-failure wrappers, and the global window error and unhandledrejection hooks to the SDK, attaching the GPU strings, the renderer info counts, and the scene state. Every crash, from a revoked context to a failed shader compile, arrives in one dashboard already carrying the GPU detail that tells you whether it is a hardware-specific fault or a logic bug that hits everyone.

Occurrence grouping is what keeps WebGL noise manageable. Bugnet folds repeated context-loss or shader reports from the same GPU family into a single counted issue, so the worst-affected hardware rises to the top instead of flooding your inbox. Custom fields like GPU renderer, WebGL version, and scene name become filters, so you can confirm a spike is one integrated chipset on one heavy level and respond with a targeted quality fallback rather than blanket downgrades for everyone.

Testing your renderer before players do

Before each release, deliberately exercise the GPU failure paths. Trigger a context loss with WEBGL_lose_context and confirm your recovery rebuilds resources and resumes. Force a shader compile failure on a test material and confirm the info log reaches your dashboard. Most importantly, run on genuinely weak hardware, an old laptop with integrated graphics and a real mid-range phone, because the bugs you cannot reproduce on a gaming rig are exactly the ones your players will hit.

When reports flow, work the occurrence list from the top, fixing the loudest GPU families first and watching counts fall after each fix. Three.js rewards you with capable browser 3D, and the cost is that you own GPU failure handling a native engine might abstract away. A disciplined crash stream with renderer context turns that cost into a manageable, steadily improving part of shipping a browser game.

Three.js crashes are GPU crashes, so handle context loss, report shader failures, and attach the GPU strings so each black-screen report names the hardware that gave out.