Three.js From Zero · Article s13-12
R3F Portfolio Site
R3F Portfolio Site is Article s13-12 of Three.js From Zero, a MasterAllArts free interactive lesson for artists learning creative 3D on the web.
Season 13 · Article 12 · R3F Mastery
The capstone: a deployable Three.js portfolio site. Scroll-driven story, project cards in 3D space, contact form, lighthouse 90+. Everything from Season 13 working together to ship a real artifact.
Capstone build
Final structure + the patterns that make this stand out from the 500 generic Three.js portfolios already on the web.
The information architecture
Three full-viewport sections, each anchored by a 3D centerpiece:
- Hero — your name in 3D text, slow camera dolly
- Projects — 5-8 project cards orbiting a center, click to expand
- Contact — a 3D mailbox + form
Scroll drives the camera through them. One canvas, three "scenes" composed in a single Three.js scene.
Tech stack
- Vite + React + TypeScript
- @react-three/fiber + drei
- @react-three/postprocessing (bloom on your name and emissive accents)
- Framer Motion for HTML transitions
- Vercel / Cloudflare Pages for deploy
The hero
import { Text3D, Center, Float } from '@react-three/drei';
function Hero() {
return (
<Float speed={1.5} rotationIntensity={0.3} floatIntensity={0.5}>
<Center>
<Text3D font="/inter-bold.json" size={1.2} height={0.3} bevelEnabled bevelSize={0.03}>
Your Name
<meshStandardMaterial color="#ec4899" metalness={0.7} roughness={0.2}
emissive="#ec4899" emissiveIntensity={1} toneMapped={false} />
</Text3D>
</Center>
</Float>
);
}
The project carousel
function Projects() {
const projects = useMemo(() => [/* your data */], []);
return (
<group position={[0, -10, 0]}>
{projects.map((p, i) => {
const angle = (i / projects.length) * Math.PI * 2;
return (
<ProjectCard key={p.id} project={p}
position={[Math.cos(angle) * 4, 0, Math.sin(angle) * 4]}
rotation={[0, -angle, 0]} />
);
})}
</group>
);
}
Scroll-driven camera
function CameraRig() {
const { camera } = useThree();
useFrame(() => {
const t = scrollFraction.current;
const yTarget = -t * 20; // 0 to -20 over full scroll
camera.position.y += (yTarget - camera.position.y) * 0.06;
camera.lookAt(0, yTarget, 0);
});
return null;
}
Lighthouse-90 checklist
The wall portfolios hit:
- preload critical assets — useGLTF.preload in app entry
- compress glb models — Draco + KTX2 (S6-02, S6-03)
- throttle for mobile — drop shadow map size, fewer particles, lower DPR cap
- SSR the HTML — Astro or Next.js renders the page shell instantly; React + R3F hydrate after
- defer postprocessing — load EffectComposer chunk on idle, not on first frame
Common first-time pitfalls
Ship checklist
- Domain bought (yourname.dev)
- Lighthouse: Performance 90+, Accessibility 100, Best Practices 100
- OpenGraph + Twitter card image (your name in 3D, screenshotted)
- Analytics: PostHog or Plausible (privacy-respecting)
- Sitemap.xml + robots.txt
- Test on iPhone SE, Android low-end, Quest browser
- Posted on X, HN ("Show HN"), r/threejs, awwwards.com
You've finished R3F Mastery
Twelve articles. From "what is JSX as scene graph" to a deployable portfolio. You now know enough R3F to take any vanilla three.js demo (including Bruno's) and port it. You can use this season as your reference forever.
SEASON 14 →
S14-01 — Vite + TypeScript from Zero → The setup chapter every R3F dev wishes they'd done first.