Mike VidalAI Engineeropen to AI / FDE roles
homewritingraw-three-js-not-react-three-fiber

Why my portfolio runs on raw Three.js, not react-three-fiber

The homepage of this site has a little 3D scene: my name, built as a real 3D object you can knock apart with a click, with a camera that drifts toward your cursor as you move it. I built it with graphics code written more or less by hand, instead of the popular shortcut most people reach for.

For most projects that's the wrong choice, and the internet will happily tell you so. For this one it was right. Here's the reasoning — including the parts where I nearly talked myself out of it.

What's actually on the screen

Before defending the how, here's the what:

  • My name as a 3D object. Click it and the letters scatter like knocked-over blocks, then settle back into place.
  • A camera that gently follows your cursor — or your finger, on a phone — so the scene feels alive without being twitchy.
  • A slow-drifting "blob" banner that quietly switches itself off when it scrolls out of view, so it isn't draining your battery for nothing.

All of it runs from one tidy, self-contained piece of code.

The popular shortcut, and why it's usually right

There's a hugely popular tool for building 3D on the web. Think of it as a kit: it handles the fiddly bookkeeping, tidies up after itself when things come and go, and ships with a big box of ready-made parts. For almost any 3D project — especially one with lots of moving pieces a visitor can rearrange — it's the right call, and I'd recommend it without blinking. The cleanup it does for you is real work I've spent hours doing by hand.

A flat-pack kit of glowing geometric parts laid out, waiting to be assembled

Why this one was the exception

Three reasons, in order of how much each mattered:

1. The scene is basically one thing. It's my name, a camera, and some lighting. The kit's big advantage is wrangling lots of pieces at once. When there's really only one, the kit becomes overhead instead of help.

2. The animation is custom. I wanted very specific behavior — pausing cleanly when off-screen, a particular camera drift, an exact "knock the letters apart and reassemble" effect. With the kit you end up constantly reaching around it to get that, at which point you may as well just write it yourself.

3. I'm on brand-new versions of everything. This site runs on bleeding-edge tools, and stacking another layer on top was one more thing to fight whenever something broke. I'd rather own all of it than spend my time debugging someone else's bridge between two fast-moving pieces.

A craftsman carving a glowing geometric crystal by hand

The tradeoff, plainly

Doing it by hand isn't free:

  • It's more code. Every little behavior is mine to write.
  • I have to clean up after the scene myself. The kit does that for you automatically; forget a step doing it by hand and you get a slow leak.
  • I gave up the box of ready-made parts. Anything extra, I wire up myself.

For most projects, that list outweighs the wins. For a single hand-built centerpiece that loads once and just sits there, it doesn't.

When to go hand-built yourself

Skip the kit only when all three are true: your scene is really one custom thing, you need specific behavior the kit fights you on, and it loads once rather than constantly appearing and disappearing. Otherwise, use the kit. It's better at the everyday case for a reason.

The takeaway

Choosing the hand-built route wasn't a stance against the popular tool. It was a read of this scene, on this setup. The right tool is the one that disappears at the exact job in front of you.

If your centerpiece is one crafted object, hand-built code disappears nicely. If it's a thing with twenty parts your visitors assemble, the kit disappears nicely. Pick whichever one makes your code shorter.


For the technically curious: this is raw Three.js, no react-three-fiber. One useHeroScene hook owns the canvas, the render loop, and the resize and pointer handlers; the scene pauses via IntersectionObserver when off-screen, keeps time with THREE.Timer, and resolves clicks with a Raycaster. The code is on GitHub.