Integrate Three.js with React/Next.js using React Three Fiber for subtle 3D UI elements. Use when users request 3D cards, 3D backgrounds, interactive 3D UI components, parallax effects, 3D hover animations, or need to add Three.js to React/Next.js applications. Focus on performance-optimized, UI-focused 3D elements, not heavy 3D scenes or games.
Integrate subtle, performant 3D UI elements into React/Next.js applications using React Three Fiber.
UI-First 3D Approach:
Required Dependencies:
{
"@react-three/fiber": "^8.15.0",
"@react-three/drei": "^9.92.0",
"three": "^0.160.0"
}
Optional (Performance):
{
"@react-three/postprocessing": "^2.16.0",
"zustand": "^4.4.0"
}
When a user requests 3D UI integration, follow this structured approach:
Ask clarifying questions (max 3-4):
Recommend the appropriate pattern from these categories:
1. 3D Background (Ambient)
2. 3D Cards (Interactive)
3. 3D Hover Effects (Micro-interactions)
4. Parallax Layers (Depth)
For each pattern, provide:
File Structure:
components/
├── three/
│ ├── Scene.tsx (Main 3D scene wrapper)
│ ├── FloatingCard.tsx (3D card component)
│ └── Background.tsx (3D background)
Canvas Wrapper (components/three/Scene.tsx):
'use client'
import { Canvas } from '@react-three/fiber'
import { useReducedMotion } from 'framer-motion' // Optional
export default function Scene({ children }: { children: React.ReactNode }) {
const prefersReducedMotion = useReducedMotion()
if (prefersReducedMotion) {
return null // Skip 3D if user prefers reduced motion
}
return (
<Canvas
camera={{ position: [0, 0, 5], fov: 75 }}
gl={{
antialias: false, // Disable AA for performance
alpha: true, // Transparent background
powerPreference: 'high-performance'
}}
dpr={[1, 2]} // Limit pixel ratio (no 3x on high-DPI)
style={{
position: 'absolute',
top: 0,
left: 0,
width: '100%',
height: '100%',
pointerEvents: 'none' // Allow clicks to pass through
}}
>
<ambientLight intensity={0.5} />
<pointLight position={[10, 10, 10]} intensity={0.8} />
{children}
</Canvas>
)
}
Key Optimizations:
antialias: false - Save GPU (use CSS for smoothing)dpr={[1, 2]} - Cap pixel ratio to avoid 3x renderingpowerPreference: 'high-performance' - Use discrete GPUpointerEvents: 'none' - Prevent blocking UI interactionsComponent (components/three/FloatingCard.tsx):
'use client'
import { useRef } from 'react'
import { useFrame } from '@react-three/fiber'
import { RoundedBox } from '@react-three/drei'
import * as THREE from 'three'
export function FloatingCard({ position = [0, 0, 0] }) {
const meshRef = useRef<THREE.Mesh>(null)
// Gentle floating animation
useFrame((state) => {
if (!meshRef.current) return
const time = state.clock.getElapsedTime()
meshRef.current.position.y = position[1] + Math.sin(time * 0.5) * 0.1
meshRef.current.rotation.x = Math.sin(time * 0.3) * 0.05
meshRef.current.rotation.y = Math.cos(time * 0.2) * 0.05
})
return (
<RoundedBox
ref={meshRef}
args={[2, 3, 0.2]} // width, height, depth
radius={0.1} // rounded corners
smoothness={4} // corner segments
position={position}
>
<meshStandardMaterial
color="#6366F1"
metalness={0.3}
roughness={0.4}
transparent
opacity={0.8}
/>
</RoundedBox>
)
}
Performance Notes:
useFrame for animations (60fps synchronized)Component (components/three/Background.tsx):
'use client'
import { useRef, useMemo } from 'react'
import { useFrame } from '@react-three/fiber'
import * as THREE from 'three'
export function ParticleBackground({ count = 100 }) {
const points = useRef<THREE.Points>(null)
// Generate particle positions once
const positions = useMemo(() => {
const positions = new Float32Array(count * 3)
for (let i = 0; i < count; i++) {
positions[i * 3] = (Math.random() - 0.5) * 10
positions[i * 3 + 1] = (Math.random() - 0.5) * 10
positions[i * 3 + 2] = (Math.random() - 0.5) * 10
}
return positions
}, [count])
// Subtle rotation
useFrame(() => {
if (!points.current) return
points.current.rotation.y += 0.0005
})
return (
<points ref={points}>
<bufferGeometry>
<bufferAttribute
attach="attributes-position"
count={count}
array={positions}
itemSize={3}
/>
</bufferGeometry>
<pointsMaterial
size={0.05}
color="#8B5CF6"
transparent
opacity={0.6}
sizeAttenuation
/>
</points>
)
}
Performance Notes:
useMemo for static geometryBufferGeometry over Geometry (faster)Component (components/three/HoverCube.tsx):
'use client'
import { useRef, useState } from 'react'
import { useFrame, ThreeEvent } from '@react-three/fiber'
import { Box } from '@react-three/drei'
import * as THREE from 'three'
export function HoverCube() {
const meshRef = useRef<THREE.Mesh>(null)
const [hovered, setHovered] = useState(false)
// Smooth transition to hover state
useFrame(() => {
if (!meshRef.current) return
const targetScale = hovered ? 1.2 : 1
meshRef.current.scale.lerp(
new THREE.Vector3(targetScale, targetScale, targetScale),
0.1 // Smoothing factor
)
})
return (
<Box
ref={meshRef}
args={[1, 1, 1]}
onPointerOver={(e: ThreeEvent<PointerEvent>) => {
e.stopPropagation()
setHovered(true)
}}
onPointerOut={() => setHovered(false)}
>
<meshStandardMaterial
color={hovered ? '#8B5CF6' : '#6366F1'}
/>
</Box>
)
}
Interaction Notes:
lerp for smooth transitionsstopPropagation prevents event bubblingDevice Detection:
const isMobile = /iPhone|iPad|iPod|Android/i.test(navigator.userAgent)
if (isMobile) {
return <SimpleVersion /> // Skip 3D on mobile
}
return <Canvas>...</Canvas>
Load 3D only when visible:
import dynamic from 'next/dynamic'
const Scene = dynamic(() => import('@/components/three/Scene'), {
ssr: false, // Disable SSR for Three.js
loading: () => <div>Loading 3D...</div>
})
Throttle animations on low FPS:
useFrame((state, delta) => {
// Skip frame if delta > 33ms (below 30fps)
if (delta > 0.033) return
// Your animation code
})
Dispose geometries and materials:
useEffect(() => {
return () => {
// Cleanup on unmount
if (meshRef.current) {
meshRef.current.geometry.dispose()
meshRef.current.material.dispose()
}
}
}, [])
Reduce complexity at distance:
import { Lod } from '@react-three/drei'
<Lod distances={[0, 5, 10]}>
<HighDetailMesh /> {/* Close */}
<MediumDetailMesh /> {/* Medium */}
<LowDetailMesh /> {/* Far */}
</Lod>
'use client'
import { useEffect, useState } from 'react'
export function useResponsive3D() {
const [show3D, setShow3D] = useState(true)
useEffect(() => {
const checkViewport = () => {
const width = window.innerWidth
setShow3D(width >= 768) // Only on tablet+
}
checkViewport()
window.addEventListener('resize', checkViewport)
return () => window.removeEventListener('resize', checkViewport)
}, [])
return show3D
}
// Usage
function MyComponent() {
const show3D = useResponsive3D()
return show3D ? <Canvas>...</Canvas> : <StaticFallback />
}
Specs:
Specs:
Specs:
'use client'
import { useReducedMotion } from 'framer-motion'
export function AccessibleScene({ children }) {
const prefersReducedMotion = useReducedMotion()
if (prefersReducedMotion) {
return <StaticAlternative /> // Show static version
}
return <Canvas>{children}</Canvas>
}
'use client'
import { Suspense } from 'react'
import { ErrorBoundary } from 'react-error-boundary'
export function Safe3DScene() {
return (
<ErrorBoundary fallback={<StaticVersion />}>
<Suspense fallback={<LoadingPlaceholder />}>
<Canvas>...</Canvas>
</Suspense>
</ErrorBoundary>
)
}
Import only what you need:
// ❌ Bad (imports entire library)
import { Box, Sphere, RoundedBox } from '@react-three/drei'
// ✅ Good (tree-shakeable)
import { Box } from '@react-three/drei/core/Box'
import { Sphere } from '@react-three/drei/core/Sphere'
// Lazy load heavy components
const HeavyScene = lazy(() => import('./HeavyScene'))
// Only load when needed
{showAdvanced && <HeavyScene />}
Client Component Marker:
'use client' // REQUIRED at top of file
import { Canvas } from '@react-three/fiber'
Server Component Integration:
// page.tsx (Server Component)
import dynamic from 'next/dynamic'
const ClientScene = dynamic(() => import('@/components/three/Scene'), {
ssr: false
})
export default function Page() {
return (
<div>
<h1>Server-rendered content</h1>
<ClientScene /> {/* Client-only 3D */}
</div>
)
}
For more complex scenarios, see:
When providing 3D integration guidance, structure as:
## 3D Integration: [Component Name]
### Overview
- Purpose: [What 3D effect achieves]
- Performance Impact: [Low/Medium/High]
- Device Support: [Desktop/Mobile/Both]
### Implementation
1. Dependencies: [npm packages needed]
2. Component Code: [React component]
3. Canvas Setup: [Canvas configuration]
### Optimizations
- [Performance optimization 1]
- [Performance optimization 2]
- [Performance optimization 3]
### Responsive Behavior
- Desktop: [Full 3D experience]
- Tablet: [Simplified version]
- Mobile: [Static fallback or disabled]
### Accessibility
- Reduced motion: [Fallback]
- Keyboard nav: [If applicable]
- Screen readers: [ARIA labels]
Remember: 3D should enhance, not hinder. Performance is non-negotiable. Always provide graceful fallbacks.