Building 60fps WebGL experiences that load fast
A practical guide to shipping photoreal 3D on the web without melting phones — asset pipelines, adaptive quality, and frame-budget discipline.
WebGL is the most powerful rendering primitive on the web — and the easiest way to ship a janky, battery-draining mess. Here is the discipline I use to keep real-time 3D both beautiful and fast.
Start with a frame budget
At 60fps you have 16.6ms per frame. Subtract browser overhead and you are realistically working with ~10ms. Treat that like money: every draw call, shader and post-process effect has a cost.
If you cannot name your frame budget, you do not have one.
Compress everything
The biggest wins are almost always in the asset pipeline, not the render loop.
- Use Draco for geometry and KTX2 / Basis for textures.
- Bake lighting where you can — real-time shadows are expensive.
- Instance repeated meshes instead of cloning them.
import { useGLTF } from '@react-three/drei';
// Draco + KTX2 decoders are resolved automatically by drei.
const { nodes } = useGLTF('/models/product-draco.glb');
Adapt to the device
Not every device deserves the same scene. Read capabilities and scale down gracefully.
const dpr = Math.min(window.devicePixelRatio, isMobile ? 1.5 : 2);
Gate heavy post-processing (bloom, SSAO, depth of field) behind a capability check, and drop the device pixel ratio when the frame time creeps up.
Measure on real hardware
Chrome's performance panel lies about thermals. Test on a mid-tier Android device, leave it running for five minutes, and watch what happens when it gets warm. That is your real baseline.
Ship the experience that stays smooth on the worst device you support — then let great hardware enjoy the extras.