Framework Integration Examples โ
This guide provides comprehensive walkthroughs for integrating 3Lens with various JavaScript frameworks and build tools. Each example is a complete, runnable project demonstrating best practices.
Overview โ
3Lens provides framework-agnostic core packages that work with any Three.js application, plus dedicated bridge packages for popular frameworks:
| Framework | Bridge Package | Example |
|---|---|---|
| Vanilla Three.js | @3lens/core + @3lens/overlay | vanilla-threejs |
| React Three Fiber | @3lens/react-bridge | react-three-fiber |
| Angular | @3lens/angular-bridge | angular-threejs |
| Vue + TresJS | @3lens/vue-bridge | vue-tresjs |
| Svelte + Threlte | @3lens/core | svelte-threlte |
| Next.js (SSR) | @3lens/react-bridge | nextjs-ssr |
| Electron | @3lens/core | electron-desktop |
Vanilla Three.js โ
The vanilla example demonstrates 3Lens integration without any UI frameworkโjust pure Three.js.
Features Demonstrated โ
- Scene Setup: Complete Three.js scene with camera, renderer, lights, and objects
- 3Lens Integration: Full probe and overlay setup
- Shadow Mapping: DirectionalLight, PointLight, and SpotLight with shadow maps
- Materials: Various material types (Standard, Physical, Lambert)
- Textures: Canvas-generated textures
- Animation: Animated objects with rotation and orbit movements
- OrbitControls: Camera controls for scene exploration
- Transform Gizmo: Interactive object manipulation
- Performance Monitoring: Real-time FPS, draw calls, and memory tracking
Quick Start โ
# From the 3Lens root directory
pnpm install
# Run this example
cd examples/framework-integration/vanilla-threejs
pnpm dev2
3
4
5
6
Open http://localhost:3000 in your browser.
Project Structure โ
vanilla-threejs/
โโโ src/
โ โโโ main.ts # Main application entry point
โ โโโ benchmark.ts # Performance benchmark page
โโโ index.html # Main HTML file
โโโ benchmark.html # Benchmark HTML file
โโโ package.json
โโโ tsconfig.json
โโโ vite.config.ts2
3
4
5
6
7
8
9
Step-by-Step Walkthrough โ
Step 1: Import 3Lens Packages โ
import * as THREE from 'three';
import { createProbe } from '@3lens/core';
import { createOverlay } from '@3lens/overlay';2
3
import * as THREE from 'three';
import { createProbe } from '@3lens/core';
import { createOverlay } from '@3lens/overlay';2
3
Step 2: Create Your Three.js Scene โ
// Standard Three.js setup
const scene = new THREE.Scene();
scene.name = 'MainScene'; // Naming helps in the devtool
const camera = new THREE.PerspectiveCamera(60, window.innerWidth / window.innerHeight, 0.1, 1000);
camera.name = 'MainCamera';
camera.position.set(5, 5, 8);
const renderer = new THREE.WebGLRenderer({ antialias: true });
renderer.setSize(window.innerWidth, window.innerHeight);
renderer.shadowMap.enabled = true;
document.body.appendChild(renderer.domElement);2
3
4
5
6
7
8
9
10
11
12
Step 3: Create the Probe โ
const probe = createProbe({
appName: 'Vanilla Three.js Example',
});
// Connect renderer and scene
probe.observeRenderer(renderer);
probe.observeScene(scene);
// Set THREE reference for advanced features
probe.setThreeReference(THREE);2
3
4
5
6
7
8
9
10
Step 4: Initialize Transform Controls (Optional) โ
// Enable transform gizmo for interactive object manipulation
await probe.initializeTransformGizmo(camera, renderer.domElement);
// Enable camera controller for viewport navigation
await probe.initializeCameraController(camera, renderer.domElement);2
3
4
5
Step 5: Create the Overlay โ
const overlay = createOverlay(probe);Step 6: Add Objects to Scene โ
// Create geometry and material
const geometry = new THREE.BoxGeometry(1, 1, 1);
const material = new THREE.MeshStandardMaterial({
name: 'CubeMaterial', // Name materials for easy identification
color: 0x60a5fa,
roughness: 0.3,
metalness: 0.7,
});
const cube = new THREE.Mesh(geometry, material);
cube.name = 'AnimatedCube'; // Name objects for the scene tree
cube.castShadow = true;
scene.add(cube);2
3
4
5
6
7
8
9
10
11
12
13
Step 7: Render Loop โ
function animate() {
requestAnimationFrame(animate);
// Your animation logic
cube.rotation.y += 0.01;
// Render the scene
renderer.render(scene, camera);
}
animate();2
3
4
5
6
7
8
9
10
Best Practices โ
- Name your objects: Setting
object.namemakes the scene tree much more readable - Name materials and geometries: Helps identify resources in the devtool panels
- Share geometries: Multiple meshes can share the same geometry instance
- Use development-only imports: Tree-shake 3Lens in production builds
Development-Only Setup โ
let probe: DevtoolProbe | null = null;
if (import.meta.env.DEV) {
const { createProbe } = await import('@3lens/core');
const { createOverlay } = await import('@3lens/overlay');
probe = createProbe({ appName: 'My App' });
probe.observeRenderer(renderer);
probe.observeScene(scene);
createOverlay(probe);
}2
3
4
5
6
7
8
9
10
11
let probe = null;
if (import.meta.env.DEV) {
const { createProbe } = await import('@3lens/core');
const { createOverlay } = await import('@3lens/overlay');
probe = createProbe({ appName: 'My App' });
probe.observeRenderer(renderer);
probe.observeScene(scene);
createOverlay(probe);
}2
3
4
5
6
7
8
9
10
11
React Three Fiber โ
The React Three Fiber (R3F) example demonstrates declarative 3D scene creation with React and full 3Lens integration.
Features Demonstrated โ
- ThreeLensProvider: Context provider for 3Lens probe
- ThreeLensCanvas: R3F Canvas wrapper with automatic 3Lens setup
- useDevtoolEntity: Hook for registering entities with the devtool
- useFPS / useDrawCalls: Real-time performance metric hooks
- Animated components: Rotating box, bouncing sphere, orbital torus group
- Lighting setup: Ambient, directional (with shadows), and point lights
- Interactive meshes: Hover and click states
Quick Start โ
# From the monorepo root
pnpm install
pnpm --filter @3lens/example-react-three-fiber dev2
3
Open http://localhost:3001 in your browser.
Project Structure โ
react-three-fiber/
โโโ src/
โ โโโ main.tsx # React entry point
โ โโโ App.tsx # Main app with ThreeLensProvider
โ โโโ components/
โ โโโ Scene.tsx # Main scene composition
โ โโโ RotatingBox.tsx # Animated box with useDevtoolEntity
โ โโโ AnimatedSphere.tsx # Bouncing sphere with metrics hooks
โ โโโ TorusGroup.tsx # Group of orbital toruses
โ โโโ Ground.tsx # Ground plane
โ โโโ Lights.tsx # Scene lighting
โ โโโ LoadingFallback.tsx # Suspense fallback
โโโ index.html
โโโ package.json
โโโ tsconfig.json
โโโ vite.config.ts2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
Step-by-Step Walkthrough โ
Step 1: Wrap App with ThreeLensProvider โ
import { ThreeLensProvider } from '@3lens/react-bridge';
function App() {
return (
<ThreeLensProvider appName="My R3F App">
{/* Your app content */}
</ThreeLensProvider>
);
}2
3
4
5
6
7
8
9
import { ThreeLensProvider } from '@3lens/react-bridge';
function App() {
return (
<ThreeLensProvider appName="My R3F App">
{/* Your app content */}
</ThreeLensProvider>
);
}2
3
4
5
6
7
8
9
Step 2: Use ThreeLensCanvas โ
Replace the standard R3F Canvas with ThreeLensCanvas:
import { ThreeLensCanvas } from '@3lens/react-bridge';
function App() {
return (
<ThreeLensProvider appName="My R3F App">
<ThreeLensCanvas
shadows
camera={{ position: [5, 5, 8], fov: 60 }}
>
<Scene />
</ThreeLensCanvas>
</ThreeLensProvider>
);
}2
3
4
5
6
7
8
9
10
11
12
13
14
import { ThreeLensCanvas } from '@3lens/react-bridge';
function App() {
return (
<ThreeLensProvider appName="My R3F App">
<ThreeLensCanvas
shadows
camera={{ position: [5, 5, 8], fov: 60 }}
>
<Scene />
</ThreeLensCanvas>
</ThreeLensProvider>
);
}2
3
4
5
6
7
8
9
10
11
12
13
14
Step 3: Register Entities with useDevtoolEntity โ
import { useRef } from 'react';
import { useFrame } from '@react-three/fiber';
import { useDevtoolEntity } from '@3lens/react-bridge';
import type { Mesh } from 'three';
function RotatingBox() {
const meshRef = useRef<Mesh>(null!);
// Register with 3Lens devtool
useDevtoolEntity(meshRef, {
name: 'RotatingBox',
module: 'scene/objects',
tags: ['animated', 'interactive'],
metadata: { description: 'A colorful rotating cube' },
});
useFrame((state, delta) => {
meshRef.current.rotation.y += delta;
});
return (
<mesh ref={meshRef} castShadow>
<boxGeometry args={[1, 1, 1]} />
<meshStandardMaterial color="#4ecdc4" />
</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
import { useRef } from 'react';
import { useFrame } from '@react-three/fiber';
import { useDevtoolEntity } from '@3lens/react-bridge';
function RotatingBox() {
const meshRef = useRef(null);
// Register with 3Lens devtool
useDevtoolEntity(meshRef, {
name: 'RotatingBox',
module: 'scene/objects',
tags: ['animated', 'interactive'],
metadata: { description: 'A colorful rotating cube' },
});
useFrame((state, delta) => {
meshRef.current.rotation.y += delta;
});
return (
<mesh ref={meshRef} castShadow>
<boxGeometry args={[1, 1, 1]} />
<meshStandardMaterial color="#4ecdc4" />
</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
Step 4: Access Real-time Metrics โ
import { useFPS, useDrawCalls, useTriangles } from '@3lens/react-bridge';
function PerformanceDisplay() {
const fps = useFPS();
const drawCalls = useDrawCalls();
const triangles = useTriangles();
return (
<div className="stats-panel">
<div>FPS: {fps.toFixed(0)}</div>
<div>Draw Calls: {drawCalls}</div>
<div>Triangles: {triangles.toLocaleString()}</div>
</div>
);
}2
3
4
5
6
7
8
9
10
11
12
13
14
15
import { useFPS, useDrawCalls, useTriangles } from '@3lens/react-bridge';
function PerformanceDisplay() {
const fps = useFPS();
const drawCalls = useDrawCalls();
const triangles = useTriangles();
return (
<div className="stats-panel">
<div>FPS: {fps.toFixed(0)}</div>
<div>Draw Calls: {drawCalls}</div>
<div>Triangles: {triangles.toLocaleString()}</div>
</div>
);
}2
3
4
5
6
7
8
9
10
11
12
13
14
15
Best Practices โ
- Use ThreeLensCanvas: It automatically sets up the probe with R3F's internal state
- Register logical entities: Use
useDevtoolEntityto track React components in the devtool - Place provider at root: ThreeLensProvider should wrap your entire 3D app section
- Metrics hooks are reactive: They update every frame, use React.memo for expensive UI
Angular Three.js โ
The Angular example demonstrates 3Lens integration with Angular's dependency injection and RxJS observables.
Features Demonstrated โ
- ThreeLensModule: Angular module for 3Lens integration
- ThreeSceneService: Injectable service managing the Three.js scene
- DevtoolProbe: Direct probe creation and attachment
- RxJS Integration: Reactive metrics via BehaviorSubject/Observable
- NgZone Optimization: Animation loop runs outside Angular zone
Quick Start โ
# From the monorepo root
pnpm install
pnpm --filter @3lens/example-angular-threejs dev2
3
Open http://localhost:3002 in your browser.
Project Structure โ
angular-threejs/
โโโ src/
โ โโโ main.ts # Angular bootstrap
โ โโโ index.html # HTML entry
โ โโโ styles.css # Global styles
โ โโโ app/
โ โโโ app.component.ts # Root component with metrics display
โ โโโ components/
โ โ โโโ scene-canvas.component.ts # Canvas rendering component
โ โโโ services/
โ โโโ three-scene.service.ts # Three.js scene management
โโโ angular.json
โโโ package.json
โโโ tsconfig.json2
3
4
5
6
7
8
9
10
11
12
13
14
Step-by-Step Walkthrough โ
Step 1: Create a Scene Service โ
// services/three-scene.service.ts
import { Injectable } from '@angular/core';
import { BehaviorSubject } from 'rxjs';
import * as THREE from 'three';
import { createProbe, DevtoolProbe } from '@3lens/core';
import { bootstrapOverlay } from '@3lens/overlay';
@Injectable({ providedIn: 'root' })
export class ThreeSceneService {
private probe!: DevtoolProbe;
private renderer!: THREE.WebGLRenderer;
private scene!: THREE.Scene;
private camera!: THREE.PerspectiveCamera;
// Expose metrics as observables
private fpsSubject = new BehaviorSubject<number>(0);
private drawCallsSubject = new BehaviorSubject<number>(0);
fps$ = this.fpsSubject.asObservable();
drawCalls$ = this.drawCallsSubject.asObservable();
initialize(canvas: HTMLCanvasElement): void {
// Create Three.js scene
this.scene = new THREE.Scene();
this.camera = new THREE.PerspectiveCamera(60, canvas.width / canvas.height, 0.1, 1000);
this.renderer = new THREE.WebGLRenderer({ canvas, antialias: true });
// Create 3Lens probe
this.probe = createProbe({
appName: 'Angular Three.js App'
});
this.probe.observeRenderer(this.renderer);
this.probe.observeScene(this.scene);
// Subscribe to metrics
this.probe.on('frame', (stats) => {
this.fpsSubject.next(stats.fps);
this.drawCallsSubject.next(stats.drawCalls);
});
// Create overlay
bootstrapOverlay(this.probe);
}
render(): void {
this.renderer.render(this.scene, this.camera);
}
}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
Step 2: Create Canvas Component โ
// components/scene-canvas.component.ts
import { Component, ElementRef, NgZone, OnDestroy, OnInit, ViewChild } from '@angular/core';
import { ThreeSceneService } from '../services/three-scene.service';
@Component({
selector: 'app-scene-canvas',
template: '<canvas #canvas></canvas>',
styles: ['canvas { width: 100%; height: 100%; display: block; }']
})
export class SceneCanvasComponent implements OnInit, OnDestroy {
@ViewChild('canvas', { static: true }) canvasRef!: ElementRef<HTMLCanvasElement>;
private animationId = 0;
constructor(
private sceneService: ThreeSceneService,
private ngZone: NgZone
) {}
ngOnInit(): void {
this.sceneService.initialize(this.canvasRef.nativeElement);
// Run animation outside Angular zone for better performance
this.ngZone.runOutsideAngular(() => {
this.animate();
});
}
ngOnDestroy(): void {
cancelAnimationFrame(this.animationId);
}
private animate = (): void => {
this.animationId = requestAnimationFrame(this.animate);
this.sceneService.render();
};
}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
Step 3: Display Metrics in Component โ
// app.component.ts
import { Component } from '@angular/core';
import { AsyncPipe } from '@angular/common';
import { ThreeSceneService } from './services/three-scene.service';
@Component({
selector: 'app-root',
standalone: true,
imports: [AsyncPipe, SceneCanvasComponent],
template: `
<app-scene-canvas></app-scene-canvas>
<div class="stats-overlay">
<div>FPS: {{ sceneService.fps$ | async | number:'1.0-0' }}</div>
<div>Draw Calls: {{ sceneService.drawCalls$ | async }}</div>
</div>
`
})
export class AppComponent {
constructor(public sceneService: ThreeSceneService) {}
}2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
Best Practices โ
- Run outside NgZone: The animation loop should run outside Angular's zone to avoid unnecessary change detection
- Use RxJS: Expose metrics as observables for seamless Angular integration
- Inject services: Use Angular's DI for the scene service
- Use signals (Angular 16+): Consider using signals for simpler reactive state
Angular Signals Integration (Angular 16+) โ
import { Injectable, signal, computed } from '@angular/core';
@Injectable({ providedIn: 'root' })
export class ThreeSceneService {
// Use signals for reactive state
readonly fps = signal<number>(0);
readonly drawCalls = signal<number>(0);
readonly triangles = signal<number>(0);
// Computed values
readonly isPerformant = computed(() => this.fps() >= 55);
// ... rest of service
}2
3
4
5
6
7
8
9
10
11
12
13
14
Vue TresJS โ
The Vue + TresJS example demonstrates declarative 3D scene creation with Vue 3's composition API.
Features Demonstrated โ
- TresCanvas: TresJS canvas component with shadows and camera config
- Declarative 3D: Vue-style declarative Three.js scene graph
- useRenderLoop: TresJS animation loop hook
- Composables: Vue 3 composition API for 3Lens integration
- Animated Components: Rotating box, bouncing sphere, orbital toruses
Quick Start โ
# From the monorepo root
pnpm install
pnpm --filter @3lens/example-vue-tresjs dev2
3
Open http://localhost:3003 in your browser.
Project Structure โ
vue-tresjs/
โโโ src/
โ โโโ main.ts # Vue app entry
โ โโโ App.vue # Root component with TresCanvas
โ โโโ composables/
โ โ โโโ useThreeLensSetup.ts # 3Lens metrics composable
โ โโโ components/
โ โโโ Scene.vue # Main scene composition
โ โโโ RotatingBox.vue # Animated rotating cube
โ โโโ AnimatedSphere.vue # Bouncing sphere
โ โโโ TorusGroup.vue # Group of orbital toruses
โ โโโ Ground.vue # Ground plane
โ โโโ Lights.vue # Scene lighting
โ โโโ InfoPanel.vue # Metrics display
โโโ index.html
โโโ package.json
โโโ tsconfig.json
โโโ vite.config.ts2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
Step-by-Step Walkthrough โ
Step 1: Create a 3Lens Composable โ
// composables/useThreeLensSetup.ts
import { ref, onMounted, onUnmounted, type Ref } from 'vue';
import { useTres } from '@tresjs/core';
import { createProbe, type DevtoolProbe } from '@3lens/core';
import { bootstrapOverlay } from '@3lens/overlay';
export function useThreeLensSetup() {
const fps: Ref<number> = ref(0);
const drawCalls: Ref<number> = ref(0);
const triangles: Ref<number> = ref(0);
let probe: DevtoolProbe | null = null;
onMounted(() => {
const { renderer, scene } = useTres();
if (renderer.value && scene.value) {
probe = createProbe({ appName: 'Vue TresJS App' });
probe.observeRenderer(renderer.value);
probe.observeScene(scene.value);
probe.on('frame', (stats) => {
fps.value = stats.fps;
drawCalls.value = stats.drawCalls;
triangles.value = stats.triangles;
});
bootstrapOverlay(probe);
}
});
onUnmounted(() => {
probe?.dispose();
});
return { fps, drawCalls, triangles };
}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
// composables/useThreeLensSetup.js
import { ref, onMounted, onUnmounted } from 'vue';
import { useTres } from '@tresjs/core';
import { createProbe } from '@3lens/core';
import { bootstrapOverlay } from '@3lens/overlay';
export function useThreeLensSetup() {
const fps = ref(0);
const drawCalls = ref(0);
const triangles = ref(0);
let probe = null;
onMounted(() => {
const { renderer, scene } = useTres();
if (renderer.value && scene.value) {
probe = createProbe({ appName: 'Vue TresJS App' });
probe.observeRenderer(renderer.value);
probe.observeScene(scene.value);
probe.on('frame', (stats) => {
fps.value = stats.fps;
drawCalls.value = stats.drawCalls;
triangles.value = stats.triangles;
});
bootstrapOverlay(probe);
}
});
onUnmounted(() => {
probe?.dispose();
});
return { fps, drawCalls, triangles };
}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
Step 2: Use TresCanvas โ
<script setup lang="ts">
import { TresCanvas } from '@tresjs/core';
import Scene from './components/Scene.vue';
</script>
<template>
<TresCanvas
shadows
:camera="{ position: [5, 5, 8], fov: 60 }"
:renderer="{ antialias: true }"
>
<Scene />
</TresCanvas>
</template>2
3
4
5
6
7
8
9
10
11
12
13
14
Step 3: Animate with useRenderLoop โ
<script setup lang="ts">
import { ref, shallowRef } from 'vue';
import { useRenderLoop } from '@tresjs/core';
import type { Mesh } from 'three';
const meshRef = shallowRef<Mesh | null>(null);
const { onLoop } = useRenderLoop();
onLoop(({ delta }) => {
if (meshRef.value) {
meshRef.value.rotation.y += delta;
}
});
</script>
<template>
<TresMesh ref="meshRef" cast-shadow>
<TresBoxGeometry :args="[1, 1, 1]" />
<TresMeshStandardMaterial color="#4ecdc4" />
</TresMesh>
</template>2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
Step 4: Display Metrics โ
<script setup lang="ts">
import { useThreeLensSetup } from '../composables/useThreeLensSetup';
const { fps, drawCalls, triangles } = useThreeLensSetup();
</script>
<template>
<div class="info-panel">
<div>FPS: {{ fps.toFixed(0) }}</div>
<div>Draw Calls: {{ drawCalls }}</div>
<div>Triangles: {{ triangles.toLocaleString() }}</div>
</div>
</template>2
3
4
5
6
7
8
9
10
11
12
13
Best Practices โ
- Use shallowRef for Three.js objects: Prevents Vue from making them deeply reactive
- Access renderer from useTres: TresJS provides the renderer through its composable
- Use onLoop for animations: Integrates with TresJS's render loop
- Dispose probe on unmount: Clean up resources when component is destroyed
Svelte Threlte โ
The Svelte + Threlte example demonstrates 3Lens integration with Svelte's reactive stores.
Features Demonstrated โ
- Threlte Canvas: Svelte-native Three.js rendering
- Declarative 3D:
<T.Mesh>,<T.Group>, and other Threlte components - useTask: Threlte's animation loop hook
- Svelte Stores: Reactive metrics with writable stores
- Interactive Components: Hover and click events on meshes
Quick Start โ
# From the monorepo root
pnpm install
pnpm --filter @3lens/example-svelte-threlte dev2
3
Open http://localhost:3007 in your browser.
Project Structure โ
svelte-threlte/
โโโ src/
โ โโโ main.ts # Svelte app entry
โ โโโ App.svelte # Root component with Canvas
โ โโโ app.d.ts # TypeScript declarations
โ โโโ lib/
โ โโโ useThreeLens.ts # 3Lens metrics store
โ โโโ components/
โ โโโ Scene.svelte # Main scene composition
โ โโโ RotatingBox.svelte # Animated rotating cube
โ โโโ AnimatedSphere.svelte # Bouncing sphere
โ โโโ TorusGroup.svelte # Group of orbital toruses
โ โโโ Ground.svelte # Ground plane
โ โโโ Lights.svelte # Scene lighting
โ โโโ InfoPanel.svelte # Metrics display
โโโ index.html
โโโ package.json
โโโ svelte.config.js
โโโ tsconfig.json
โโโ vite.config.ts2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
Step-by-Step Walkthrough โ
Step 1: Create a 3Lens Store โ
// lib/useThreeLens.ts
import { writable, type Writable } from 'svelte/store';
import { useThrelte } from '@threlte/core';
import { onMount, onDestroy } from 'svelte';
import { createProbe, type DevtoolProbe } from '@3lens/core';
import { bootstrapOverlay } from '@3lens/overlay';
export function useThreeLens() {
const fps: Writable<number> = writable(0);
const drawCalls: Writable<number> = writable(0);
const triangles: Writable<number> = writable(0);
let probe: DevtoolProbe | null = null;
onMount(() => {
const { renderer, scene } = useThrelte();
probe = createProbe({ appName: 'Svelte Threlte App' });
probe.observeRenderer(renderer);
probe.observeScene(scene);
probe.on('frame', (stats) => {
fps.set(stats.fps);
drawCalls.set(stats.drawCalls);
triangles.set(stats.triangles);
});
bootstrapOverlay(probe);
});
onDestroy(() => {
probe?.dispose();
});
return { fps, drawCalls, triangles };
}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
// lib/useThreeLens.js
import { writable } from 'svelte/store';
import { useThrelte } from '@threlte/core';
import { onMount, onDestroy } from 'svelte';
import { createProbe } from '@3lens/core';
import { bootstrapOverlay } from '@3lens/overlay';
export function useThreeLens() {
const fps = writable(0);
const drawCalls = writable(0);
const triangles = writable(0);
let probe = null;
onMount(() => {
const { renderer, scene } = useThrelte();
probe = createProbe({ appName: 'Svelte Threlte App' });
probe.observeRenderer(renderer);
probe.observeScene(scene);
probe.on('frame', (stats) => {
fps.set(stats.fps);
drawCalls.set(stats.drawCalls);
triangles.set(stats.triangles);
});
bootstrapOverlay(probe);
});
onDestroy(() => {
probe?.dispose();
});
return { fps, drawCalls, triangles };
}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
Step 2: Use Threlte Canvas โ
<script>
import { Canvas } from '@threlte/core';
import Scene from './lib/components/Scene.svelte';
</script>
<Canvas>
<Scene />
</Canvas>2
3
4
5
6
7
8
Step 3: Animate with useTask โ
<script lang="ts">
import { T, useTask } from '@threlte/core';
import type { Mesh } from 'three';
let mesh: Mesh;
useTask((delta) => {
if (mesh) {
mesh.rotation.y += delta;
}
});
</script>
<T.Mesh bind:ref={mesh} castShadow>
<T.BoxGeometry args={[1, 1, 1]} />
<T.MeshStandardMaterial color="#4ecdc4" />
</T.Mesh>2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
Step 4: Display Metrics โ
<script lang="ts">
import { useThreeLens } from '../useThreeLens';
const { fps, drawCalls, triangles } = useThreeLens();
</script>
<div class="info-panel">
<div>FPS: {$fps.toFixed(0)}</div>
<div>Draw Calls: {$drawCalls}</div>
<div>Triangles: {$triangles.toLocaleString()}</div>
</div>2
3
4
5
6
7
8
9
10
11
Best Practices โ
- Use Svelte stores: Writable stores integrate naturally with Svelte's reactivity
- Access from useThrelte: Get renderer/scene from Threlte's context
- Use
$syntax: Auto-subscribe to stores in templates - Dispose on destroy: Clean up the probe when component unmounts
Next.js SSR โ
The Next.js example demonstrates proper handling of server-side rendering (SSR) constraints with Three.js.
The SSR Challenge โ
Three.js and WebGL cannot run on the server because they require access to the DOM and browser APIs. This example shows patterns for:
- Dynamic imports with
ssr: falseto load 3D components client-only 'use client'directive for React Server Components compatibility- Loading states while the 3D scene initializes
- Hydration without errors
Quick Start โ
# From the monorepo root
pnpm install
pnpm --filter @3lens/example-nextjs-ssr dev2
3
Open http://localhost:3008 in your browser.
Project Structure โ
nextjs-ssr/
โโโ src/
โ โโโ app/
โ โ โโโ layout.tsx # Root layout with metadata
โ โ โโโ page.tsx # Home page with dynamic import
โ โ โโโ globals.css # Global styles
โ โโโ components/
โ โโโ Scene3D.tsx # Main 3D scene (client-only)
โ โโโ RotatingBox.tsx # Animated box
โ โโโ AnimatedSphere.tsx # Bouncing sphere
โ โโโ TorusGroup.tsx # Orbital toruses
โ โโโ Ground.tsx # Ground plane
โ โโโ Lights.tsx # Scene lighting
โ โโโ InfoPanel.tsx # Metrics display
โ โโโ index.ts # Component exports
โโโ next.config.js
โโโ package.json
โโโ tsconfig.json2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
Step-by-Step Walkthrough โ
Step 1: Dynamic Import with SSR Disabled โ
// app/page.tsx
import dynamic from 'next/dynamic';
// Dynamically import the 3D scene with SSR disabled
const Scene3D = dynamic(() => import('@/components/Scene3D'), {
ssr: false, // โ ๏ธ Critical: Disable server-side rendering
loading: () => (
<div className="loading-container">
<div className="spinner" />
<p>Loading 3D Scene...</p>
</div>
),
});
export default function Home() {
return (
<main>
<Scene3D />
</main>
);
}2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
// app/page.jsx
import dynamic from 'next/dynamic';
const Scene3D = dynamic(() => import('@/components/Scene3D'), {
ssr: false,
loading: () => (
<div className="loading-container">
<div className="spinner" />
<p>Loading 3D Scene...</p>
</div>
),
});
export default function Home() {
return (
<main>
<Scene3D />
</main>
);
}2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
Step 2: Mark Components as Client-Only โ
All components using Three.js must have the 'use client' directive:
// components/Scene3D.tsx
'use client';
import { Suspense } from 'react';
import { ThreeLensProvider, ThreeLensCanvas } from '@3lens/react-bridge';
import { RotatingBox } from './RotatingBox';
import { Lights } from './Lights';
export default function Scene3D() {
return (
<ThreeLensProvider appName="Next.js SSR Example">
<ThreeLensCanvas
shadows
camera={{ position: [5, 5, 8], fov: 60 }}
>
<Suspense fallback={null}>
<Lights />
<RotatingBox />
</Suspense>
</ThreeLensCanvas>
</ThreeLensProvider>
);
}2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
// components/Scene3D.jsx
'use client';
import { Suspense } from 'react';
import { ThreeLensProvider, ThreeLensCanvas } from '@3lens/react-bridge';
import { RotatingBox } from './RotatingBox';
import { Lights } from './Lights';
export default function Scene3D() {
return (
<ThreeLensProvider appName="Next.js SSR Example">
<ThreeLensCanvas
shadows
camera={{ position: [5, 5, 8], fov: 60 }}
>
<Suspense fallback={null}>
<Lights />
<RotatingBox />
</Suspense>
</ThreeLensCanvas>
</ThreeLensProvider>
);
}2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
Step 3: Client-Side Only Check (Optional Safety) โ
For extra safety, you can add a client-side check:
'use client';
import { useState, useEffect } from 'react';
export default function Scene3D() {
const [isClient, setIsClient] = useState(false);
useEffect(() => {
setIsClient(true);
}, []);
// Don't render anything server-side
if (!isClient) return null;
return (
<ThreeLensProvider>
<ThreeLensCanvas>
{/* ... */}
</ThreeLensCanvas>
</ThreeLensProvider>
);
}2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
Step 4: Next.js Configuration โ
// next.config.js
/** @type {import('next').NextConfig} */
const nextConfig = {
// Transpile 3Lens packages if needed
transpilePackages: ['@3lens/core', '@3lens/overlay', '@3lens/react-bridge'],
// Webpack configuration for Three.js
webpack: (config) => {
// Handle Three.js imports
config.resolve.alias = {
...config.resolve.alias,
three: require.resolve('three'),
};
return config;
},
};
module.exports = nextConfig;2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
Best Practices โ
- Always use
ssr: false: Three.js cannot run on the server - Add
'use client': Required for all WebGL-using components - Provide loading states: Show feedback while the 3D scene loads
- Transpile packages: Some dependencies may need transpilation
- Handle hydration: Ensure client render matches server expectations
Common Pitfalls โ
| Issue | Solution |
|---|---|
| "window is not defined" | Use dynamic import with ssr: false |
| "document is not defined" | Add 'use client' directive |
| Hydration mismatch | Return null on server, render on client |
| Module not found | Add to transpilePackages in config |
Electron Desktop โ
The Electron example demonstrates 3Lens integration in a native desktop application.
Features Demonstrated โ
- Electron Main Process: App window creation and IPC communication
- Preload Script: Secure API bridge between main and renderer
- Context Isolation: Secure renderer with contextBridge
- GPU Info Access: Electron-specific GPU information
- Native Title Bar: Custom draggable title bar
- 3Lens Integration: Full devtool overlay in desktop app
Quick Start โ
# From the monorepo root
pnpm install
# Development mode (Vite + Electron)
pnpm --filter @3lens/example-electron-desktop dev
# Build for production
pnpm --filter @3lens/example-electron-desktop build
# Package as distributable
pnpm --filter @3lens/example-electron-desktop package2
3
4
5
6
7
8
9
10
11
Project Structure โ
electron-desktop/
โโโ src/
โ โโโ main/
โ โ โโโ main.ts # Electron main process
โ โ โโโ preload.ts # Preload script for IPC bridge
โ โโโ renderer/
โ โโโ index.html # Renderer HTML
โ โโโ main.ts # Three.js scene with 3Lens
โโโ package.json
โโโ tsconfig.json
โโโ tsconfig.main.json
โโโ vite.config.ts2
3
4
5
6
7
8
9
10
11
12
Step-by-Step Walkthrough โ
Step 1: Main Process Setup โ
// src/main/main.ts
import { app, BrowserWindow, ipcMain } from 'electron';
import path from 'path';
const isDev = process.env.NODE_ENV === 'development';
function createWindow() {
const mainWindow = new BrowserWindow({
width: 1400,
height: 900,
webPreferences: {
nodeIntegration: false, // Security: Don't allow Node.js in renderer
contextIsolation: true, // Security: Isolate renderer context
preload: path.join(__dirname, 'preload.js'),
},
});
// Load Vite dev server or built files
if (isDev) {
mainWindow.loadURL('http://localhost:3009');
mainWindow.webContents.openDevTools();
} else {
mainWindow.loadFile(path.join(__dirname, '../renderer/index.html'));
}
}
// Handle IPC for GPU info
ipcMain.handle('get-gpu-info', () => {
return app.getGPUFeatureStatus();
});
ipcMain.handle('get-app-metrics', () => {
return app.getAppMetrics();
});
app.whenReady().then(createWindow);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
Step 2: Preload Script for Secure IPC โ
// src/main/preload.ts
import { contextBridge, ipcRenderer } from 'electron';
// Expose safe APIs to renderer
contextBridge.exposeInMainWorld('electronAPI', {
// GPU information
getGpuInfo: () => ipcRenderer.invoke('get-gpu-info'),
// App performance metrics
getAppMetrics: () => ipcRenderer.invoke('get-app-metrics'),
// Platform info
platform: process.platform,
// App version
appVersion: process.env.npm_package_version || '0.0.0',
});2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
Step 3: Type Definitions for Renderer โ
// src/renderer/electron.d.ts
interface ElectronAPI {
getGpuInfo: () => Promise<Electron.GPUFeatureStatus>;
getAppMetrics: () => Promise<Electron.ProcessMetric[]>;
platform: NodeJS.Platform;
appVersion: string;
}
declare global {
interface Window {
electronAPI?: ElectronAPI;
}
}2
3
4
5
6
7
8
9
10
11
12
13
Step 4: Renderer with 3Lens โ
// src/renderer/main.ts
import * as THREE from 'three';
import { createProbe } from '@3lens/core';
import { bootstrapOverlay } from '@3lens/overlay';
// Create Three.js scene
const scene = new THREE.Scene();
const camera = new THREE.PerspectiveCamera(60, window.innerWidth / window.innerHeight, 0.1, 1000);
const renderer = new THREE.WebGLRenderer({ antialias: true });
// Setup 3Lens probe
const probe = createProbe({ appName: 'My Electron App' });
probe.observeRenderer(renderer);
probe.observeScene(scene);
bootstrapOverlay(probe);
// Access Electron APIs
if (window.electronAPI) {
// Display platform info
console.log('Platform:', window.electronAPI.platform);
// Get GPU feature status
window.electronAPI.getGpuInfo().then((gpuInfo) => {
console.log('GPU Features:', gpuInfo);
});
}
// Animation loop
function animate() {
requestAnimationFrame(animate);
renderer.render(scene, camera);
}
animate();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
Step 5: Vite Configuration for Electron โ
// vite.config.ts
import { defineConfig } from 'vite';
export default defineConfig({
root: 'src/renderer',
base: './', // Important for Electron file:// protocol
build: {
outDir: '../../dist/renderer',
emptyOutDir: true,
},
server: {
port: 3009,
},
});2
3
4
5
6
7
8
9
10
11
12
13
14
Best Practices โ
- Use context isolation: Never disable contextIsolation in production
- Expose only needed APIs: Use contextBridge to expose minimal APIs
- Handle both dev and prod: Support both Vite dev server and built files
- Use preload scripts: Bridge main and renderer processes safely
- Type your APIs: Create type definitions for exposed APIs
Electron-Specific Features โ
// Access GPU info in 3Lens custom panel
probe.on('frame', async () => {
if (window.electronAPI) {
const gpuStatus = await window.electronAPI.getGpuInfo();
// Custom metrics for Electron
probe.setCustomMetric('gpu.webgl', gpuStatus.webgl);
probe.setCustomMetric('gpu.webgl2', gpuStatus.webgl2);
}
});2
3
4
5
6
7
8
9
10
Running All Examples โ
To run all examples simultaneously:
# From the monorepo root
pnpm install
pnpm dev2
3
This starts all examples on different ports:
| Example | Port | URL |
|---|---|---|
| Vanilla Three.js | 3000 | http://localhost:3000 |
| React Three Fiber | 3001 | http://localhost:3001 |
| Angular | 3002 | http://localhost:3002 |
| Vue TresJS | 3003 | http://localhost:3003 |
| Svelte Threlte | 3007 | http://localhost:3007 |
| Next.js SSR | 3008 | http://localhost:3008 |
| Electron | 3009 | Desktop app |
See Also โ
- Getting Started Guide - Basic 3Lens setup
- React/R3F Guide - Deep dive into React integration
- Angular Guide - Deep dive into Angular integration
- Vue/TresJS Guide - Deep dive into Vue integration
- Code Examples - Copy-paste code snippets
- API Reference - Full API documentation