Comprehensive guide for React Three Fiber and the Poimandres ecosystem. Contains 70+ rules across 12 categories, prioritized by impact.
Sources & Credits
Additional tips from 100 Three.js Tips by Utsubo
When to Apply
Reference these guidelines when:
-
Writing new R3F components
-
Optimizing R3F performance (re-renders are the #1 issue)
-
Using Drei helpers correctly
-
Managing state with Zustand
-
Implementing post-processing or physics
Ecosystem Coverage
-
@react-three/fiber - React renderer for Three.js
-
@react-three/drei - Useful helpers and abstractions
-
@react-three/postprocessing - Post-processing effects
-
@react-three/rapier - Physics engine
-
zustand - State management
-
leva - Debug GUI
Rule Categories by Priority
| 1
| Performance & Re-renders
| CRITICAL
| perf-
| 2
| useFrame & Animation
| CRITICAL
| frame-
| 3
| Component Patterns
| HIGH
| component-
| 4
| Canvas & Setup
| HIGH
| canvas-
| 5
| Drei Helpers
| MEDIUM-HIGH
| drei-
| 6
| Loading & Suspense
| MEDIUM-HIGH
| loading-
| 7
| State Management
| MEDIUM
| state-
| 8
| Events & Interaction
| MEDIUM
| events-
| 9
| Post-processing
| MEDIUM
| postpro-
| 10
| Physics (Rapier)
| LOW-MEDIUM
| physics-
| 11
| Leva (Debug GUI)
| LOW
| leva-
Quick Reference
1. Performance & Re-renders (CRITICAL)
-
perf-never-set-state-in-useframe- NEVER call setState in useFrame -
perf-isolate-state- Isolate components that need React state -
perf-zustand-selectors- Use Zustand selectors, not entire store -
perf-transient-subscriptions- Use transient subscriptions for continuous values -
perf-memo-components- Memoize expensive components -
perf-keys-for-lists- Use stable keys for dynamic lists -
perf-avoid-inline-objects- Avoid creating objects/arrays in JSX -
perf-dispose-auto- Understand R3F auto-dispose behavior -
perf-visibility-toggle- Toggle visibility instead of remounting -
perf-r3f-perf- Use r3f-perf for performance monitoring
2. useFrame & Animation (CRITICAL)
-
frame-priority- Use priority for execution order -
frame-delta-time- Always use delta for animations -
frame-conditional-subscription- Disable useFrame when not needed -
frame-destructure-state- Destructure only what you need -
frame-render-on-demand- Use invalidate() for on-demand rendering -
frame-avoid-heavy-computation- Move heavy work outside useFrame
3. Component Patterns (HIGH)
-
component-jsx-elements- Use JSX for Three.js objects -
component-attach-prop- Use attach for non-standard properties -
component-primitive- Use primitive for existing objects -
component-extend- Use extend() for custom classes -
component-forwardref- Use forwardRef for reusable components -
component-dispose-null- Set dispose={null} on shared resources
4. Canvas & Setup (HIGH)
-
canvas-size-container- Canvas fills parent container -
canvas-camera-default- Configure camera via prop -
canvas-gl-config- Configure WebGL context -
canvas-shadows- Enable shadows at Canvas level -
canvas-frameloop- Choose appropriate frameloop mode -
canvas-events- Configure event handling -
canvas-linear-flat- Use linear/flat for correct colors
5. Drei Helpers (MEDIUM-HIGH)
-
drei-use-gltf- useGLTF with preloading -
drei-use-texture- useTexture for texture loading -
drei-environment- Environment for realistic lighting -
drei-orbit-controls- OrbitControls from Drei -
drei-html- Html for DOM overlays -
drei-text- Text for 3D text -
drei-instances- Instances for optimized instancing -
drei-use-helper- useHelper for debug visualization -
drei-bounds- Bounds to fit camera -
drei-center- Center to center objects -
drei-float- Float for floating animation
6. Loading & Suspense (MEDIUM-HIGH)
-
loading-suspense- Wrap async components in Suspense -
loading-preload- Preload assets with useGLTF.preload -
loading-use-progress- useProgress for loading UI -
loading-lazy-components- Lazy load heavy components -
loading-error-boundary- Handle loading errors
7. State Management (MEDIUM)
-
state-zustand-store- Create focused Zustand stores -
state-avoid-objects-in-store- Be careful with Three.js objects -
state-subscribeWithSelector- Fine-grained subscriptions -
state-persist- Persist state when needed -
state-separate-concerns- Separate stores by concern
8. Events & Interaction (MEDIUM)
-
events-pointer-events- Use pointer events on meshes -
events-stop-propagation- Prevent event bubbling -
events-cursor-pointer- Change cursor on hover -
events-raycast-filter- Filter raycasting -
events-event-data- Understand event data structure
9. Post-processing (MEDIUM)
-
postpro-effect-composer- Use EffectComposer -
postpro-common-effects- Common effects reference -
postpro-selective-bloom- SelectiveBloom for optimized glow -
postpro-custom-shader- Create custom effects -
postpro-performance- Optimize post-processing
10. Physics Rapier (LOW-MEDIUM)
-
physics-setup- Basic Rapier setup -
physics-body-types- dynamic, fixed, kinematic -
physics-colliders- Choose appropriate colliders -
physics-events- Handle collision events -
physics-api-ref- Use ref for physics API -
physics-performance- Optimize physics
11. Leva (LOW)
-
leva-basic- Basic Leva usage -
leva-folders- Organize with folders -
leva-conditional- Hide in production
How to Use
Read individual rule files for detailed explanations and code examples:
rules/perf-never-set-state-in-useframe.md
rules/drei-use-gltf.md
rules/state-zustand-selectors.md
Full Compiled Document
For the complete guide with all rules expanded: ../R3F_BEST_PRACTICES.md
Critical Patterns
NEVER setState in useFrame
// BAD - 60 re-renders per second!
function BadComponent() {
const [position, setPosition] = useState(0);
useFrame(() => {
setPosition(p => p + 0.01); // NEVER DO THIS
});
return <mesh position-x={position} />;
}
// GOOD - Mutate refs directly
function GoodComponent() {
const meshRef = useRef();
useFrame(() => {
meshRef.current.position.x += 0.01;
});
return <mesh ref={meshRef} />;
}
Zustand Selectors
// BAD - Re-renders on ANY store change
const store = useGameStore();
// GOOD - Only re-renders when playerX changes
const playerX = useGameStore(state => state.playerX);
// BETTER - No re-renders, direct mutation
useFrame(() => {
const { value } = useStore.getState();
ref.current.position.x = value;
});
Drei useGLTF
import { useGLTF } from '@react-three/drei';
function Model() {
const { scene } = useGLTF('/model.glb');
return <primitive object={scene} />;
}
// Preload for instant loading
useGLTF.preload('/model.glb');
Suspense Loading
function App() {
return (
<Canvas>
<Suspense fallback={<Loader />}>
<Model />
</Suspense>
</Canvas>
);
}
r3f-perf Monitoring
import { Perf } from 'r3f-perf';
function App() {
return (
<Canvas>
<Perf position="top-left" />
<Scene />
</Canvas>
);
}
Toggle Visibility (Not Remounting)
// BAD: Remounting destroys and recreates
{showModel && <Model />}
// GOOD: Toggle visibility, keeps instance alive
<Model visible={showModel} />