Navigate
Search topics across all sections
GLTF Models
Think of a GLTF file like a 3D printer file. When you send an STL to a 3D printer, the file contains the entire model -- shape, structure, everything needed to produce the object. A GLTF file does the same for the web: shapes, colors, textures, and animations, all packaged into one file ready to display in your browser.
You export a model from Blender, drop the 50MB GLB file into your public folder, and load it in your scene. On your development machine it works fine. But when a user on a mobile phone tries to load the page, they wait 30 seconds and then the browser crashes.
net::ERR_INSUFFICIENT_RESOURCES — Browser ran out of memory while downloading and parsing a 50MB uncompressed GLTF model on a mobile device.
Real-world
Imagine you want to 3D print a robot figurine. You download an STL file that contains the entire robot: the body, the arms, the head, even the tiny gears inside. You send it to the printer and out comes the complete model.
GLTF works the same way for the web. A designer creates a model in Blender or Maya and exports it as a GLB file. That single file contains the geometry (the shape), the materials (the colors and textures), and even animations (walk cycles, idle loops). You load it in your R3F scene and the entire model appears, ready to go.
Blender / Maya
Create the model
Export as GLB
Package everything
Compress
Draco or gltfpack
useLoader
Load in R3F
On Screen!
Model appears
Loading Your First Model
Getting a GLTF model into your scene takes just a few lines. Let's walk through it step by step, from file to screen.
Step 1: Place the file in public
Drop your .glb or .gltf file into the /public/models/ folder. GLB is preferred for production because it is a single binary file. GLTF is a JSON file with separate binary and texture files -- useful for debugging but requires multiple HTTP requests.
Step 2: Load with useLoader
import { useLoader } from '@react-three/fiber'
import { GLTFLoader } from 'three/examples/jsm/loaders/GLTFLoader'
function Robot() {
const gltf = useLoader(GLTFLoader, '/models/robot.glb')
return <primitive object={gltf.scene} />
}useLoader downloads the file, parses it, and returns the loaded GLTF object. The primitive component takes the parsed scene graph and drops it directly into your R3F scene.
Step 3: Wrap in Suspense
<Canvas>
<Suspense fallback={null}>
<Robot />
</Suspense>
<ambientLight />
</Canvas>useLoader suspends the component while the model downloads. Without Suspense, React does not know what to show during that time. Wrap your model in Suspense so users see a loading indicator (or at least a blank canvas) instead of an error.
Step 4: Enable shadows and traverse
gltf.scene.traverse((child) => {
if (child.isMesh) {
child.castShadow = true
child.receiveShadow = true
}
})Models do not cast shadows by default. Use traverse to walk through every mesh in the model and enable castShadow and receiveShadow where needed.
What you just learned
GLTF is the standard 3D format for the web -- like a 3D printer file that contains shapes, colors, textures, and animations in one package.
Use useLoader with GLTFLoader to load models, and <primitive> to render them.
Always wrap loader components in <Suspense> to handle the download gracefully.
GLB (binary) is better for production; GLTF (JSON) is better for debugging.
Always compress models with Draco or gltfpack before shipping -- raw exports can be 10x too large.
Question
If a model is just a file containing shapes and materials, what happens when you try to place the same model in three different positions? Can you just render it three times?
Think about it...
You want to place the same tree model at 50 different positions to create a forest. What is the correct approach?
Hint: Remember: in Three.js, a child object can only have one parent...
Try These Challenges
Put what you learned into practice. Try each challenge in the demo above using the Leva controls, then check the solution.
Try This!
Beginner
Change wallColor to red — painted house!
Try This!
Beginner
Toggle showChimney — Santa's route
Try This!
Beginner
Set rotationSpeed to 0 — still life
These are the patterns that trip up developers most often. Switch between Wrong and Fixed to compare the code side by side.
// 50MB uncompressed model — slow download, crashes mobile
function Model() {
const gltf = useLoader(GLTFLoader, '/models/scene.glb')
return <primitive object={gltf.scene} />
}function Forest() {
const gltf = useLoader(GLTFLoader, '/models/tree.glb')
return (
<>
<primitive object={gltf.scene} position={[-3, 0, 0]} />
<primitive object={gltf.scene} position={[0, 0, 0]} />
<primitive object={gltf.scene} position={[3, 0, 0]} />
</>
)
}// User sees nothing for 3 seconds
function App() {
return (
<Canvas>
<Model />
</Canvas>
)
}Best Practices
Always Compress
Use Draco or gltfpack compression on all production models. Uncompressed exports can be 10x larger than necessary. The drei useGLTF hook handles Draco decompression automatically.
Use Suspense Boundaries
Always wrap model components in Suspense with a meaningful fallback. Users should see a loading indicator, not a blank screen while models download.
Clone for Reuse
When placing the same model multiple times, use drei's Clone component. Direct reuse of the scene object will just move it to the last position instead of duplicating it.
Prefer useGLTF Over useLoader
The drei useGLTF hook adds automatic Draco support, preloading, and caching over the raw useLoader approach. Use it for all new projects unless you need custom loader configuration.