name: r3f-performance-optimization description: Use when optimizing React Three Fiber scenes, experiencing frame drops, high draw calls, or implementing 3D performance features - ensures systematic performance analysis and applies proven optimization patterns for smooth 60fps rendering
R3F Performance Optimization Skill
When This Skill Applies
Use this skill when:
- Frame rates drop below 60fps in R3F scenes
- Draw call count exceeds 500-1000
- Implementing new 3D features that may impact performance
- Scene contains many similar objects (>50 meshes)
- Static scenes render continuously
- Performance varies significantly across devices
- Memory usage grows during scene interaction
Performance Optimization Checklist
When optimizing R3F performance, complete each step systematically:
☐ 1. Measure Current Performance
Required before any optimization:
import { Stats } from '@react-three/drei'
// or
import { Perf } from 'r3f-perf'
<Canvas>
<Stats /> {/* Basic FPS/memory */}
<Perf position="top-left" /> {/* Advanced: calls, triangles, textures */}
<Scene />
</Canvas>
Record baseline metrics:
- Current FPS (target: 60fps)
- Draw call count (target: <500, max: 1000)
- Triangle/vertex count
- Memory usage patterns
☐ 2. Enable On-Demand Rendering
For static or mostly-static scenes:
<Canvas frameloop="demand">
{/* Only renders when props change or invalidate() called */}
</Canvas>
With manual control:
const { invalidate } = useThree()
// Before animation starts
invalidate()
requestAnimationFrame(() => {
// Synchronous animation
})
Impact: Can reduce CPU/GPU usage by 90%+ for static scenes.
☐ 3. Apply Instancing for Repeated Objects
When drawing >50 similar objects:
Replace individual meshes:
// BEFORE: 1000 draw calls
{objects.map(obj => <mesh key={obj.id} geometry={...} material={...} />)}
With InstancedMesh:
// AFTER: 1 draw call
<instancedMesh args={[geometry, material, 1000]}>
{/* Manage transforms programmatically */}
</instancedMesh>
Impact: 100-1000x draw call reduction.
☐ 4. Implement Level of Detail (LOD)
For objects at varying distances:
import { Detailed } from '@react-three/drei'
<Detailed distances={[0, 10, 20]}>
<HighPolyMesh /> {/* 0-10 units from camera */}
<MediumPolyMesh /> {/* 10-20 units */}
<LowPolyMesh /> {/* 20+ units */}
</Detailed>
Impact: Reduces vertex processing for distant objects without visible quality loss.
☐ 5. Reuse Geometries and Materials
Create once, reference many times:
// WRONG: Creates new geometry/material per mesh
function Cubes() {
return cubes.map(cube => (
<mesh>
<boxGeometry />
<meshStandardMaterial />
</mesh>
))
}
// CORRECT: Shared resources
const sharedGeometry = new THREE.BoxGeometry()
const sharedMaterial = new THREE.MeshStandardMaterial()
function Cubes() {
return cubes.map(cube => (
<mesh geometry={sharedGeometry} material={sharedMaterial} />
))
}
Critical: Each unique geometry/material requires GPU compilation.
☐ 6. Use Delta-Based Animations
Ensure refresh-rate independence:
useFrame((state, delta) => {
// WRONG: Fixed speed, varies by refresh rate
mesh.current.rotation.x += 0.01
// CORRECT: Same speed everywhere
mesh.current.rotation.x += delta * rotationSpeed
})
Best Practice: Mutate directly in useFrame, don't trigger React re-renders.
☐ 7. Implement Movement Regression
For dynamic quality scaling:
const { performance } = useThree()
useFrame(() => {
// Scale quality based on interaction
const pixelRatio = performance.current * 2 // 0-1 range
gl.setPixelRatio(pixelRatio)
})
// Trigger during interaction
const onInteraction = () => {
performance.regress()
}
Impact: Maintains smoothness during movement, restores quality when static.
☐ 8. Add Adaptive Quality Monitoring
For device-specific optimization:
import { PerformanceMonitor } from '@react-three/drei'
<PerformanceMonitor
onIncline={() => setQuality(q => Math.min(q + 1, 3))}
onDecline={() => setQuality(q => Math.max(q - 1, 1))}
>
<Scene quality={quality} />
</PerformanceMonitor>
Pattern: Adjust shadows, textures, effects, or instance counts based on FPS.
☐ 9. Optimize Asset Loading
Progressive loading strategy:
// Preload in background
useGLTF.preload('/heavy-model.glb')
// Nested Suspense for quality transitions
<Suspense fallback={<Placeholder />}>
<Suspense fallback={<LowQualityModel />}>
<HighQualityModel />
</Suspense>
</Suspense>
Key: useLoader automatically caches identical URLs.
☐ 10. Use React 18 Concurrent Features
For heavy synchronous operations:
import { startTransition } from 'react'
startTransition(() => {
// Heavy state updates distributed across frames
setMassiveDataset(computeExpensiveValue())
})
Benchmark: Maintains ~60fps vs ~5fps without transition.
Common Performance Anti-Patterns
When reviewing code, actively look for and fix:
❌ Creating resources in render
// WRONG
<mesh>
<boxGeometry args={[1, 1, 1]} /> {/* Creates new geometry every render */}
</boxGeometry>
❌ Excessive draw calls
- More than 1000 individual meshes
- Not using instancing for repeated objects
❌ Continuous rendering for static scenes
- No frameloop="demand" when scene is mostly static
❌ Fixed animation values
useFrame(() => {
mesh.rotation.x += 0.01 // WRONG: refresh-rate dependent
})
❌ Missing LOD for varied distances
- Same detail level for near and far objects
❌ Blocking heavy operations
- Synchronous operations without startTransition
Verification Steps
After applying optimizations, verify:
- FPS: Stable 60fps on target devices
- Draw calls: <500 (optimal), <1000 (max)
- Memory: No leaks during interaction
- Smoothness: No visible frame drops during animation
- Consistency: Performance across different refresh rates
When Optimizations Are Complete
Only mark optimization complete when:
- ✅ Baseline metrics recorded
- ✅ All applicable checklist items addressed
- ✅ Verification shows stable 60fps
- ✅ No performance anti-patterns remain
- ✅ Monitoring tools (Stats/Perf) integrated
Additional Resources
For advanced patterns, consult:
- Advanced Reference:
docs/r3f-jotai-advanced-reference.md - Scaling Performance: https://r3f.docs.pmnd.rs/advanced/scaling-performance
- Performance Pitfalls: https://docs.pmnd.rs/react-three-fiber/advanced/pitfalls