Navigate
Search topics across all sections
useGLTF Hook
Think of useGLTF like opening an IKEA flatpack. Your model arrives as one box (the GLB file). useGLTF opens it and lays out all the parts on the floor -- the body, the wheels, the glass, the paint colors -- so you can access each piece individually. You can use the whole assembly as-is, or pick out just the parts you need.
You load a model with useGLTF and it appears on screen. Great! But now you want to change the color of just the car's body while keeping the glass transparent. You have no idea how to reach inside the model to modify individual parts.
Using <primitive object={scene} /> renders the entire model as a black box. Cannot access individual meshes, override materials, or add per-part event handlers.Real-world
You order a bookshelf from IKEA. It arrives in one box. You could leave everything sealed and just prop the box against the wall, but that is not very useful. Instead, you open the box and lay out every piece: the shelves, the sides, the screws, the backing board.
useGLTF does the same thing. It opens the GLB file and gives you access to every piece inside: nodes (the individual meshes), materials (the surface finishes), and animations (the moving parts). You can use the whole scene as-is with primitive, or you can destructure the parts and build something custom.
GLB File
The sealed box
useGLTF
Open and unpack
nodes
Individual meshes
materials
Surface finishes
Your Scene!
Assemble freely
Unpacking Your Model
useGLTF gives you two ways to work with a model: drop the whole thing in with primitive, or destructure nodes and materials for full control. Let's start simple and then open the box.
Step 1: Load and display the whole model
const { scene } = useGLTF('/models/robot.glb')
return <primitive object={scene} scale={1.5} />This is the quickest approach. Like propping the IKEA box against the wall. The model shows up exactly as the designer exported it, but you cannot easily change individual parts.
Step 2: Destructure for full control
const { nodes, materials } = useGLTF('/models/car.glb')
return (
<group>
<mesh geometry={nodes.Body.geometry}
material={materials.Paint} castShadow />
<mesh geometry={nodes.Glass.geometry}>
<meshPhysicalMaterial transmission={0.9} />
</mesh>
</group>
)Now you have opened the box. You can pick each piece by name, override its material, add click handlers, or decide which parts cast shadows. This is the recommended approach for any model you need to customize.
Step 3: Preload for instant display
function Car() {
const { nodes, materials } = useGLTF('/models/car.glb')
return <primitive object={nodes.Body} />
}
// Start downloading before the component mounts
useGLTF.preload('/models/car.glb')useGLTF.preload() starts the download as soon as your JavaScript file is imported -- long before the component mounts. When the component finally renders, the model is already cached and appears instantly with no loading spinner.
Step 4: Auto-generate with gltfjsx
npx gltfjsx public/models/robot.glb --transform --typesFor complex models with many parts, run gltfjsx to auto-generate a typed React component. It inspects the model and creates a file with every node and material properly destructured and typed. The --transform flag also optimizes the model file itself.
What you just learned
useGLTF is like opening an IKEA flatpack -- it unpacks a GLB file and lays out all the parts (nodes, materials, animations).
Use <primitive object={scene} /> for quick display, or destructure nodes/materials for full control.
useGLTF handles Draco decompression automatically -- no manual DRACOLoader setup needed.
Call useGLTF.preload() at the module level to start downloading before the component mounts.
Use gltfjsx to auto-generate typed React components from complex models.
Question
If useGLTF gives you individual nodes and materials, could you mix and match parts from two different models? Like putting the wheels from one car onto the body of another?
Think about it...
You have a car model with a Body, Glass, and Wheels part. You want the body to be red, the glass to be transparent, and the wheels to cast shadows. Which approach gives you this level of control?
Hint: Think about which approach lets you use React's strengths...
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 bodyColor — custom robot!
Try This!
Beginner
Set eyeColor to red — evil robot
Try This!
Beginner
Toggle showAntenna — stealth mode
These are the patterns that trip up developers most often. Switch between Wrong and Fixed to compare the code side by side.
function Scene() {
// Download starts when Scene mounts
// User stares at a fallback for 3 seconds
const { scene } = useGLTF('/models/heavy.glb')
return <primitive object={scene} />
}function Car() {
const { scene } = useGLTF('/models/car.glb')
// No control over individual parts
return <primitive object={scene} />
}import { DRACOLoader } from 'three/examples/jsm/loaders/DRACOLoader'
function Model() {
const gltf = useLoader(GLTFLoader, '/models/model.glb',
(loader) => {
const draco = new DRACOLoader()
draco.setDecoderPath('/draco/')
loader.setDRACOLoader(draco)
})
return <primitive object={gltf.scene} />
}Best Practices
Always Preload
Call useGLTF.preload() at the module level for every model. This starts the download when the JS is parsed, not when the component mounts, eliminating visible loading delays.
Use gltfjsx for Complex Models
Run npx gltfjsx on your model to generate a typed React component with every node properly extracted. The --transform flag also optimizes the model file itself.
Destructure Nodes
Prefer destructuring nodes and materials over rendering the whole scene with primitive. This gives you full React control: event handlers, conditional rendering, material overrides.
Pair with useAnimations
For animated models, use the useAnimations hook from drei. It handles mixer creation, action management, and cleanup automatically. Just pass in the animations from useGLTF.