React Three Fiber: Building 3D Web Experiences with React and Three.js

Three-dimensional web experiences are no longer the exclusive domain of game developers. With React Three Fiber (R3F), a React renderer for Three.js, frontend developers can compose 3D scenes using the declarative component model they already know. R3F bridges the gap between Three.js's imperative API and React's component-driven architecture, making WebGL accessible without sacrificing performance.
Setting Up a React Three Fiber Scene
Getting started with R3F requires three core packages: @react-three/fiber for the renderer, @react-three/drei for helpful utilities, and three as the peer dependency. A minimal scene looks like this:
import { Canvas } from '@react-three/fiber'
import { OrbitControls } from '@react-three/drei'
function Scene() {
return (
<Canvas camera={{ position: [2, 2, 5], fov: 50 }}>
<ambientLight intensity={0.5} />
<directionalLight position={[5, 5, 5]} />
<mesh>
<boxGeometry args={[1, 1, 1]} />
<meshStandardMaterial color="#2563eb" />
</mesh>
<OrbitControls />
</Canvas>
)
}
The Canvas component replaces Three.js's WebGLRenderer and Scene setup. Every Three.js object becomes a React component: geometries, materials, lights, and controls all map to declarative JSX elements. Drei's OrbitControls adds mouse-based camera rotation and zoom out of the box, saving dozens of lines of imperative boilerplate.
Lighting and Materials for Realistic Scenes
Three.js offers several light types that R3F exposes as components. Ambient light provides base illumination, directional light simulates sunlight, point light radiates from a single origin, and spot light creates a focused cone. Combining them produces realistic environments:
function Lighting() {
return (
<>
<ambientLight intensity={0.3} />
<pointLight position={[2, 3, 4]} intensity={1} color="#ff6b6b" />
<spotLight
position={[-3, 5, 2]}
angle={0.3}
penumbra={0.5}
intensity={2}
castShadow
/>
<hemisphereLight
args={['#87ceeb', '#f5f5dc', 0.6]}
/>
</>
)
}
Materials control how surfaces react to light. meshStandardMaterial handles PBR (physically based rendering) with properties for roughness, metalness, and environment maps. For more performance-sensitive scenes, meshPhongMaterial offers a lighter alternative with good visual quality.
Animations with useFrame
R3F's useFrame hook runs on every animation frame, analogous to Three.js's animation loop. It receives the state object containing clock, camera, and scene references:
import { useFrame } from '@react-three/fiber'
import { useRef } from 'react'
function RotatingCube() {
const ref = useRef()
useFrame((state, delta) => {
ref.current.rotation.x += delta * 0.5
ref.current.rotation.y += delta * 0.3
ref.current.position.y = Math.sin(state.clock.elapsedTime) * 0.5
})
return (
<mesh ref={ref}>
<boxGeometry args={[1, 1, 1]} />
<meshStandardMaterial color="#2563eb" />
</mesh>
)
}
The delta parameter provides frame-time independent movement, ensuring consistent animation speed across different refresh rates. For complex animations, libraries like @react-three/rapier add physics simulation, while @react-spring/three bridges spring-based animations from react-spring into 3D.
Interactivity and Raycasting
R3F handles user interaction through Three.js raycasting under the hood. Events like onClick, onPointerOver, and onPointerOut work exactly as they do in DOM React:
function InteractiveMesh() {
const [hovered, setHovered] = useState(false)
const [clicked, setClicked] = useState(false)
return (
<mesh
onClick={() => setClicked(!clicked)}
onPointerOver={() => setHovered(true)}
onPointerOut={() => setHovered(false)}
scale={hovered ? 1.2 : 1}
>
<sphereGeometry args={[1, 32, 32]} />
<meshStandardMaterial
color={clicked ? '#ef4444' : '#2563eb'}
roughness={hovered ? 0.2 : 0.5}
/>
</mesh>
)
}
For advanced interactivity, the useCursor hook from Drei changes the CSS cursor on hover, and Html components from Drei overlay DOM elements onto 3D surfaces—perfect for tooltips, labels, or UI controls embedded in the scene.
Performance Optimization for the Browser
WebGL performance degrades quickly without discipline. Start by reducing geometry complexity: use Drei's <Float> and <Text> components instead of custom geometry where possible. Enable frameloop="demand" on the Canvas to stop rendering when nothing changes. Use performance={{ max: 0.5 }} to cap the pixel ratio on high-DPI devices:
<Canvas
dpr={[1, 1.5]}
performance={{ max: 0.5 }}
frameloop="demand"
gl={{ antialias: true, alpha: false }}
>
For scenes with many objects, implement instancing with @react-three/rapier or merge geometries manually. Use Drei's <Instances> component to render thousands of similar objects efficiently. Always test on lower-end devices and throttle CPU in DevTools to catch performance bottlenecks early.
React Three Fiber transforms 3D web development from a specialized skill into an extension of everyday React knowledge. By composing scenes declaratively, reusing Drei utilities, and following performance best practices, your team can deliver immersive experiences that load fast and run smoothly.
Ready to bring 3D to your next project? <a href="/services/web-development">Our web development team</a> specializes in React Three Fiber integration and can help turn your concept into an interactive reality. Get in touch to discuss your vision.
Related Insights

Building Accessible React Applications: WCAG 2.2 Compliance Guide
A guide to building WCAG 2.2 compliant React applications including semantic HTML, ARIA attributes, keyboard navigation, focus management, and automated accessibility testing.

Code Splitting and Lazy Loading in React: Performance Optimization Guide
A comprehensive guide to code splitting and lazy loading in React applications including React.lazy, Suspense, route-based splitting, and component-level chunking.

Building a Design System with React, TypeScript, and Storybook
A complete guide to building a scalable design system using React, TypeScript, and Storybook including component architecture, theming, accessibility, and documentation.