The portfolio you are reading uses three animation libraries. Next.js 14 for the framework, Framer Motion for component transitions, and GSAP + ScrollTrigger for scroll-driven sequences. The decision was not made in advance. Each library earned its place by being the best tool for a specific job. This is a technical note on what those jobs are, where the boundaries sit, and how to keep the bundle from collapsing.
The division of labor
Framer Motion handles component-level animation. Mount / unmount, layout transitions, hover states, modal reveals, accordion height. The work is local to a component, lasts under 600ms, and respects React’s render cycle. Framer Motion’s API is React-native, it speaks the language of state, props, and refs. You do not fight it.
GSAP + ScrollTrigger handles scroll-driven sequences. The 3D scene, the parallax, the marquees, the scroll-linked word scrubs. The work is timeline-based, often 1-3 seconds long, and lives outside React’s render cycle. GSAP speaks the language of timelines, eases, and scroll positions. You do not try to make it declarative.
Next.js 14 handles the rest. The App Router, the route transitions, the loading states, the RSC. The framework is the conductor, not an animation library. You do not animate with Next, you animate inside Next.
The boundary rule
The boundary between Framer Motion and GSAP is a simple one. If the animation is triggered by a component lifecycle event (mount, hover, click, state change), use Framer Motion. If the animation is triggered by a scroll position, use GSAP. The two libraries do not mix inside the same component, because their triggers conflict and the resulting animation is jankier than either alone.
Mixing Framer Motion and GSAP inside the same component is a recipe for jank. Pick one. The boundary rule is the only thing that keeps the codebase sane.
A scroll-driven example
The 3D hero scene uses GSAP ScrollTrigger to drive camera and mesh position. The text overlays use Framer Motion for the initial mount animation. The two coexist on the same page because they animate different things.
"color:#9A9A9A">// GSAP owns the camera and the 3D scene.
useGSAP(() => {
gsap.to(camera.position, {
y: scrollY * -0.15,
duration: 0.5,
ease: 'none',
scrollTrigger: {
trigger: heroRef.current,
start: 'top top',
end: 'bottom top',
scrub: true,
},
});
}, []);
"color:#9A9A9A">// Framer Motion owns the text overlay mount.
<motion.h1
initial={{ y: 60, opacity: 0 }}
animate={{ y: 0, opacity: 1 }}
transition={{ duration: 0.8, ease: [0.16, 1, 0.3, 1], delay: 0.2 }}
>
Brand to URL.
</motion.h1>The performance budget
Three animation libraries sounds heavy. It is, on paper. The portfolio’s First Load JS is 196 kB, and GSAP + ScrollTrigger account for about 40 of those. That is the cost. The benefit is that scroll-driven sequences work the way the user expects, they are driven by scroll position, not by React state, so they do not stutter when the main thread is busy.
The budget rule is simple. If the project does not need scroll-driven animation, do not use GSAP. If the project does not need complex component transitions, do not use Framer Motion. If the project is mostly a content site, do not use any of them and rely on CSS transitions. The libraries earn their place when the work demands them.
When to reach for what
- 01CSS transitions, hover, focus, color, opacity, transform. Use for everything that lasts under 300ms and does not need orchestration.
- 02Framer Motion, modals, accordions, layout transitions, page transitions, staggered lists. Use for component-lifecycle animation.
- 03GSAP + ScrollTrigger, scroll-driven sequences, marquees, parallax, word scrubs, timeline-based reveals. Use for scroll-position animation.
- 04WebGL / R3F, 3D scenes, GPU-accelerated motion, custom shaders. Use for the rare project that needs 3D as a primary surface.
The bundle weight
A typical Next.js project with Framer Motion and GSAP weighs in at 180-220 kB First Load JS. That is a real number, and it is the cost of motion. The cost is justified when the motion is the product, a portfolio, a launch site, a product demo. The cost is not justified for a content site, a SaaS dashboard, or an e-commerce checkout flow. For those, CSS transitions and a small Framer Motion presence are enough.
“The libraries earn their place when the work demands them.
The triangulation is not a prescription. It is a pattern that works for the kind of work I do, full-stack brand-and-site engagements where the motion is part of the system. Your project may need one library, not three. The honest answer is to start with CSS, add Framer Motion when the work demands it, and reach for GSAP only when scroll-driven sequences become the right answer. The boundary rule holds either way.
Need production Next.js with motion that stays at 98+ Lighthouse? See web engineering or shipped case studies.