Command Palette

Search for a command to run...

Hectal
Course outline
Topic 11.6Intermediate ~19 min · step 6 of 7

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.

0/7 · 0%

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 getDerivedStateFromError that 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. 1

    Break the file name

    In the first example, change lumen-bike-draco.glb to lumen-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. 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

useGLTF, Suspense and a fallback New tab

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.

idle

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, crankset
A model URL that 404s New tab

React also prints its own error report. Only the lines starting with a label are ours.

idle

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.

terminal
$ Run
── what you'll see ──
Uncaught (in promise) TypeError: Failed to fetch (printed several times; the bike never appears, and an error boundary is never called)

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.

  • ▸

    useGLTF uses three-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, pass extendLoader to configure the loader.

  • ▸

    A cached scene is one object: using <primitive object={scene} /> in two places moves it rather than copies it. Use drei's <Clone object={scene} /> or scene.clone() for several copies.

  • ▸

    React's useTransition or startTransition can keep an old model on screen while a new URL loads, so changing a model does not flash the fallback.

Remember this

  1. 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. 2

    **How useGLTF suspends.** 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 second useGLTF call for the same file returns at once. The first example prints fallback on screen, fallback removed, and then bike ready.

  3. 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. 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/). Call useGLTF.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. 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 with useGLTF.clear(url).

  6. 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 Found and 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. 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

01

Why does the whole scene go blank when one model is slow, and how do you fix it?

02

A Try again button resets the error boundary but the model fails at once. What did you forget?

Practice

01

Write a Loader component that shows the percent loaded as HTML, using useProgress from drei.

02

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