threejs-r3f
Three.js / React Three Fiber
Guide for building 3D web experiences with R3F v9 (React 19) and the pmndrs ecosystem.
Version Constraints
| Package | Version | React |
|---|---|---|
@react-three/fiber |
v9.x | 19.x |
@react-three/drei |
v9.x+ | 19.x |
@react-three/rapier |
v2.x | 19.x |
three |
r171+ | — |
Use fiber v8 + rapier v1 for React 18 projects. Never mix version lines.
Installation
Standalone Vite + R3F app
bun create vite my-scene -- --template react-ts
cd my-scene
bun add three @react-three/fiber @react-three/drei
bun add -d @types/three
bun dev
Into an existing React 19 project
bun add three @react-three/fiber @react-three/drei
bun add -d @types/three
Optional packages: bun add @react-three/rapier (physics), bun add zustand (state), bun add leva (debug GUI).
Scene Setup
For the minimal scene template, Canvas prop table, frameloop values, responsive canvas, and WebGPU renderer bootstrap, read references/scene-setup.md.
Key facts to keep in mind without reading the reference:
Canvasdefaults:antialias: true,outputColorSpace = SRGBColorSpace,toneMapping = ACESFilmicToneMapping,ColorManagement.enabled = true- Always set
dpr={[1, 2]}to clamp device pixel ratio - Use
shadows="soft"for shadow maps; addcastShadow/receiveShadowto meshes frameloop="demand"for static scenes that only need to re-render on interaction
Drei Helpers — Top 15
All import from @react-three/drei. Read references/drei-helpers.md for full inventory, props, and examples.
Controls
OrbitControls— rotate/zoom/pan; addmakeDefaultto expose viauseThreeScrollControls— scroll-driven scenes; pair withuseScrollinsideuseFramePresentationControls— spring-animated drag; no OrbitControls neededKeyboardControls— typed key state context; read imperatively viaget()inuseFrame
Staging
Environment— IBL from preset ('sunset' | 'warehouse' | 'city' | ...) or custom HDRStage— one-component scene staging: lighting, camera fit, shadowsContactShadows— fake soft shadow on a plane; cheaper than shadow mapsFloat— floating/bobbing idle animation for hero objects
Shapes / Content
Text— SDF 3D text with font loading, line wrapping, outlinesHtml— embed DOM content in 3D; supportsoccludeandtransform
Loaders
useGLTF— load and cache GLTF/GLB; auto-configures Draco CDN decoderuseTexture— load and cache textures; object form for PBR maps
Performance
Instances/Instance— instanced meshes with simple component APIDetailed— LOD: show different meshes by camera distancePerformanceMonitor— FPS callbacks; pair withAdaptiveDpr
Materials
MeshTransmissionMaterial— physically-based glass with refraction and chromatic aberration
Asset Pipeline
GLTF Loading
import { useGLTF } from '@react-three/drei'
import { Suspense } from 'react'
function Model(props) {
const { nodes, materials } = useGLTF('/model.glb')
return (
<group {...props} dispose={null}>
<mesh geometry={nodes.Body.geometry} material={materials.Metal} castShadow />
</group>
)
}
useGLTF.preload('/model.glb')
// Wrap in Suspense
<Canvas>
<Suspense fallback={null}>
<Model />
</Suspense>
</Canvas>
gltfjsx CLI
# Generate TypeScript component + Draco-compress the GLB
npx gltfjsx model.glb --transform --types --shadows
# Outputs: model-transformed.glb (move to /public) + Model.tsx (move to /src/components)
--transform shrinks most models 70–90% via Draco geometry compression, 1024px texture resize, and WebP conversion.
For clone pattern, KTX2 textures, lazy loading, and parallel preloading, read references/r3f-patterns.md.
Performance Rules
Draw call budgets:
- Mobile: < 100 draw calls
- Desktop: < 300 draw calls
The 7 anti-patterns — never do these:
- Never
setStateinsideuseFrame— mutate refs directly - Always use
deltafor animation — never fixed increments - Never
setStateinonPointerMove— mutate refs directly - Never read reactive store state in
useFrame— usestore.getState()imperative form - Never conditionally mount/unmount in render loop — use
visibleprop instead - Never create
new THREE.Vector3()insideuseFrame— allocate outside, reuse with.set() - Never create duplicate geometries/materials for identical meshes — share via
useMemoorInstances
Disposal: Always call geometry.dispose() and material.dispose() in useEffect cleanup for dynamically created Three.js objects.
Read references/performance.md for all 7 anti-patterns with wrong/correct code pairs, texture optimization, frustum culling, stats tooling, and mobile rules.
State, Physics, Debug GUI, and WebGPU
For these topics, read references/extras.md:
- Zustand imperative pattern (required for
useFramecorrectness) - Leva debug GUI setup
- Rapier physics quick start
- WebGPU renderer bootstrap
For the full Rapier v2 API (all collider types, collision events, sensors, joints, forces, InstancedRigidBodies), read references/physics.md.
Reference Files
references/scene-setup.md— Minimal scene template, Canvas defaults, frameloop, responsive canvas, WebGPU rendererreferences/r3f-patterns.md— Canvas props,useFrame,useThree, event system, scroll-driven scenes, GLTF clone pattern, KTX2, lazy loadingreferences/drei-helpers.md— Full Drei inventory by category: every helper with import, key props, and examplereferences/performance.md— All 7 anti-patterns with wrong/correct code, instancing, LOD, texture compression, disposal checklist, mobile rulesreferences/physics.md— Full Rapier v2: colliders, collision events, sensors, 6 joints, forces/impulses, InstancedRigidBodiesreferences/extras.md— Zustand imperative pattern, Leva debug GUI, Rapier quick start, WebGPU bootstrap