Draco compression is the difference between a 90MB glTF export and a 3MB one. For CAD-heavy product models it is not optional. But there is a trap in the standard setup that quietly costs you half a second on first paint.
The default that looks fine
Most examples point the decoder at Google's CDN:
const dracoLoader = new DRACOLoader()
dracoLoader.setDecoderPath('https://www.gstatic.com/draco/v1/decoders/')
It works. It also means every first-time visitor makes a cross-origin trip to fetch the WASM decoder before a single triangle appears. In my measurements on real configurators that added around 600ms to first paint on a cold cache, right at the moment the user is staring at an empty canvas deciding whether to wait.
The fix: self-host the decoder
Copy the decoder out of the Three.js package you already have installed:
node_modules/three/examples/jsm/libs/draco/
Drop it in your public/ folder and point at it:
const dracoLoader = new DRACOLoader()
dracoLoader.setDecoderPath('/draco/') // served from your own origin
const loader = new GLTFLoader()
loader.setDRACOLoader(dracoLoader)
Now the decoder loads from the same origin, in parallel with the rest of your assets, cached by the same CDN or server rules as everything else. No extra cross-origin handshake on the critical path.
Two things people miss
-
The decoder is WASM plus a JS wrapper. Copy the whole
draco/folder, not just one file, or you get a confusing "failed to load" with no stack trace. -
Set the right cache headers. The decoder never changes between builds. Give it a long
Cache-Control: max-ageso returning visitors pay nothing. First visit is the only one that touches it.
Small change, measurable win, and it lives on the critical path so it punches above its weight. The pool configurator at basen3d.grodev.pl serves its decoder this way.
I am Dominik Groński / GroDev, a studio in Poznan, Poland building custom 3D configurators and Laravel back-ends for manufacturers.
Top comments (0)