Detailed 3D models look great — and load slowly. By default, a browser has to download an entire glTF or GLB file before it can render anything. For a multi-megabyte asset, that means your users stare at a spinner.
We built a fix: @needle-tools/gltf-progressive, a small library that streams models the way modern images load — a light version appears almost instantly, full detail arrives in the background. It works with Google’s <model-viewer>, plain three.js, React Three Fiber, and (out of the box) Needle Engine.
Here it is running live in <model-viewer>:
Original size: 17.4 MB. Compressed: 6 MB. With gltf-progressive: 715 KB initial download + streamed detail.
Instead of one monolithic file, the model is split into quality levels — like progressive JPEG, but for geometry and textures:
The final quality is identical — only the waiting disappears.
<model-viewer> — one script tagNo code changes, no new API. Include the script, and progressive loading is enabled for every compatible model on the page. Models that weren’t processed for streaming simply load the normal way.
<!DOCTYPE html>
<html>
<head>
<title>Progressive Loading with Needle and model-viewer</title>
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<!-- 1. Importmap for three.js, a dependency of model-viewer -->
<script type="importmap">
{
"imports": {
"three": "https://cdn.jsdelivr.net/npm/three/build/three.module.js",
"three/": "https://cdn.jsdelivr.net/npm/three/"
}
}
</script>
<!-- 2. The <model-viewer> web component -->
<script
type="module"
src="https://ajax.googleapis.com/ajax/libs/model-viewer/4.0.0/model-viewer.min.js"
></script>
<!-- 3. Needle gltf-progressive. This enables progressive loading. -->
<script
type="module"
src="https://cdn.jsdelivr.net/npm/@needle-tools/gltf-progressive@latest/gltf-progressive.min.js"
></script>
</head>
<body>
<!--
The model-viewer tag is standard. The src model has been processed by
Needle Cloud to support progressive streaming.
-->
<model-viewer
src="https://cloud.needle.tools/-/assets/Z23hmXB2oYehK-2oYehK-world/file"
ar
shadow-intensity="1"
camera-controls
touch-action="pan-y"
auto-rotate
max-camera-orbit="auto 90deg auto"
></model-viewer>
<style>
html, body { margin: 0; padding: 0; width: 100%; height: 100%; }
model-viewer {
width: 100%;
height: 100%;
background-color: #f0f0f0;
}
</style>
</body>
</html> Progressive loading needs assets that actually contain multiple levels of detail. Building those by hand means mesh decimation, texture resizing, and compression tuning — exactly the work you don’t want to own.
Needle Cloud does it automatically. Upload a model (glTF, GLB, FBX, USD, OBJ, VRM…) and the pipeline:
The workflow: sign up free → drag your file onto cloud.needle.tools → copy the URL into your src. That’s the whole integration. (More detail in our optimization pipeline article.)
npm i @needle-tools/gltf-progressive import { GLTFLoader } from 'three/examples/jsm/loaders/GLTFLoader.js';
import { useNeedleProgressive } from '@needle-tools/gltf-progressive';
const url = "https://cloud.needle.tools/-/assets/Z23hmXBZN45qJ-ZN45qJ-world/file";
const gltfLoader = new GLTFLoader();
// register the gltf-progressive plugin
useNeedleProgressive(gltfLoader, renderer);
gltfLoader.load(url, gltf => {
scene.add(gltf.scene);
}); import { useGLTF } from '@react-three/drei';
import { Canvas, useThree } from '@react-three/fiber';
import { useNeedleProgressive } from '@needle-tools/gltf-progressive';
function Model() {
const { gl } = useThree();
const url = 'https://cloud.needle.tools/-/assets/Z23hmXBZN45qJ-ZN45qJ-world/file';
const { scene } = useGLTF(url, false, false, (loader) => {
useNeedleProgressive(loader, gl);
});
return <primitive object={scene} />;
}
function App() {
return (
<Canvas>
<Model />
</Canvas>
);
} Needle Engine includes progressive loading by default — along with physics, networking, and XR. If you’re building a full interactive experience rather than embedding a single model, start there.
Upload your heaviest asset to Needle Cloud — the free tier includes 30 optimizations — and drop the resulting URL into the <model-viewer> snippet above. Watching your own 17 MB model appear in under a second makes a better argument than this article can.
Optimize your first model — free