ArchiveJournalFrontend
Frontend · April 26, 2026 · 11 min read

How I rebuilt this portfolio with a single global R3F canvas, GSAP scroll-scrubbed reveals, Lenis smooth scroll, and a shader page-transition curtain — without tanking LCP.

#nextjs#threejs#webgl#gsap#performance
Sanyam Jain
Sanyam JainAuthor
Three.js + Next.js — Shipping a Marketing Site With a WebGL Hero

Why bother

Most developer portfolios look the same: shadcn cards, Inter, a hero with a typewriter. Mine did too. This post is the case study of rebuilding it as a dark, motion-first WebGL experience that still passes Lighthouse.

The stack

  • three.js via @react-three/fiber + drei + @react-three/postprocessing
  • GSAP with ScrollTrigger for scroll-scrubbed timelines
  • Lenis for inertial smooth scroll, bridged into GSAP's ticker
  • zustand as the single source of truth between DOM and GL

One canvas, many scenes

The key architectural choice: one global <Canvas> mounted in the root layout, with drei's <View> tunnels per section. Hero particles, ambient flow-field noise, the wireframe sphere on Skills, the torus knot on Services, the contact halo, and the page-transition curtain all share one WebGL context. Cheaper, smoother, and the curtain can persist across route changes.

Scroll-linked reveals

Every <Display> heading splits into per-word spans with overflow-clip masks. A ScrollTrigger with scrub: 0.6 animates them from y: 110% to 0 — words appear as you scroll past, not just on enter.

The shader page transition

App Router's template.tsx remounts on each navigation. A <TransitionLink> intercepts clicks, runs an exit timeline that ramps a uniform from 0→1 in the curtain shader (noise-displaced wipe + RGB split), then calls router.push. Template remounts and runs the inverse.

Performance posture

The Canvas is next/dynamic with ssr: false, lazy-mounted after requestIdleCallback. LCP is the server-rendered display headline, not the canvas. DPR clamped to 1.75. frameloop="demand" when it's safe.

What I'd change

This is a stub — full breakdown coming soon. If you want the source while I write it, ping me.

About the author

Full-Stack Developer specializing in Ruby on Rails and Next.js