Building a 3D Portfolio with React Three Fiber
How I built an interactive 3D hub with procedural icons, glass bubbles, and a carousel orbit — all without external 3D models.
Why 3D?
Most developer portfolios are static grids. I wanted something different — an immersive experience where each project module is a physical object you can explore.
The Stack
- React Three Fiber — React renderer for Three.js
- Drei — Utility components (Environment, Sparkles, etc.)
- Postprocessing — SMAA, Bloom, Chromatic Aberration, Noise, Vignette
- GSAP — UI panel animations
- Zustand — State management bridging 3D and UI layers
Procedural Icons
Every icon is generated in code. No .glb, no Blender. The treble clef uses CatmullRomCurve3 with TubeGeometry. The padlock combines ExtrudeGeometry shapes. The gamepad is a group of rounded boxes and spheres.
const curve = new THREE.CatmullRomCurve3(points, false, 'catmullrom', 0.5)
const geometry = new THREE.TubeGeometry(curve, 100, 0.08, 8, false)
The trick: white cores with colored emissive glow. This gives contrast against the colored glass bubbles.
Glass Material
Each bubble uses meshPhysicalMaterial with:
transmission: 0.9— see-through glass effectiridescence: 0.4— rainbow reflectionsclearcoat: 1— glossy surface
Critical gotcha: transmission requires an <Environment> component in the scene, otherwise the glass renders black.
Orbit Architecture
The 7 modules sit on a rotating ring (OrbitRing). The ring rotates at 0.1 rad/s (~60s per revolution) with a 20° tilt for a plunging perspective.
The camera fly-through reads the orbit angle from a shared mutable object (orbitState) — not Zustand — to avoid React re-renders at 60fps.
What I Learned
- Performance matters. Post-processing is expensive. Use
multisampling={0}with SMAA instead of MSAA. - Module positions are local to the orbit group. World positions need matrix transforms.
- Web Audio API has strict autoplay policies. Always
resume()the AudioContext on user interaction.