Course outline
Suspense and Loading in R3F
In one line
useGLTF suspends the component until the model is ready. <Suspense fallback> shows something meanwhile, preload starts early, and an error boundary turns a failed load (a 404) into a message instead of a blank canvas.
Think of it like this
Ordering a table at a restaurant. While the kitchen cooks (the file loads), the waiter puts bread on your table (the fallback). If the kitchen says "we are out of that" (the 404), the waiter tells you and offers another dish (the error boundary).
Words you'll meet
New words in this topic, in plain English. Come back here whenever one feels fuzzy.
- Suspense
- A React component that shows a fallback while something inside it is still loading.
- fallback
- What Suspense shows while it waits: a placeholder mesh, a spinner, nothing.
- preload
- Start downloading a file early, so it is ready when needed.
- error boundary
- A class component with
getDerivedStateFromErrorthat catches errors from its children and shows a replacement. - primitive
- The R3F tag that puts an existing three.js object (such as a loaded model) into the scene.
Step by step
01Preload at the top
useGLTF.setDecoderPath(...) and useGLTF.preload(URL) run when the module loads, so the download starts before React renders anything.
02Suspend, then show
Bike calls useGLTF(URL). Until the file is ready React shows Placeholder, a blue wireframe box. Then Bike renders <primitive object={scene} />.
03Catch the failure
The second example has two cases. Each is inside its own Boundary and Suspense. The wrong file name rejects with a 404, and the boundary shows a red box.
04Independent cases
The first case still loads: one failed model does not take the others down, because each has its own boundary.
Try it yourself
- 1
Break the file name
In the first example, change
lumen-bike-draco.glbtolumen-bike-dracoo.glb. The 404 is thrown out of the Canvas as an uncaught error and the whole scene disappears: with no error boundary, nothing catches it. The second example adds the boundary. - 2
Remove Suspense
Delete the
<Suspense>wrapper. The wireframe never shows, since the Canvas's own boundary has no fallback: the canvas stays blank until the bike is ready, then the bike appears.
Code & diagrams
The bike is the 27 KB Draco file from Phase 10. The wireframe box is the fallback and is removed when the bike is ready.
Console
Run the scene to see its console.Expected console output
THREE.Clock: This module has been deprecated. Please use THREE.Timer instead.
fallback on screen
fallback removed
bike ready, parts: down-tube, top-tube, seat-tube, chain-stays, seat-stays, fork, front-wheel, rear-wheel, handlebar, cranksetReact also prints its own error report. Only the lines starting with a label are ours.
Console
Run the scene to see its console.Expected console output
THREE.Clock: This module has been deprecated. Please use THREE.Timer instead.
Uncaught Error: Could not load /data/3d/models/lumen-bike-dracoo.glb: fetch for "https://hectal.test/data/3d/models/lumen-bike-dracoo.glb" responded with 404: Not Found
a wrong file name: FAILED, responded with 404: Not Found
the right file: loaded, first child is "bike"Break it on purpose
Errors are the best teachers. Make each change, read the error, guess what went wrong, then reveal the answer.
Break #1
Forget the decoder path
In the first example, delete the useGLTF.setDecoderPath(...) line.
Myth vs fact
Myth
Suspense shows a loading spinner automatically.
Fact
It shows whatever you pass as fallback, and nothing if you pass nothing. Without your own Suspense, the Canvas waits with a blank screen.
Myth
An error boundary around the Canvas catches loading errors.
Fact
It does catch them, but the boundary then replaces the whole scene. Put one around each slow asset to keep the rest of the scene on screen.
Pro corner
Extra depth for experienced readers. New to this? Skip it for now and come back later.
- ▸
useGLTFusesthree-stdlib's GLTFLoader, a copy of three's own. It supports the extensions the bike file uses (Draco, texture transform, instancing). If a rare extension fails, passextendLoaderto configure the loader. - ▸
A cached
sceneis one object: using<primitive object={scene} />in two places moves it rather than copies it. Use drei's<Clone object={scene} />orscene.clone()for several copies. - ▸
React's
useTransitionorstartTransitioncan keep an old model on screen while a new URL loads, so changing a model does not flash the fallback.
Remember this
- 1
The problem this solves: loading is slow and can fail. Without a plan, the visitor sees an empty canvas, and does not know whether to wait. R3F uses React's own tools for both cases, so you write them the same way as in any React app.
- 2
**How
useGLTFsuspends.** On the first call for a URL it starts the download and throws a promise. React stops rendering that part, shows the nearest<Suspense fallback>, and tries again when the promise settles. The result is cached by URL: a seconduseGLTFcall for the same file returns at once. The first example printsfallback on screen,fallback removed, and thenbike ready. - 3
Where the Suspense goes. Without one of your own, the Canvas has its own boundary around everything, and the whole scene stays blank until every pending file is ready (the helpers example waited for its HDR for the same reason). Put each slow thing in its own
<Suspense>: the stand and lights then appear at once and the bike arrives later. - 4
Draco in drei.
useGLTF(url)sets up Draco and Meshopt decoders. For Draco the default decoder path is Google's CDN (https://www.gstatic.com/draco/versioned/decoders/1.5.5/). CalluseGLTF.setDecoderPath("/data/3d/draco/")once at start-up to use your own copy. drei keeps one Draco loader for all models, so a path given in one call changes it for every later load. - 5
Preloading.
useGLTF.preload(url)starts the download when the module loads, before any component asks. With it the file is often ready by the time React gets to it, and the fallback never shows. A cached model can be released withuseGLTF.clear(url). - 6
Failure. If the load rejects (404, bad file, a decoder that cannot load), React rethrows the error where the component was waiting. Wrap the Suspense in an error boundary to catch it. The second example loads a wrong file name: the boundary logs
FAILED, responded with 404: Not Foundand shows a red wireframe. For a visitor, the plain message and a Try again button from Topics 10.4 and 10.7 are better; the build topic does this. - 7
The cache remembers failures. After a rejection the failed result is cached for that URL. A Try again button must call
useGLTF.clear(url)before it resets the boundary, or the next render throws the same error immediately.
Explain it without notes
Why does the whole scene go blank when one model is slow, and how do you fix it?
A Try again button resets the error boundary but the model fails at once. What did you forget?
Practice
Write a Loader component that shows the percent loaded as HTML, using useProgress from drei.
Write the wrapper that shows a grey box while /data/3d/models/lumen-bike-draco.glb loads and a red box if it fails.
Done when you can
Load a model with useGLTF inside its own Suspense
Preload a model and set the decoder path
Catch a failed load with an error boundary
Explain why a retry must clear the cache