Quick answer: The page created more WebGL contexts than the browser allows at once (around 16 in desktop Chrome, fewer on mobile), so the browser killed the oldest, often your game's. Reuse a single renderer instead of creating one per scene, minimap or thumbnail; destroy engines properly (renderer.dispose() plus forceContextLoss() in three.js, game.destroy(true) in Phaser); and handle webglcontextlost so a lost context does not end the session.
The game runs for a while, then the screen goes black or freezes after a few level restarts or menu visits, and the console shows this warning. Browsers cap how many WebGL contexts a page may hold, and code that creates a new one each time something renders eventually crosses the cap.
- The exact warning
- Where the extra contexts come from
- Fix 1: one renderer for the whole session
- Fix 2: actually release contexts you are done with
- Fix 3: survive hot reload and Strict Mode
- Fix 4: handle context loss anyway
- Checking how many contexts you have
- Black screens players never describe
- Related problems
- Frequently asked questions
The exact warning
WARNING: Too many active WebGL contexts. Oldest context will be lost.
Right after it, the oldest context fires webglcontextlost and stops drawing. If that context was your main game canvas, the game appears to freeze or go black while JavaScript keeps running.
Where the extra contexts come from
- A new engine instance per level or retry. Creating
new THREE.WebGLRenderer(),new Phaser.Game()or a new PixiJS application each time instead of reusing one. - Extra canvases for small things. A separate renderer for a minimap, a character preview, item thumbnails or an animated background.
- Development tooling. Hot module reload re-runs your setup code without tearing down the old renderer, and React's Strict Mode mounts components twice in development.
- Single-page apps. Navigating away from and back to a game page without destroying the engine.
Fix 1: one renderer for the whole session
Create the renderer once and keep it. Switch scenes, not renderers. For minimaps and previews, render with the same renderer into a render target or a viewport region:
// three.js: one renderer, a second view via scissor
renderer.setScissorTest(true);
renderer.setViewport(0, 0, width, height);
renderer.setScissor(0, 0, width, height);
renderer.render(scene, camera);
renderer.setViewport(width - 200, height - 200, 200, 200);
renderer.setScissor(width - 200, height - 200, 200, 200);
renderer.render(scene, minimapCamera);
renderer.setScissorTest(false);
Static thumbnails can be rendered once to an image (canvas.toDataURL() or an ImageBitmap) and the extra context released.
Fix 2: actually release contexts you are done with
Removing a canvas from the DOM does not immediately free its context; garbage collection decides when. Release it explicitly:
// three.js
renderer.dispose();
renderer.forceContextLoss();
renderer.domElement.remove();
// Phaser 3
game.destroy(true); // true removes the canvas
// Babylon.js
engine.dispose();
// any raw WebGL context
gl.getExtension('WEBGL_lose_context')?.loseContext();
PixiJS has an equivalent destroy option that removes the canvas, and an embedded Unity WebGL build should be shut down with unityInstance.Quit() before you leave its page in a single-page app.
Fix 3: survive hot reload and Strict Mode
// Vite / webpack HMR: tear down before the module is replaced
if (import.meta.hot) {
import.meta.hot.dispose(() => {
renderer.dispose();
renderer.forceContextLoss();
});
}
// React: clean up in the effect's return
useEffect(() => {
const game = new Phaser.Game(config);
return () => game.destroy(true);
}, []);
Fix 4: handle context loss anyway
Contexts can also be lost to GPU resets, driver updates and mobile backgrounding. Listen for the events, stop your loop on loss, and rebuild GPU resources on restore. Calling preventDefault() in the lost handler tells the browser you intend to restore:
canvas.addEventListener('webglcontextlost', (e) => { e.preventDefault(); pauseGame(); });
canvas.addEventListener('webglcontextrestored', () => { recreateGpuResources(); resumeGame(); });
Checking how many contexts you have
Count your own: wrap renderer creation in a function that increments a counter and logs it, and call it from the console after a few level restarts. If the number climbs with each restart, you have found the leak.
Black screens players never describe
A lost context looks like a frozen or black game, and players rarely mention a console warning. Bugnet's web SDK is one script tag: it listens for uncaught errors and unhandled promise rejections and files them with a screenshot of your canvas, the browser and device, and a performance snapshot, grouped so you see how many players each one hits. Report context loss yourself from the webglcontextlost handler and you see how often it happens, on which browsers and after how long.
Related problems
- How to Fix a “WebGL context lost” Error in a Web Game
- Fix: WebGL Game Context Lost After Tab Switch Mobile Chrome
- How to Fix a Web Game Showing a Black Screen on Load
Frequently asked questions
What does “Too many active WebGL contexts” mean?
The page holds more WebGL contexts than the browser allows at once, so the browser destroys the oldest one, which is often the main game canvas.
How many WebGL contexts can a page have?
It depends on the browser and device. Desktop Chrome allows around 16 active contexts; mobile browsers allow fewer. Design for one.
How do I free a WebGL context in three.js?
Call renderer.dispose() and renderer.forceContextLoss(), then remove the canvas from the page.
One canvas, one renderer, for the whole session. Everything else renders through it.