Install any skill in seconds. Free to start, no credit card required.
Get Started Free →React Three Fiber (R3F) and Poimandres ecosystem best practices. Use when writing, reviewing, or optimizing R3F code. Triggers on tasks involving @react-three/fiber, @react-three/drei, zustand, @react-three/postprocessing, @react-three/rapier, or leva.
| Test case | Without → With | Effect | Δ tokens | Δ turns |
|---|---|---|---|---|
| case-08 | ✗→✓ | ▲ Improved | 76% | 0% |
| case-20 | ✓→✓ | = Same ✓ | 109% | 0% |
| case-21 | ✓→✓ | = Same ✓ | 203% | 0% |
| case-01 | ✓→✓ | = Same ✓ | 148% | 0% |
| case-02 | ✓→✓ | = Same ✓ | 148% | 0% |
Comprehensive guide for React Three Fiber and the Poimandres ecosystem. Contains 70+ rules across 12 categories, prioritized by impact.
> Additional tips from 100 Three.js Tips by Utsubo
Reference these guidelines when:
| Priority | Category | Impact | Prefix | |----------|----------|--------|--------| | 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- |
perf-never-set-state-in-useframe - NEVER call setState in useFrameperf-isolate-state - Isolate components that need React stateperf-zustand-selectors - Use Zustand selectors, not entire storeperf-transient-subscriptions - Use transient subscriptions for continuous valuesperf-memo-components - Memoize expensive componentsperf-keys-for-lists - Use stable keys for dynamic listsperf-avoid-inline-objects - Avoid creating objects/arrays in JSXperf-dispose-auto - Understand R3F auto-dispose behaviorperf-visibility-toggle - Toggle visibility instead of remountingperf-r3f-perf - Use r3f-perf for performance monitoringframe-priority - Use priority for execution orderframe-delta-time - Always use delta for animationsframe-conditional-subscription - Disable useFrame when not neededframe-destructure-state - Destructure only what you needframe-render-on-demand - Use invalidate() for on-demand renderingframe-avoid-heavy-computation - Move heavy work outside useFramecomponent-jsx-elements - Use JSX for Three.js objectscomponent-attach-prop - Use attach for non-standard propertiescomponent-primitive - Use primitive for existing objectscomponent-extend - Use extend() for custom classescomponent-forwardref - Use forwardRef for reusable componentscomponent-dispose-null - Set dispose={null} on shared resourcescanvas-size-container - Canvas fills parent containercanvas-camera-default - Configure camera via propcanvas-gl-config - Configure WebGL contextcanvas-shadows - Enable shadows at Canvas levelcanvas-frameloop - Choose appropriate frameloop modecanvas-events - Configure event handlingcanvas-linear-flat - Use linear/flat for correct colorsdrei-use-gltf - useGLTF with preloadingdrei-use-texture - useTexture for texture loadingdrei-environment - Environment for realistic lightingdrei-orbit-controls - OrbitControls from Dreidrei-html - Html for DOM overlaysdrei-text - Text for 3D textdrei-instances - Instances for optimized instancingdrei-use-helper - useHelper for debug visualizationdrei-bounds - Bounds to fit cameradrei-center - Center to center objectsdrei-float - Float for floating animationloading-suspense - Wrap async components in Suspenseloading-preload - Preload assets with useGLTF.preloadloading-use-progress - useProgress for loading UIloading-lazy-components - Lazy load heavy componentsloading-error-boundary - Handle loading errorsstate-zustand-store - Create focused Zustand storesstate-avoid-objects-in-store - Be careful with Three.js objectsstate-subscribeWithSelector - Fine-grained subscriptionsstate-persist - Persist state when neededstate-separate-concerns - Separate stores by concernevents-pointer-events - Use pointer events on meshesevents-stop-propagation - Prevent event bubblingevents-cursor-pointer - Change cursor on hoverevents-raycast-filter - Filter raycastingevents-event-data - Understand event data structurepostpro-effect-composer - Use EffectComposerpostpro-common-effects - Common effects referencepostpro-selective-bloom - SelectiveBloom for optimized glowpostpro-custom-shader - Create custom effectspostpro-performance - Optimize post-processingphysics-setup - Basic Rapier setupphysics-body-types - dynamic, fixed, kinematicphysics-colliders - Choose appropriate collidersphysics-events - Handle collision eventsphysics-api-ref - Use ref for physics APIphysics-performance - Optimize physicsleva-basic - Basic Leva usageleva-folders - Organize with foldersleva-conditional - Hide in productionRead 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.mdFor the complete guide with all rules expanded: ../R3F_BEST_PRACTICES.md
jsx// 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} />; }
jsx// 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; });
jsximport { useGLTF } from '@react-three/drei'; function Model() { const { scene } = useGLTF('/model.glb'); return <primitive object={scene} />; } // Preload for instant loading useGLTF.preload('/model.glb');
jsxfunction App() { return ( <Canvas> <Suspense fallback={<Loader />}> <Model /> </Suspense> </Canvas> ); }
jsximport { Perf } from 'r3f-perf'; function App() { return ( <Canvas> <Perf position="top-left" /> <Scene /> </Canvas> ); }
jsx// BAD: Remounting destroys and recreates {showModel && <Model />} // GOOD: Toggle visibility, keeps instance alive <Model visible={showModel} />
Other measured skills in the registry, with their headline benchmark lift.