Kutsik Games
English
All posts

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

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.

Real GPUApple M2 Pro, ANGLE Metal
10–24 ms
gate open → the scene loads
SwiftShaderthe CPU rasterizer PageSpeed uses
1.4–1.7 s
gate closed → the scene bundle is never downloaded
Probe time in headless Chrome 155 on an Apple M2 Pro.

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-gate
import { 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.

The package on npmThe code on GitHub