mrkeyoor.com_
Wed 23 Sept 00:35 UTC
npmWeb Frontendupdated 22 Sept 2026

@react-three/fiber review

@react-three/fiber 9.7.0 is a React 19 renderer for Three.js scenes. JSX elements construct actual Three.js objects, `Canvas` owns renderer state and events, and hooks expose the frame loop, camera, viewport, loaders, raycaster, and scene. The release fixes keyed child reordering, pierced-prop resets, host-prop synchronization, and reconstruction of multiple instances; it also matches React DOM event priorities and enables reconciler microtasks. Our full-package browser build measured 887.8 KB minified and 238.4 KB gzipped, before application models, textures, or helper packages.

Verdict

Our @react-three/fiber 9.7.0 full import measured 238.4 KB gzipped, and the install brought 8 peer contracts before any model or texture arrived. Pay that cost for a real React 19 Three.js application with shared components and scene tooling; use plain Three.js or a lighter visual when React reconciliation adds no product value.

We installed it

Lab card: what happened when we installed @react-three/fiberScreenshot of @react-three/fiber documentation
Install✓ · 7.5s17 packages on disk · 32 MB
ImportESM import works · require() works · CommonJS package
Browser238.4 KBgzipped (887.8 KB minified), bundled with esbuild
TypesTypeScript types bundled
Known vulns00 critical · 0 high · 0 moderate · 0 low (npm audit)

Answers from our run

Does @react-three/fiber install cleanly?

Yes. In a fresh container with an empty cache, npm install @react-three/fiber finished in 8 seconds, leaving 17 packages and 32 MB on disk. npm audit reported no known vulnerabilities.

How much does @react-three/fiber add to a browser bundle?

238.4 KB gzipped (887.8 KB minified) when the whole package is bundled for the browser with esbuild. Importing only part of it is usually smaller.

Does @react-three/fiber work with both ESM and CommonJS?

Yes. Both import '@react-three/fiber' and require('@react-three/fiber') worked in Node 22 in our run. The package is published as CommonJS.

Does @react-three/fiber include TypeScript types?

Yes, type declarations ship inside the package, so no @types install is needed.

@react-three/fiber or three: which should you use?

three: Choose it for direct scene ownership and an imperative loop without React reconciliation. Our @react-three/fiber 9.7.0 full import measured 238.4 KB gzipped, and the install brought 8 peer contracts before any model or texture arrived.

When should you not use @react-three/fiber?

The application remains on React 18. The project pairs Fiber 9 with React 19 and tells React 18 users to stay on Fiber 8.

API stability4/5`Canvas`, `useFrame`, `useThree`, `useLoader`, pointer events, and JSX-backed Three.js objects remain the main programming model throughout the 9.x line. The version boundary is strict enough to plan around: Fiber 9 follows React 19, and Fiber 8 follows React 18. Release 9.7.0 fixes several reconciler semantics without replacing the public model, while a published v10 alpha already previews different renderer state and scheduling APIs.
Docs5/5The official site documents Canvas defaults, hooks, events, object construction, loading, performance, testing, TypeScript, custom roots, and React Native. It warns that hooks require Canvas context, cached assets are shared, per-frame React state is expensive, and manual roots own resize behavior. The README also tells beginners to learn React and Three.js first. Users still need the separate Three.js reference because Fiber deliberately exposes that engine rather than restating every class.
Maintenance5/5npm published 9.7.0 on July 31, 2026, and GitHub records a push on August 25, 2026. The release fixed 4 reconciler correctness areas, adopted React DOM event priority behavior, and added microtask support. GitHub currently combines 50 open issues and pull requests on an unarchived repository with 31,790 stars. Current 9.x releases and a v10 alpha show active work on both maintenance and the next renderer architecture.
Ecosystem5/5The latest completed week recorded 4,910,054 npm downloads. The README links packages for model-to-JSX conversion, controls, physics, XR, testing, post-processing, accessibility, GPU path tracing, UI, workers, and visual editing. Three.js classes become JSX elements without waiting for a wrapper release. This breadth is useful for a 3D product, though every helper adds its own peer versions, asset costs, and runtime behavior to coordinate.

Use it if

  • A React 19 product needs an interactive Three.js scene whose objects map cleanly to reusable components and application state.
  • The team knows Three.js and wants direct refs to its meshes, materials, cameras, loaders, and renderer while using JSX for composition.
  • Suspense-based asset loading, pointer events, demand rendering, or the pmndrs helper ecosystem will replace substantial local glue.
  • The same scene architecture must cover browser WebGL and a separately configured Expo React Native target.
Skip it if

Setup reality

Our install of @react-three/fiber 9.7.0 completed in 7.5 seconds and left 17 packages using 32 MB. The package is 2276 KB unpacked, with 10 direct dependencies and 8 peer dependencies. npm audit reported zero known vulnerabilities. It ships CommonJS without an exports map; require() and ESM import worked in our Node 22 check. TypeScript declarations are included.

Peer alignment is the first blocker. Fiber 9.7.0 requires React and React DOM >=19 <19.3 plus Three.js >=0.156; Fiber 8 is the documented React 18 line. Browser scenes belong behind a client boundary in server-rendered frameworks. Canvas creates context for useFrame and useThree, so those hooks throw outside its tree. Model and texture URLs still need correct public paths, CORS, loading fallbacks, and error handling.

The default frame loop runs continuously. Use frameloop="demand" for mostly static scenes and call invalidate() after an imperative change. Inside useFrame, mutate Three.js refs and scale movement by the delta in seconds instead of setting React state every frame. useLoader caches by URL; mutating or disposing a cached scene can damage another component using the same object. Reuse geometry and materials, and instance repeated meshes when draw calls become the bottleneck.

Our esbuild test of a namespace import produced 887.8 KB minified and 238.4 KB gzipped. That number excludes GLB files, textures, Drei, physics, post-processing, and decoder binaries. React Native adds the /native entry plus compatible Expo packages, and Metro may need GLB, image, and CJS extensions. On the web, provide a useful DOM fallback for renderer creation, context loss, reduced motion, keyboard use, and devices that cannot sustain the scene's GPU load.

Patterns

Create a lit Three.js scene render-scene

import { Canvas } from '@react-three/fiber'

export function ProductScene() {
  return (
    <Canvas camera={{ position: [0, 0, 5], fov: 45 }}>
      <ambientLight intensity={0.6} />
      <directionalLight position={[4, 5, 3]} intensity={2} />
      <mesh>
        <boxGeometry args={[1, 1, 1]} />
        <meshStandardMaterial color="tomato" />
      </mesh>
    </Canvas>
  )
}

`Canvas` creates the renderer, scene, camera, resize observer, raycaster, and event context in a browser client component.

Advance rotation with frame delta animate-object

import { useRef } from 'react'
import { useFrame } from '@react-three/fiber'
import type { Mesh } from 'three'

function Rotor() {
  const mesh = useRef<Mesh>(null!)
  useFrame((_, delta) => {
    mesh.current.rotation.y += delta * 0.6
  })
  return <mesh ref={mesh}><boxGeometry /><meshNormalMaterial /></mesh>
}

In 9.7.0 the `delta` value is seconds; mutate the mesh ref instead of scheduling React state on every frame.

Subscribe to one renderer value select-canvas-state

import { useThree } from '@react-three/fiber'

function CameraBridge() {
  const camera = useThree((state) => state.camera)
  const invalidate = useThree((state) => state.invalidate)
  return null
}

`useThree` only works beneath `Canvas`; selectors react to store changes, not deep imperative mutations such as `camera.zoom` edits.

Suspend while a GLTF model loads load-gltf

import { Suspense } from 'react'
import { useLoader } from '@react-three/fiber'
import { GLTFLoader } from 'three/examples/jsm/loaders/GLTFLoader.js'

function Model() {
  const gltf = useLoader(GLTFLoader, '/models/chair.glb')
  return <primitive object={gltf.scene} />
}

<Canvas><Suspense fallback={null}><Model /></Suspense></Canvas>

`useLoader` caches the result by URL. Clone before scene-specific mutation and avoid disposing an object shared through that cache.

Start a GLTF request early preload-model

import { useLoader } from '@react-three/fiber'
import { GLTFLoader } from 'three/examples/jsm/loaders/GLTFLoader.js'

useLoader.preload(GLTFLoader, '/models/chair.glb')

Preloading only hits the same cache when the later loader class, URL, and extension setup match.

Stop a click at the nearest mesh handle-mesh-events

function Selectable() {
  return (
    <mesh onClick={(event) => {
      event.stopPropagation()
      choose(event.object.uuid)
    }}>
      <sphereGeometry />
      <meshStandardMaterial color="royalblue" />
    </mesh>
  )
}

R3F events can reach multiple ray intersections; `stopPropagation()` also blocks objects behind the selected mesh.

Wake a static scene after mutation render-on-demand

function Movable() {
  const mesh = useRef(null)
  const invalidate = useThree((state) => state.invalidate)
  return <mesh ref={mesh} onClick={() => {
    mesh.current.position.x += 1
    invalidate()
  }}><boxGeometry /><meshNormalMaterial /></mesh>
}

<Canvas frameloop="demand"><Movable /></Canvas>

`invalidate()` schedules a frame; imperative changes are otherwise invisible to a demand loop.

Reuse geometry and material objects share-gpu-resources

const shape = new THREE.SphereGeometry(1, 24, 24)
const finish = new THREE.MeshStandardMaterial({ color: 'navy' })

function Pair() {
  return <>
    <mesh geometry={shape} material={finish} position={[-2, 0, 0]} />
    <mesh geometry={shape} material={finish} position={[2, 0, 0]} />
  </>
}

Two meshes can share 1 geometry and 1 material, avoiding duplicate GPU compilation and memory.

Update transforms on an InstancedMesh instance-meshes

function Dots({ count = 500 }) {
  const mesh = useRef(null)
  useLayoutEffect(() => {
    const helper = new THREE.Object3D()
    for (let i = 0; i < count; i += 1) {
      helper.position.set(i % 25, Math.floor(i / 25), 0)
      helper.updateMatrix()
      mesh.current.setMatrixAt(i, helper.matrix)
    }
    mesh.current.instanceMatrix.needsUpdate = true
  }, [count])
  return <instancedMesh ref={mesh} args={[undefined, undefined, count]}>
    <circleGeometry args={[0.1, 8]} /><meshBasicMaterial />
  </instancedMesh>
}

Set `instanceMatrix.needsUpdate` after writing transforms; 500 instances then share geometry, material, and a draw call.

Inherit mesh props in TypeScript type-component-props

import type { ThreeElements } from '@react-three/fiber'

type TileProps = ThreeElements['mesh'] & { tone?: string }

function Tile({ tone = 'orange', ...props }: TileProps) {
  return <mesh {...props}><boxGeometry /><meshStandardMaterial color={tone} /></mesh>
}

`ThreeElements['mesh']` derives constructor props, object properties, refs, and R3F events from the installed Three.js types.

Expose a Three.js class as JSX register-three-class

import { extend, useThree } from '@react-three/fiber'
import { OrbitControls } from 'three/examples/jsm/controls/OrbitControls.js'

extend({ OrbitControls })

function Controls() {
  const { camera, gl } = useThree()
  return <orbitControls args={[camera, gl.domElement]} />
}

`extend()` registers runtime construction; TypeScript also needs intrinsic-element augmentation, while Drei's control component supplies that typing and cleanup.

Provide a non-WebGL product view show-webgl-fallback

<Canvas fallback={<img src="/chair.jpg" alt="Blue chair, front view" />}>
  <ChairScene />
</Canvas>

The fallback handles renderer creation failure. Asset errors and later context loss still belong in an error boundary with an equivalent DOM path.

Alternatives

PackageRegistryPick it when
threenpmChoose it for direct scene ownership and an imperative loop without React reconciliation.
react-babylonjsnpmChoose it when React composition should sit on Babylon.js rather than the Three.js engine and ecosystem.
@tresjs/corenpmChoose it for a declarative Three.js renderer in a Vue application.
@react-three/dreinpmAdd it beside Fiber when controls, loaders, staging helpers, and common scene abstractions are the missing layer.

More web frontend guides

postcss · react · react-dom · tailwindcss · htmlparser2 · tailwind-merge · the whole shelf →

How this guide is made: grounded in the library's documentation, release notes, changelog, and issue history, on a fixed rubric — not a hands-on install of every release. The 50 most-downloaded entries are additionally install-verified in clean containers. Corrections: contact the desk.