100 on PageSpeed with live WebGL, and the small gate behind it
Our studio site runs live game covers drawn with WebGL, and still scores 100 in all four PageSpeed Insights categories, on mobile and on desktop. One of the changes that got us there is now a tiny open-source package: webgl-gate.
Lighthouse tests mobile on a CPU
PageSpeed Insights measures mobile pages on SwiftShader, a rasterizer that draws WebGL on the processor instead of a graphics card. Many virtual machines, remote desktops and browsers with hardware acceleration turned off do the same. There a WebGL scene is not just slow: downloading and parsing its code alone costs main-thread time, and Total Blocking Time shows it.
Our redesigned site started at 78 on mobile, with the largest image appearing after 4.2 seconds.
What we changed
- Critical CSS inlined into the HTML, so nothing blocks the first paint.
- WebGL only where a real GPU draws the page; elsewhere the page shows static art and never downloads the PixiJS bundle.
- Responsive AVIF covers: phones get 720-pixel images, about three times lighter.
- Game screenshots wait until the main cover has loaded.
Mobile went to 100, with the largest image at 1.7 seconds, Total Blocking Time 0 ms and no layout shift. Desktop is 100, with the largest image at 0.4 seconds.
How the gate decides
The gate creates a throwaway WebGL context, reads the renderer name and looks for SwiftShader, llvmpipe, softpipe and Basic Render. The failIfMajorPerformanceCaveat flag alone is not enough: in our tests, headless Chrome on SwiftShader still hands out a context with it set.
The number that surprised us
On a real GPU the probe takes 10–24 ms. On SwiftShader, creating that first context took 1.4–1.7 seconds of main thread. The check is expensive exactly where the answer is no. So webgl-gate waits for the page to load and for the browser to be idle before it asks, and never sits on the critical path.
Use it
Install it with npm and load your scene only on a real GPU. On the CPU the import is never called, so the bundle is never downloaded.
npm i webgl-gateimport { loadIfFastWebGL } from 'webgl-gate';
const scene = await loadIfFastWebGL(() => import('./scene'));
if (scene) scene.mount(canvas);There is a React hook too, useFastWebGL, and ready recipes for Next.js, three.js, React Three Fiber, Vue, Svelte and MapLibre. The core is under 1 kB.
What it does not do
It answers one question: GPU or not. If you need to know how strong the GPU is, detect-gpu by pmndrs classifies GPUs by benchmark scores. The verdict is not cached between visits yet: a cached answer could outlive the hardware it describes.