React & React Three Fiber Integration Guide
This guide covers integrating 3Lens with React applications, including React Three Fiber (R3F) projects.
Table of Contents
- Quick Start
- Installation
- Basic Setup
- React Three Fiber Integration
- Hooks Reference
- Entity Registration
- Advanced Patterns
- Next.js & SSR
- TypeScript Support
- Best Practices
- Common Pitfalls
- Troubleshooting
Quick Start
Get up and running in under 2 minutes:
// 1. Install dependencies
// npm install @3lens/core @3lens/overlay @3lens/react-bridge @react-three/fiber three
// 2. Create your app with 3Lens
import { ThreeLensCanvas } from '@3lens/react-bridge';
function App() {
return (
<ThreeLensCanvas config={{ appName: 'My R3F App' }}>
<ambientLight />
<mesh>
<boxGeometry />
<meshStandardMaterial color="hotpink" />
</mesh>
</ThreeLensCanvas>
);
}
// 3. Press Ctrl+Shift+D to toggle the devtools overlay!2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
That's it! The overlay will appear showing FPS, draw calls, scene hierarchy, and more.
Installation
npm install @3lens/core @3lens/overlay @3lens/react-bridgeFor React Three Fiber projects:
npm install @3lens/core @3lens/overlay @3lens/react-bridge @react-three/fiberBasic Setup
ThreeLensProvider
Wrap your application with ThreeLensProvider:
import React from 'react';
import { ThreeLensProvider } from '@3lens/react-bridge';
import { Canvas } from '@react-three/fiber';
import { Scene } from './Scene';
function App() {
return (
<ThreeLensProvider
config={{
appName: 'My R3F App',
debug: false,
showOverlay: true,
toggleShortcut: 'ctrl+shift+d',
}}
>
<Canvas>
<Scene />
</Canvas>
</ThreeLensProvider>
);
}
export default App;2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
Provider Configuration
interface ThreeLensProviderConfig {
/**
* Application name displayed in the devtools
*/
appName?: string;
/**
* Enable debug logging
* @default false
*/
debug?: boolean;
/**
* Show overlay on mount
* @default true
*/
showOverlay?: boolean;
/**
* Keyboard shortcut to toggle overlay
* @default 'ctrl+shift+d'
*/
toggleShortcut?: string;
/**
* Performance rules and thresholds
*/
rules?: {
maxDrawCalls?: number;
maxTriangles?: number;
maxFrameTimeMs?: number;
maxTextures?: number;
maxTextureMemory?: number;
};
/**
* Sampling configuration
*/
sampling?: {
frameStatsInterval?: number;
snapshotInterval?: number;
enableGpuTiming?: boolean;
enableResourceTracking?: boolean;
};
}2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
React Three Fiber Integration
Using ThreeLensCanvas
For the easiest R3F integration, use the pre-configured ThreeLensCanvas:
import React from 'react';
import { ThreeLensCanvas } from '@3lens/react-bridge';
function App() {
return (
<ThreeLensCanvas
config={{
appName: 'My R3F Game',
}}
// Regular R3F Canvas props
camera={{ position: [0, 0, 5] }}
shadows
>
<ambientLight intensity={0.5} />
<directionalLight position={[10, 10, 5]} />
<mesh>
<boxGeometry args={[1, 1, 1]} />
<meshStandardMaterial color="orange" />
</mesh>
</ThreeLensCanvas>
);
}2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
Manual R3F Connection
For more control, use the useR3FConnection hook:
import React from 'react';
import { Canvas } from '@react-three/fiber';
import { ThreeLensProvider, useR3FConnection } from '@3lens/react-bridge';
function SceneContent() {
// Automatically connects probe to R3F's renderer and scene
useR3FConnection();
return (
<>
<ambientLight intensity={0.5} />
<directionalLight position={[10, 10, 5]} />
<mesh>
<boxGeometry args={[1, 1, 1]} />
<meshStandardMaterial color="orange" />
</mesh>
</>
);
}
function App() {
return (
<ThreeLensProvider config={{ appName: 'My App' }}>
<Canvas>
<SceneContent />
</Canvas>
</ThreeLensProvider>
);
}2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
R3F Connector Factory
For advanced setups, create a custom connector:
import { createR3FConnector } from '@3lens/react-bridge';
import { useThree, useFrame } from '@react-three/fiber';
// Create the connector with R3F hooks
const { R3FProbe, useR3FProbe } = createR3FConnector({
useThree,
useFrame,
});
function Scene() {
// Use inside your R3F canvas
useR3FProbe();
return (
<mesh>
<boxGeometry />
<meshStandardMaterial />
</mesh>
);
}2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
Hooks Reference
useThreeLensProbe
Access the probe instance directly:
import { useThreeLensProbe } from '@3lens/react-bridge';
function MyComponent() {
const probe = useThreeLensProbe();
const handleExport = () => {
const snapshot = probe.getSnapshot();
console.log('Scene snapshot:', snapshot);
};
return <button onClick={handleExport}>Export Scene</button>;
}2
3
4
5
6
7
8
9
10
11
12
useSelectedObject
Track and control selection state:
import { useSelectedObject } from '@3lens/react-bridge';
function SelectionInfo() {
const {
selectedNode, // Currently selected SceneNode
selectObject, // Function to select by UUID
clearSelection, // Function to clear selection
isSelected, // Check if a specific UUID is selected
} = useSelectedObject();
if (!selectedNode) {
return <div>No object selected</div>;
}
return (
<div>
<h3>Selected: {selectedNode.name || selectedNode.uuid}</h3>
<p>Type: {selectedNode.type}</p>
<button onClick={clearSelection}>Clear Selection</button>
</div>
);
}2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
useMetric
Subscribe to specific metrics:
import { useMetric } from '@3lens/react-bridge';
function PerformanceDisplay() {
const fps = useMetric('fps');
const drawCalls = useMetric('drawCalls');
const triangles = useMetric('triangles');
return (
<div className="perf-overlay">
<span>FPS: {fps.toFixed(1)}</span>
<span>Draws: {drawCalls}</span>
<span>Tris: {triangles.toLocaleString()}</span>
</div>
);
}2
3
4
5
6
7
8
9
10
11
12
13
14
15
Convenience Metric Hooks
Pre-built hooks for common metrics:
import {
useFPS,
useFrameTime,
useDrawCalls,
useTriangles,
useGPUMemory,
useTextureCount,
useGeometryCount,
} from '@3lens/react-bridge';
function Metrics() {
const fps = useFPS();
const frameTime = useFrameTime();
const drawCalls = useDrawCalls();
const triangles = useTriangles();
const gpuMemory = useGPUMemory();
const textureCount = useTextureCount();
const geometryCount = useGeometryCount();
return (
<div>
<p>FPS: {fps.toFixed(1)} ({frameTime.toFixed(2)}ms)</p>
<p>Draw Calls: {drawCalls}</p>
<p>Triangles: {triangles.toLocaleString()}</p>
<p>GPU Memory: {(gpuMemory / 1024 / 1024).toFixed(1)}MB</p>
<p>Textures: {textureCount}</p>
<p>Geometries: {geometryCount}</p>
</div>
);
}2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
Entity Registration
useDevtoolEntity
Register Three.js objects with logical names and metadata:
import { useRef, useEffect } from 'react';
import { useDevtoolEntity } from '@3lens/react-bridge';
import * as THREE from 'three';
function PlayerCharacter({ health, level }) {
const meshRef = useRef<THREE.Mesh>(null);
// Register this mesh as a logical entity
const { entityId } = useDevtoolEntity(meshRef, {
name: 'Player Character',
module: '@game/characters',
tags: ['player', 'controllable'],
metadata: {
health,
level,
class: 'warrior',
},
});
return (
<mesh ref={meshRef}>
<capsuleGeometry args={[0.5, 1, 16, 32]} />
<meshStandardMaterial color="#4488ff" />
</mesh>
);
}2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
useDevtoolEntityGroup
Register groups of objects under one entity:
import { useRef } from 'react';
import { useDevtoolEntityGroup } from '@3lens/react-bridge';
import * as THREE from 'three';
function EnemySquad({ squadId, enemies }) {
const groupRef = useRef<THREE.Group>(null);
// Register all children as part of this entity
useDevtoolEntityGroup(groupRef, {
name: `Enemy Squad ${squadId}`,
module: '@game/enemies',
tags: ['enemy', 'ai-controlled'],
metadata: {
squadId,
memberCount: enemies.length,
},
});
return (
<group ref={groupRef}>
{enemies.map((enemy, i) => (
<Enemy key={i} {...enemy} />
))}
</group>
);
}2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
Entity Options
interface EntityOptions {
/**
* Display name in the devtools
*/
name?: string;
/**
* Module identifier (e.g., '@scope/library' or 'category/name')
*/
module?: string;
/**
* Custom metadata attached to the entity
*/
metadata?: Record<string, unknown>;
/**
* Tags for filtering and organization
*/
tags?: string[];
/**
* Component type identifier
*/
componentType?: string;
/**
* Unique component instance ID
*/
componentId?: string;
/**
* Parent entity ID for hierarchical organization
*/
parentEntityId?: string;
}2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
Advanced Patterns
Conditional Debugging
Only enable 3Lens in development:
import React from 'react';
import { ThreeLensProvider } from '@3lens/react-bridge';
import { Canvas } from '@react-three/fiber';
function App() {
const isDev = process.env.NODE_ENV !== 'production';
const content = (
<Canvas>
<Scene />
</Canvas>
);
if (!isDev) {
return content;
}
return (
<ThreeLensProvider config={{ appName: 'My App' }}>
{content}
</ThreeLensProvider>
);
}2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
Multiple Scenes
Handle multiple canvases:
import React, { useRef } from 'react';
import { ThreeLensProvider, useThreeLensProbe } from '@3lens/react-bridge';
import { Canvas, useThree } from '@react-three/fiber';
function SceneConnector({ name }) {
const probe = useThreeLensProbe();
const { gl, scene, camera } = useThree();
React.useEffect(() => {
probe.observeRenderer(gl);
probe.observeScene(scene, { name });
}, [gl, scene, name]);
return null;
}
function App() {
return (
<ThreeLensProvider config={{ appName: 'Multi-Scene App' }}>
<div style={{ display: 'flex' }}>
<Canvas style={{ flex: 1 }}>
<SceneConnector name="Main Scene" />
<MainScene />
</Canvas>
<Canvas style={{ flex: 1 }}>
<SceneConnector name="UI Scene" />
<UIScene />
</Canvas>
</div>
</ThreeLensProvider>
);
}2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
Performance Monitoring Component
Create a performance HUD:
import React, { useState, useEffect } from 'react';
import { useFPS, useDrawCalls, useTriangles, useThreeLensProbe } from '@3lens/react-bridge';
import { Html } from '@react-three/drei';
function PerformanceHUD() {
const fps = useFPS();
const drawCalls = useDrawCalls();
const triangles = useTriangles();
const probe = useThreeLensProbe();
const [warnings, setWarnings] = useState<string[]>([]);
useEffect(() => {
const unsubscribe = probe.onRuleViolation((violation) => {
setWarnings((prev) => [...prev.slice(-4), violation.message]);
// Auto-clear after 5 seconds
setTimeout(() => {
setWarnings((prev) => prev.slice(1));
}, 5000);
});
return unsubscribe;
}, [probe]);
const fpsColor = fps >= 55 ? '#4caf50' : fps >= 30 ? '#ff9800' : '#f44336';
return (
<Html position={[-5, 3, 0]} style={{ pointerEvents: 'none' }}>
<div style={{
background: 'rgba(0,0,0,0.7)',
color: 'white',
padding: '10px',
borderRadius: '5px',
fontFamily: 'monospace',
fontSize: '12px',
minWidth: '150px',
}}>
<div style={{ color: fpsColor }}>
FPS: {fps.toFixed(1)}
</div>
<div>Draw Calls: {drawCalls}</div>
<div>Triangles: {triangles.toLocaleString()}</div>
{warnings.length > 0 && (
<div style={{
marginTop: '10px',
borderTop: '1px solid #666',
paddingTop: '10px',
color: '#ff9800',
}}>
{warnings.map((w, i) => (
<div key={i}>⚠️ {w}</div>
))}
</div>
)}
</div>
</Html>
);
}2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
Selection Handler Component
React to selection changes:
import React, { useEffect } from 'react';
import { useSelectedObject, useThreeLensProbe } from '@3lens/react-bridge';
function SelectionHandler({ onSelect }) {
const { selectedNode } = useSelectedObject();
const probe = useThreeLensProbe();
useEffect(() => {
if (selectedNode) {
// Get the actual Three.js object
const object = probe.getObjectByUuid(selectedNode.uuid);
onSelect?.(object, selectedNode);
} else {
onSelect?.(null, null);
}
}, [selectedNode, probe, onSelect]);
return null;
}
// Usage
function App() {
const handleSelect = (object, node) => {
if (object) {
console.log('Selected:', node.name, object);
}
};
return (
<ThreeLensProvider config={{ appName: 'My App' }}>
<Canvas>
<SelectionHandler onSelect={handleSelect} />
<Scene />
</Canvas>
</ThreeLensProvider>
);
}2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
Next.js & SSR
App Router (Next.js 13+)
3Lens needs to be client-side only. Use the "use client" directive:
// components/ThreeScene.tsx
"use client";
import { ThreeLensCanvas } from '@3lens/react-bridge';
import { Suspense } from 'react';
export function ThreeScene() {
return (
<ThreeLensCanvas config={{ appName: 'Next.js App' }}>
<Suspense fallback={null}>
<Scene />
</Suspense>
</ThreeLensCanvas>
);
}
// app/page.tsx
import { ThreeScene } from '../components/ThreeScene';
export default function Page() {
return (
<main>
<ThreeScene />
</main>
);
}2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
Dynamic Import with No SSR
For Pages Router or when you need more control:
import dynamic from 'next/dynamic';
const ThreeScene = dynamic(
() => import('../components/ThreeScene').then((mod) => mod.ThreeScene),
{ ssr: false }
);
export default function Page() {
return <ThreeScene />;
}2
3
4
5
6
7
8
9
10
Environment-Based Loading
Only load 3Lens in development:
"use client";
import dynamic from 'next/dynamic';
import { Canvas } from '@react-three/fiber';
const ThreeLensCanvas = dynamic(
() => import('@3lens/react-bridge').then((mod) => mod.ThreeLensCanvas),
{ ssr: false }
);
export function Scene() {
const isDev = process.env.NODE_ENV !== 'production';
if (!isDev) {
return (
<Canvas>
<SceneContent />
</Canvas>
);
}
return (
<ThreeLensCanvas config={{ appName: 'My App' }}>
<SceneContent />
</ThreeLensCanvas>
);
}2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
TypeScript Support
Full Type Definitions
The package includes complete TypeScript definitions:
import type {
ThreeLensProviderConfig,
ThreeLensContextValue,
UseMetricOptions,
MetricValue,
EntityOptions,
RegisteredEntity,
DevtoolProbe,
FrameStats,
SceneSnapshot,
SceneNode,
} from '@3lens/react-bridge';2
3
4
5
6
7
8
9
10
11
12
Generic Metric Hook
The useMetric hook is fully typed:
import { useMetric } from '@3lens/react-bridge';
// Type-safe metric access
const fps = useMetric<number>('fps');
const memory = useMetric<number>('gpuMemory');
// Custom metrics
interface CustomMetrics {
customValue: number;
}
const custom = useMetric<CustomMetrics['customValue']>('customValue');2
3
4
5
6
7
8
9
10
11
12
Entity Registration Types
import { useDevtoolEntity } from '@3lens/react-bridge';
import { useRef } from 'react';
import * as THREE from 'three';
interface PlayerMetadata {
health: number;
mana: number;
inventory: string[];
}
function Player() {
const ref = useRef<THREE.Mesh>(null);
const { entityId, update, unregister } = useDevtoolEntity(ref, {
name: 'Player',
module: '@game/player',
metadata: {
health: 100,
mana: 50,
inventory: ['sword', 'shield'],
} satisfies PlayerMetadata,
});
// Update metadata when it changes
const takeDamage = (amount: number) => {
update({
metadata: { health: 100 - amount },
});
};
return (
<mesh ref={ref}>
<boxGeometry />
<meshStandardMaterial />
</mesh>
);
}2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
Best Practices
1. Use ThreeLensCanvas for R3F Projects
Instead of manually wiring up the provider and canvas, use the combined component:
// ✅ Recommended
<ThreeLensCanvas config={{ appName: 'My App' }}>
<Scene />
</ThreeLensCanvas>
// ❌ More boilerplate (use only if you need custom setup)
<ThreeLensProvider config={{ appName: 'My App' }}>
<Canvas>
<R3FProbe />
<Scene />
</Canvas>
</ThreeLensProvider>2
3
4
5
6
7
8
9
10
11
12
2. Register Logical Entities for Better Debugging
Give meaningful names to important objects:
function Player({ position }) {
const meshRef = useRef();
useDevtoolEntity(meshRef, {
name: 'Player Character',
module: '@game/player',
tags: ['player', 'controllable'],
});
return (
<mesh ref={meshRef} position={position}>
<capsuleGeometry />
<meshStandardMaterial />
</mesh>
);
}2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
3. Set Performance Rules
Define thresholds to catch performance issues early:
<ThreeLensProvider
config={{
appName: 'My App',
rules: {
maxDrawCalls: 200,
maxTriangles: 500_000,
maxFrameTimeMs: 16.67, // Target 60 FPS
maxTextures: 50,
},
}}
>2
3
4
5
6
7
8
9
10
11
4. Use Module Patterns for Large Apps
Organize entities by feature module:
// features/enemies/Enemy.tsx
useDevtoolEntity(ref, {
name: `Enemy-${id}`,
module: '@game/enemies',
tags: ['enemy', 'ai'],
});
// features/environment/Tree.tsx
useDevtoolEntity(ref, {
name: `Tree-${id}`,
module: '@game/environment',
tags: ['decoration', 'static'],
});2
3
4
5
6
7
8
9
10
11
12
13
5. Production Stripping
Remove 3Lens from production builds:
// Use dynamic import for tree-shaking
const ThreeLensCanvas = lazy(() =>
import('@3lens/react-bridge').then(m => ({ default: m.ThreeLensCanvas }))
);
function App() {
if (process.env.NODE_ENV === 'production') {
return <Canvas><Scene /></Canvas>;
}
return (
<Suspense fallback={<Canvas><Scene /></Canvas>}>
<ThreeLensCanvas config={{ appName: 'My App' }}>
<Scene />
</ThreeLensCanvas>
</Suspense>
);
}2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
Common Pitfalls
❌ Using Hooks Outside Provider
// ❌ Wrong - hook called before provider
function App() {
const probe = useThreeLensProbe(); // Error!
return <ThreeLensProvider>...</ThreeLensProvider>;
}
// ✅ Correct - hook inside provider tree
function App() {
return (
<ThreeLensProvider config={{ appName: 'My App' }}>
<Scene /> {/* hooks work here */}
</ThreeLensProvider>
);
}2
3
4
5
6
7
8
9
10
11
12
13
14
❌ Forgetting to Connect R3F
// ❌ Won't work - probe not connected to R3F
<ThreeLensProvider>
<Canvas>
<Scene />
</Canvas>
</ThreeLensProvider>
// ✅ Correct - use ThreeLensCanvas or R3FProbe
<ThreeLensCanvas config={{ appName: 'My App' }}>
<Scene />
</ThreeLensCanvas>
// Or with R3FProbe
<ThreeLensProvider config={{ appName: 'My App' }}>
<Canvas>
<R3FProbe />
<Scene />
</Canvas>
</ThreeLensProvider>2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
❌ Registering Entities Too Early
// ❌ Wrong - ref is null on first render
function MyObject() {
const ref = useRef();
useDevtoolEntity(ref, { name: 'My Object' }); // ref.current is null!
return <mesh ref={ref} />;
}
// ✅ Correct - useDevtoolEntity handles null refs properly
// The hook internally waits for ref.current to be set
function MyObject() {
const ref = useRef();
useDevtoolEntity(ref, { name: 'My Object' }); // Works!
return <mesh ref={ref} />;
}2
3
4
5
6
7
8
9
10
11
12
13
14
❌ Memory Leaks with Event Listeners
// ❌ Wrong - no cleanup
useEffect(() => {
probe.onRuleViolation((v) => console.log(v));
}, []);
// ✅ Correct - cleanup on unmount
useEffect(() => {
const unsubscribe = probe.onRuleViolation((v) => console.log(v));
return () => unsubscribe();
}, [probe]);2
3
4
5
6
7
8
9
10
Troubleshooting
Probe Not Available
If useThreeLensProbe() throws an error:
// Use the optional version that returns null instead of throwing
import { useThreeLensProbeOptional } from '@3lens/react-bridge';
function MyComponent() {
const probe = useThreeLensProbeOptional();
if (!probe) {
return <div>3Lens not available</div>;
}
// Use probe safely
}2
3
4
5
6
7
8
9
10
11
12
Context Outside Provider
Ensure all hooks are used within ThreeLensProvider:
// ❌ Wrong - outside provider
function App() {
const probe = useThreeLensProbe(); // Error!
return <ThreeLensProvider>...</ThreeLensProvider>;
}
// ✅ Correct - inside provider
function App() {
return (
<ThreeLensProvider config={{ appName: 'My App' }}>
<MyComponent />
</ThreeLensProvider>
);
}
function MyComponent() {
const probe = useThreeLensProbe(); // Works!
return <div>...</div>;
}2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
Hot Reload Issues
If the probe disconnects after hot reload:
import { useEffect } from 'react';
import { useThreeLensProbe } from '@3lens/react-bridge';
import { useThree } from '@react-three/fiber';
function ReconnectOnHMR() {
const probe = useThreeLensProbe();
const { gl, scene } = useThree();
useEffect(() => {
// Re-observe on mount (handles HMR)
probe.observeRenderer(gl);
probe.observeScene(scene);
}, [probe, gl, scene]);
return null;
}2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
Examples
See the example projects for complete implementations:
- examples/framework-integration/react-three-fiber
- examples/feature-showcase/transform-gizmo
- examples/debugging-profiling/performance-debugging