Building a 3D Portfolio with Three.js: Lessons from the Deep End
Building the 3D character on this portfolio was the most technically challenging thing I've done in a frontend context. Here's what I wish I'd known before starting — the GLTF pipeline, WebGL performance, and how to make Three.js play nicely with Angular SSR.
The GLTF Pipeline
GLTF is the right format for web 3D — compact, fast-loading, with excellent Three.js support. But getting from Blender to a web-ready asset took longer than expected. The key steps: optimize geometry, bake textures, export with Draco compression, and validate with the glTF Viewer before integrating.
The SSR Problem
Three.js depends on WebGL, which depends on a canvas element, which doesn't exist in Node.js. This means the LivingPortraitComponent had to be carefully isolated from the SSR render path. The solution: check isPlatformBrowser before initializing Three.js, and set a minimum height on the container to prevent layout shift.
// Core pattern for Three.js + Angular SSR
ngAfterViewInit(): void {
if (!isPlatformBrowser(this.platformId)) return;
// Safe to use WebGL here
this.renderer = new THREE.WebGLRenderer({ antialias: true, alpha: true });
this.initScene();
this.animate();
}Performance: What Actually Matters
The main performance levers for a Three.js portfolio scene: polygon count, texture resolution, draw calls, and animation complexity. The cartoon character was exported at ~18k polygons, uses a single 1024×1024 texture atlas, and runs at 60fps on mid-range mobile hardware.
Don't animate in a requestAnimationFrame loop if nothing is changing. Use a dirty flag to skip renders when the scene hasn't updated.
What I'd Do Differently
I'd start with a simpler model. The urge to use a highly-detailed character drove weeks of optimization work that a simpler low-poly model would have avoided entirely. For a portfolio piece, visual distinctiveness matters more than technical complexity.