Loading...
Loading...
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.
npx skill4agent add emalorenzo/three-agent-skills r3f-best-practicesAdditional tips from 100 Three.js Tips by Utsubo
| Priority | Category | Impact | Prefix |
|---|---|---|---|
| 1 | Performance & Re-renders | CRITICAL | |
| 2 | useFrame & Animation | CRITICAL | |
| 3 | Component Patterns | HIGH | |
| 4 | Canvas & Setup | HIGH | |
| 5 | Drei Helpers | MEDIUM-HIGH | |
| 6 | Loading & Suspense | MEDIUM-HIGH | |
| 7 | State Management | MEDIUM | |
| 8 | Events & Interaction | MEDIUM | |
| 9 | Post-processing | MEDIUM | |
| 10 | Physics (Rapier) | LOW-MEDIUM | |
| 11 | Leva (Debug GUI) | LOW | |
perf-never-set-state-in-useframeperf-isolate-stateperf-zustand-selectorsperf-transient-subscriptionsperf-memo-componentsperf-keys-for-listsperf-avoid-inline-objectsperf-dispose-autoperf-visibility-toggleperf-r3f-perfframe-priorityframe-delta-timeframe-conditional-subscriptionframe-destructure-stateframe-render-on-demandframe-avoid-heavy-computationcomponent-jsx-elementscomponent-attach-propcomponent-primitivecomponent-extendcomponent-forwardrefcomponent-dispose-nullcanvas-size-containercanvas-camera-defaultcanvas-gl-configcanvas-shadowscanvas-frameloopcanvas-eventscanvas-linear-flatdrei-use-gltfdrei-use-texturedrei-environmentdrei-orbit-controlsdrei-htmldrei-textdrei-instancesdrei-use-helperdrei-boundsdrei-centerdrei-floatloading-suspenseloading-preloadloading-use-progressloading-lazy-componentsloading-error-boundarystate-zustand-storestate-avoid-objects-in-storestate-subscribeWithSelectorstate-persiststate-separate-concernsevents-pointer-eventsevents-stop-propagationevents-cursor-pointerevents-raycast-filterevents-event-datapostpro-effect-composerpostpro-common-effectspostpro-selective-bloompostpro-custom-shaderpostpro-performancephysics-setupphysics-body-typesphysics-collidersphysics-eventsphysics-api-refphysics-performanceleva-basicleva-foldersleva-conditionalrules/perf-never-set-state-in-useframe.md
rules/drei-use-gltf.md
rules/state-zustand-selectors.md../R3F_BEST_PRACTICES.md// 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} />;
}// 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;
});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');function App() {
return (
<Canvas>
<Suspense fallback={<Loader />}>
<Model />
</Suspense>
</Canvas>
);
}import { Perf } from 'r3f-perf';
function App() {
return (
<Canvas>
<Perf position="top-left" />
<Scene />
</Canvas>
);
}// BAD: Remounting destroys and recreates
{showModel && <Model />}
// GOOD: Toggle visibility, keeps instance alive
<Model visible={showModel} />