Fix meshopt decoder errors in three.js
Our GLBs, like many web-ready models, are compressed with EXT_meshopt_compression and quantised with KHR_mesh_quantization. That keeps them small (the medium door from the London terraced houses pack is 82,560 bytes, against 104,832 uncompressed; the pack's models are 10.9 MB against 15.4 MB), but three.js needs to be told how to read them. These are the two errors you will meet, reproduced in three.js 0.186, and the fixes.
1. “setMeshoptDecoder must be called before loading compressed files”
Load a compressed GLB with a plain GLTFLoader and the promise rejects with:
THREE.GLTFLoader: setMeshoptDecoder must be called before loading compressed filesThe file is fine; the loader has no decoder. Give it the one that ships with three.js:
import { GLTFLoader } from 'three/addons/loaders/GLTFLoader.js';
import { MeshoptDecoder } from 'three/addons/libs/meshopt_decoder.module.js';
const loader = new GLTFLoader().setMeshoptDecoder(MeshoptDecoder);KHR_mesh_quantization needs nothing extra: three.js reads quantised attributes natively. In react-three-fiber, drei's useGLTF sets the meshopt decoder for you (it is on by default in drei 10).
2. A Content-Security-Policy that blocks WebAssembly
MeshoptDecoder is WebAssembly. If your site sends a Content-Security-Policy whose script-src does not allow it, the decoder cannot start and Chrome reports (with your own policy quoted at the end):
WebAssembly.instantiate(): Compiling or instantiating WebAssembly module violates the following Content Security policy directive because 'unsafe-eval' is not an allowed source of script in the following Content Security Policy directive: "script-src 'self' 'unsafe-inline' https://cdn.jsdelivr.net".In our test it appeared twice: as the load error, and as an uncaught error from the decoder's own start-up. Add 'wasm-unsafe-eval' to script-src. It allows WebAssembly only, not JavaScript eval:
Content-Security-Policy: script-src 'self' 'wasm-unsafe-eval'3. The fallback: uncompressed copies
Every pack ships models-uncompressed/ next to models/: the same models with neither extension, for tools and pipelines that cannot decode meshopt. This loader tries the compressed file and falls back to the copy. The code below is the code we ran; the picture is the door it loaded under a policy without 'wasm-unsafe-eval', from the uncompressed copy.

// Tutorial example: a GLTFLoader that reads the packs' meshopt-compressed GLBs,
// with the uncompressed copies as a fallback.
// Assumes the pack is unzipped next to this file (models/, models-uncompressed/).
import { GLTFLoader } from 'three/addons/loaders/GLTFLoader.js';
import { MeshoptDecoder } from 'three/addons/libs/meshopt_decoder.module.js';
// The fix for "setMeshoptDecoder must be called before loading compressed files":
const loader = new GLTFLoader().setMeshoptDecoder(MeshoptDecoder);
export async function loadPart(id, level = 'medium') {
try {
return (await loader.loadAsync(`models/${id}/${level}-${id}.glb`)).scene;
} catch (err) {
// The decoder is WebAssembly. If it cannot run (for example a Content-Security-Policy
// without 'wasm-unsafe-eval'), load the same model from the uncompressed copy.
console.warn(`meshopt load failed for ${id}, using the uncompressed copy:`, err.message);
return (await new GLTFLoader().loadAsync(`models-uncompressed/${id}/${level}-${id}.glb`)).scene;
}
}
Questions
What does "setMeshoptDecoder must be called before loading compressed files" mean?
The GLB uses EXT_meshopt_compression and GLTFLoader has no decoder for it. Create the loader with new GLTFLoader().setMeshoptDecoder(MeshoptDecoder), importing MeshoptDecoder from three/addons/libs/meshopt_decoder.module.js.
Why does the meshopt decoder fail with a WebAssembly Content Security Policy error?
MeshoptDecoder is WebAssembly. A Content-Security-Policy whose script-src does not include 'wasm-unsafe-eval' blocks it. Add 'wasm-unsafe-eval' to script-src, or load the pack's uncompressed copies.
Do I need a decoder for KHR_mesh_quantization?
No. three.js reads quantised attributes natively; only EXT_meshopt_compression needs the decoder.