executing-plans
executing-plans
Use when executing an implementation plan in the current session as the implementer yourself — your human partner chose inline execution, or no subagent tool is available
react-three-fiberBuilds 3D scenes in React with React Three Fiber (R3F), a React renderer for Three.js where meshes, lights and cameras are JSX components. Use when a user asks to add a 3D scene to a React or Next.js site, build a product configurator, load a GLTF model, animate with useFrame, handle click and hover on 3D objects, or speed up a slow R3F scene.
Independent GitHub snapshot · Automated classification · Original documentation governs
React Three Fiber (R3F) is a React renderer for Three.js. You write scenes as JSX (<mesh>, <boxGeometry>, <meshStandardMaterial>), and props map straight onto Three.js object properties. Hooks, state, Suspense and context work as in any React app, and everything in the Three.js ecosystem stays available. @react-three/drei adds ready-made helpers (controls, loaders, environment maps, text, HTML overlays).
Current versions: @react-three/fiber 9.8, @react-three/drei 10.7, three 0.186. R3F 9 requires React 19; with React 18 install @react-three/fiber@8 and @react-three/drei@9.
npm install three @react-three/fiber @react-three/drei
npm install -D @types/three # TypeScript
npm install @react-three/postprocessing # optional: bloom, depth of field, etc.
In Next.js, components that render a <Canvas> must be client components ('use client'). If a Three.js add-on ships untranspiled code, add transpilePackages: ['three'] to next.config.js.
'use client'
import { Canvas } from '@react-three/fiber'
import { OrbitControls, Environment } from '@react-three/drei'
export default function Scene() {
return (
<Canvas camera={{ position: [0, 2, 5], fov: 50 }} dpr={[1, 2]} shadows>
<ambientLight intensity={0.4} />
<directionalLight position={[5, 5, 5]} castShadow />
<mesh position={[0, 1, 0]} castShadow>
<boxGeometry args={[1, 1, 1]} />
<meshStandardMaterial color="orange" />
</mesh>
<mesh rotation={[-Math.PI / 2, 0, 0]} receiveShadow>
<planeGeometry args={[10, 10]} />
<meshStandardMaterial color="#444" />
</mesh>
<OrbitControls />
<Environment preset="city" />
</Canvas>
)
}
Canvas fills its parent, so give the parent a height. Defaults: dpr [1, 2], sRGB output and ACES filmic tone mapping, a translucent WebGL renderer with antialiasing. Pass fallback={<p>WebGL is not available</p>} for devices without WebGL.
import { useRef } from 'react'
import { useFrame } from '@react-three/fiber'
import * as THREE from 'three'
function SpinningBox() {
const meshRef = useRef<THREE.Mesh>(null)
useFrame((state, delta) => {
if (!meshRef.current) return
meshRef.current.rotation.y += delta // frame-rate independent
meshRef.current.position.x = THREE.MathUtils.lerp(
meshRef.current.position.x, state.pointer.x * 2, 0.1, // pointer is -1..1; state.mouse is deprecated
)
})
return (
<mesh ref={meshRef}>
<boxGeometry />
<meshStandardMaterial color="hotpink" />
</mesh>
)
}
Mutate refs inside useFrame; never call setState there. useThree() returns the renderer, scene, camera, viewport and size.
import { Suspense, useEffect } from 'react'
import { useGLTF, useAnimations } from '@react-three/drei'
function Character({ animation = 'idle' }: { animation?: string }) {
const { scene, animations } = useGLTF('/models/character.glb')
const { actions } = useAnimations(animations, scene)
useEffect(() => {
actions[animation]?.reset().fadeIn(0.3).play()
return () => { actions[animation]?.fadeOut(0.3) }
}, [animation, actions])
return <primitive object={scene} scale={1.5} />
}
useGLTF.preload('/models/character.glb')
// <Canvas><Suspense fallback={null}><Character /></Suspense></Canvas>
useGLTF decodes Draco-compressed files out of the box. To get a typed JSX component for a model and a compressed copy, run npx gltfjsx model.glb --transform --types; it writes model-transformed.glb (Draco, resized webp textures, pruned) and a component.
import { useState } from 'react'
function InteractiveBox() {
const [hovered, setHovered] = useState(false)
const [active, setActive] = useState(false)
return (
<mesh
scale={active ? 1.5 : 1}
onClick={(e) => { e.stopPropagation(); setActive(!active) }}
onPointerOver={() => setHovered(true)}
onPointerOut={() => setHovered(false)}
>
<boxGeometry />
<meshStandardMaterial color={hovered ? 'hotpink' : 'orange'} />
</mesh>
)
}
R3F raycasts pointer events onto meshes. Use e.stopPropagation() so objects behind the hit do not also fire, and onPointerMissed on Canvas for clicks on empty space.
<Canvas frameloop="demand">…</Canvas>
// elsewhere: const invalidate = useThree((s) => s.invalidate); invalidate()
With frameloop="demand" a frame is drawn only when props change or invalidate() is called; useful for static configurators on laptops and phones.
User prompt: "Show our sneaker.glb on the product page, let people rotate it, and keep the page fast."
Create a 'use client' component with <Canvas frameloop="demand" camera={{ position: [0, 0, 3] }}>, wrap a <Stage> or <Environment preset="studio" /> plus <OrbitControls makeDefault /> and a <Suspense fallback={null}> around the model. Run npx gltfjsx public/sneaker.glb --transform --types first and use sneaker-transformed.glb. Result: a draggable model that renders frames only while the user interacts, with the file typically 70-90% smaller.
User prompt: "Our star field with 5,000 meshes drops to 15 fps. Fix it."
Replace the 5,000 <mesh> elements with one <Instances limit={5000}> (from drei) holding a shared geometry and material, and render each star as <Instance position={…} />; for pure animation use a single <instancedMesh args={[undefined, undefined, 5000]}> and update matrices in useFrame. Result: one draw call instead of 5,000, and the frame rate recovers.
<Canvas>; DOM elements go outside, or inside it through drei's <Html>.requestAnimationFrame or React state for per-frame values; use useFrame with refs.useMemo), not on every render or frame.<Suspense>; useGLTF, useTexture and useLoader suspend and cache by URL. R3F disposes objects when they unmount, so do not dispose shared cached assets by hand.<AdaptiveDpr> and <PerformanceMonitor> from drei to lower resolution on slow devices.flat to drop tone mapping, linear to drop colour conversion, only when you know why.args on a geometry or object are constructor arguments; changing them recreates the object, so animate properties instead.THREE.WebGPURenderer through an async gl factory (see the Canvas docs), but treat it as advanced.@react-three/rapier.