Core R3F/Three.js performance optimization principles. Use when FPS drops below 60.
"Optimize for mobile, scale up for desktop ā 60 FPS is the goal."
Use when:
| System | Budget | Notes |
|---|---|---|
| Input | ~1ms | Event handling |
| Physics | ~3ms | Rapier/Cannon updates |
| Game Logic | ~4ms | State, AI, animations |
| Render | ~5ms | Three.js draw calls |
| Buffer | ~3ms | Safety margin |
| Total | 16.67ms | 60 FPS target |
// Performance-optimized Canvas
<Canvas
dpr={[1, 2]} // Limit pixel ratio
performance={{ min: 0.5 }} // Auto-reduce quality
gl={{ antialias: false }} // Disable for mobile
>
<Suspense fallback={null}>
<Scene />
</Suspense>
</Canvas>
| Symptom | Likely Cause | Solution |
|---|---|---|
| Low FPS everywhere | Too many draw calls | Instancing, merging |
| FPS drops on zoom | LOD not implemented | Add LOD system |
| Mobile slow | DPR too high | Limit to 1.5 |
| Memory grows | Dispose missing | Add cleanup |
| Stuttering | GC pressure | Object pooling |
// Limit device pixel ratio
<Canvas dpr={Math.min(window.devicePixelRatio, 2)}>
// Disable expensive features on mobile
const isMobile = /iPhone|iPad|Android/i.test(navigator.userAgent);
<Canvas
shadows={!isMobile}
gl={{
antialias: !isMobile,
powerPreference: 'high-performance',
}}
>
// Reuse Vector3, Quaternion instances
const position = useRef(new THREE.Vector3());
const rotation = useRef(new THREE.Quaternion());
useFrame(() => {
position.current.set(0, 0, 0); // Reuse, don't create
});
// CRITICAL: Dispose of Three.js objects
useEffect(() => {
const geometry = new THREE.BoxGeometry();
const material = new THREE.MeshStandardMaterial();
return () => {
geometry.dispose();
material.dispose();
if (material.map) material.map.dispose();
};
}, []);
import { useFrame } from '@react-three/fiber';
import { useRef } from 'react';
function PerformanceMonitor() {
const frameCount = useRef(0);
const lastTime = useRef(performance.now());
useFrame(() => {
frameCount.current++;
const now = performance.now();
if (now - lastTime.current >= 1000) {
console.log(`FPS: ${frameCount.current}`);
frameCount.current = 0;
lastTime.current = now;
}
});
return null;
}
ā DON'T:
ā DO: