What Is React Three Fiber?
React Three Fiber is a React renderer for Three.js. You declare a scene as JSX components instead of writing imperative setup code, and props map directly to Three.js properties. It produces identical output to vanilla Three.js because it uses the same renderer underneath, with no meaningful overhead added.
Instead of imperatively creating a scene, a camera, a renderer, and a loop, you declare the scene as JSX: a Canvas component, meshes as children, lights as siblings. A useFrame hook replaces the hand-rolled animation loop. The official documentation says that everything that works in Three.js works here without exception, and that components render outside of React with no added overhead.
In practice the gain is composition: a product viewer, a particle field or a baked hero scene becomes a component you can drop into any React tree, pass props to, and reuse across pages. For the sites we build on Astro islands or Next.js, that matches how the rest of the page is already structured, and it fits the scroll-driven patterns we cover in how scroll animation websites work.
When Should You Choose R3F Over Vanilla Three.js?
Choose R3F when the site is React-based, the 3D work must be reused across pages, or more than one person will maintain it. Choose vanilla Three.js when there is no React in the stack, when bundle size for a single isolated scene matters most, or when you are doing renderer-level work where the abstraction gets in the way.
The ecosystem also favors R3F. The drei helper library covers controls, model loading, environment lighting, text and staging in a few lines each, and physics, post-processing and scroll controls all have maintained React bindings. You write meaningfully less code, and less of it handles disposal by hand.
The adoption numbers say the React path is now the mainstream one. Roughly one in three weekly installs of three arrives alongside the React renderer:
| Package | Weekly npm downloads | GitHub stars |
|---|---|---|
| three | 10.1M | 113k |
| @react-three/fiber | 3.5M | 31.1k |
| @react-three/drei | 2.7M | 9.7k |
Weekly npm downloads, June 2026
Source: npm registry download counts APIWhy Does Disposal Make or Break Memory in Single-Page Apps?
Three.js never frees GPU memory automatically. Geometries, materials, and textures persist until you call dispose, so every revisit to a 3D route in a single-page app can stack another copy of the scene in GPU memory. R3F disposes the objects it created when a component unmounts, which removes the most common production leak.
The three.js manual is explicit that this is by design: the library cannot clean these resources up automatically, and a single 1024-pixel texture can hold four to six megabytes of GPU memory. With traditional page loads this never matters, because leaving the page destroys the whole context. In a single-page app it matters a great deal, because users visit your 3D page, leave and come back, and every visit that does not clean up stacks another copy of the scene in GPU memory until mobile Safari kills the tab.
Disposal is the strongest single argument for R3F. In vanilla Three.js you write that teardown yourself: traverse the scene, dispose geometries and materials, unbind events and stop the loop. We have audited sites where the missing dispose path was the entire performance complaint.

How Should You Structure a Production Scene?
Give the scene one entry point that owns the renderer, camera, sizing, and time, then split the world into modules or components that each own their own assets and cleanup. The render loop and each asset then have exactly one owner, and no module reaches across to change another module's mesh.
Hero-scene code rots fast when everything lives in one file. In vanilla projects we use a single Experience entry point with the world split into modules behind it. In R3F the component tree gives you the same structure without extra work: the Canvas is the boundary, and each child component loads, animates and cleans up its own part of the world.
What Belongs in a Mobile Performance Budget?
Set the budget before you build: pixel ratio capped at 2, draw calls held to the low hundreds with instancing, one or two real-time lights with shadows baked or off, textures sized to what is actually visible, and a frame-time check on a mid-range Android phone before launch, not just the development Mac.
Every scene we ship gets a performance budget up front, because retrofitting one later is miserable. These are the numbers we hold to:
- Pixel ratio capped at 2, no matter what the device reports. Rendering at 3x on a phone burns battery for sharpness nobody perceives.
- Draw calls in the low hundreds, using instancing when many objects share a geometry and material.
- Real-time lights kept to one or two, with shadows baked or off on mobile.
- Textures sized to what is actually visible rather than to whatever the designer exported.
- A frame-time check on a mid-range Android phone before launch, not just on the development Mac.
Why Does Baked Lighting Look Premium for Almost Nothing?
Baked lighting looks premium for almost nothing because the lighting cost moves out of the browser entirely. You light the scene in Blender, bake the result into one texture, and ship the model with a basic unlit material showing it. The scene has soft global illumination that real-time rendering cannot afford, at the render cost of a textured cube.
The best-looking scenes we ship are usually the cheapest to render. We model the scene low-poly in Blender, light it properly there, and bake all of that lighting into a single texture. In the browser the model loads with a basic unlit material that uses the baked texture, so there are no real-time lights or shadow maps and no lighting math per frame.
R3F handles this pattern well. We load the model with useGLTF inside Suspense, apply the baked texture and add a couple of shader-driven accents such as animated particles, and the whole hero runs smoothly on a four-year-old phone. Baking is the core technique behind most of our 3D website design work, and we wrote more about where it fits in our note on Three.js for business sites.
What Are the Honest Tradeoffs of R3F?
R3F adds React as a dependency, which is wrong for a one-canvas brochure page. The abstraction can hide per-frame allocations created inside render functions, and debugging spans two layers (React and Three.js), which costs real time for developers who only know one. On multi-page production work the maintainability win has outweighed all three.
The allocation trap is the hardest of the three to spot. Creating a new vector, material or geometry inside a render function or a useFrame callback allocates on every frame, and nothing warns you. Avoiding it takes ordinary React discipline: memoize what persists, mutate what changes, and profile on real hardware instead of trusting the development machine.
Where Did We Land on R3F Versus Vanilla?
We use R3F by default for any site with React or Astro islands in the stack, and vanilla Three.js for isolated single-scene pages where bundle size matters most. The four lessons apply either way: dispose deliberately, structure the scene like an app, set the mobile budget before modeling starts, and bake lighting whenever the scene allows it.
The framework choice matters less than the discipline, though R3F makes the discipline easier to keep. We take on hero scenes built to these budgets from the start rather than retrofitted to them; see how we work with clients for the shape of an engagement.



