When to Use Framer Motion in a Next.js Project: Lessons from the woosal1337 Blog
Framer Motion should be reserved for physics-based animations, coordinated multi-element sequences, and interactive UI states that cannot be expressed with CSS alone, while simple transitions should remain CSS-only to minimize bundle size.
The woosal1337/blog repository demonstrates a disciplined approach to animation in modern React 18 and Next.js 13/14 App Router applications. By examining the production components in components/blocks/, we can establish clear criteria for when to import framer-motion versus relying on Tailwind CSS utilities.
When Framer Motion Becomes Essential
Framer Motion excels in scenarios where CSS transitions and keyframes reach their declarative limits. According to the source code analysis, the library is specifically leveraged for five distinct categories of motion that require runtime physics, gesture handling, or complex coordination.
Complex Coordinated UI Animations
When you need to orchestrate multiple elements with staggered timing or spring physics, Framer Motion's variants system becomes necessary. In components/blocks/contribution-graph.tsx, the repository implements a GitHub-style contribution grid where each day-square enters with a cascading delay.
The component uses a parent motion.div with a container variant that defines staggerChildren, allowing the grid to animate as a coordinated unit rather than managing individual CSS animations:
// components/blocks/contribution-graph.tsx
<motion.div
variants={container}
initial="hidden"
animate={play ? "show" : "hidden"}
className="inline-grid grid-cols-7 gap-1"
>
{contributions.map((day, i) => (
<motion.div key={i} variants={item} className={/* color logic */} />
))}
</motion.div>
Interactive Hover-to-Reveal Effects
For elements requiring dynamic property changes on interaction—such as rotation, scale, or cursor state changes—Framer Motion's gesture props (whileHover, whileTap) provide physics-based feedback that CSS :hover cannot replicate cleanly.
The components/blocks/polaroid.tsx component implements a photo card that rotates randomly when idle, then springs to zero rotation and scales up on hover:
// components/blocks/polaroid.tsx
<motion.div
onClick={onClick}
animate={{ rotate: fullscreen ? 0 : randomRotation }}
whileHover={{
rotate: 0,
scale: 1.2,
zIndex: 20,
cursor: "zoom-in",
}}
className={cn("w-20 h-auto z-10 relative", fullscreen && "w-full h-full")}
>
…
</motion.div>
Animated Numeric Counters
Smoothly interpolating between numeric values—such as incrementing a follower count—requires reactive motion values that update the DOM outside React's render cycle. The components/blocks/number-ticker.tsx uses useMotionValue combined with useSpring to drive the animation:
// components/blocks/number-ticker.tsx
const motionValue = useMotionValue(direction === "down" ? value : 0);
const springValue = useSpring(motionValue, { damping: 100, stiffness: 1000 });
useEffect(() => {
play && setTimeout(() => motionValue.set(direction === "down" ? 0 : value), delay * 1000);
}, [play, delay, value, direction]);
useEffect(() =>
springValue.on("change", (latest) => {
if (ref.current) {
ref.current.textContent = `${Intl.NumberFormat("en-US").format(
Number.parseInt(latest.toFixed(0))
)} ${label ?? ""}`;
}
}), [springValue, label]);
Micro-Animations for UI Feedback
Subtle opacity and transform changes triggered by state updates—such as fading in a tooltip—are handled cleanly with motion.div and the animate prop. The components/blocks/github-stars.tsx wraps the star count in a motion component to provide immediate visual feedback without CSS class toggling.
Dynamic Table of Contents Navigation
When navigation must react to scroll position while animating layout properties like underline width, Framer Motion provides a declarative API for these reactive states. In components/blocks/post-toc.tsx, the active heading link uses motion.a with variants to handle the complex interaction between scroll detection and visual highlighting.
When to Avoid Framer Motion
The woosal1337/blog codebase maintains a strict boundary between animation libraries and CSS utilities to optimize bundle size. You should avoid Framer Motion for:
- Simple fade, slide, or scale transitions that can be expressed with Tailwind CSS utilities (
transition,duration-150,ease-out). - Purely decorative animations that do not enhance user experience or provide feedback.
- Server-rendered components where client-side animation is unnecessary; keep these as pure React/Next.js server components.
Implementation Examples from the Source
Basic Entrance Animation
For comparison with CSS, here is a minimal fade-in component:
import { motion } from "framer-motion";
export const FadeIn = ({ children }: { children: React.ReactNode }) => (
<motion.div
initial={{ opacity: 0, y: 10 }}
animate={{ opacity: 1, y: 0 }}
transition={{ duration: 0.3, ease: "easeOut" }}
>
{children}
</motion.div>
);
Key Files Reference
| File | Purpose |
|---|---|
components/blocks/contribution-graph.tsx |
Staggered grid animation using variants |
components/blocks/polaroid.tsx |
Interactive rotation and scaling on hover |
components/blocks/number-ticker.tsx |
Spring-based numeric interpolation |
components/blocks/github-stars.tsx |
Micro-animation for tooltip feedback |
components/blocks/post-toc.tsx |
Scroll-linked navigation states |
Summary
- Use Framer Motion for physics-based motion (springs, damping), coordinated multi-element animations, runtime-driven values (API data), and interactive gestures beyond CSS
:hover. - Prefer CSS for simple opacity/transform transitions using Tailwind utilities to keep bundle size minimal.
- The woosal1337/blog repository treats Framer Motion as a specialized tool for components in
components/blocks/, ensuring that animation logic is isolated and purposeful.
Frequently Asked Questions
Does Framer Motion work with Next.js App Router?
Yes, Framer Motion is fully compatible with Next.js 13/14 App Router and React 18. The woosal1337/blog repository uses it within client components to handle interactive animations while keeping server components lightweight.
How much does Framer Motion increase bundle size?
Framer Motion adds approximately 25-30kB gzipped to your bundle. The woosal1337/blog codebase mitigates this by restricting the library to specific block components (located in components/blocks/) and using CSS for all standard transitions.
Can I use Framer Motion in Next.js Server Components?
No, Framer Motion requires client-side JavaScript to calculate physics and handle gestures. In the woosal1337/blog repository, animated components are marked with "use client" directives, while static content remains server-rendered.
What are the alternatives for simple animations in Next.js?
For simple fade, slide, or scale effects, use Tailwind CSS transition utilities (transition-all, duration-300, hover:scale-105). The woosal1337/blog repository demonstrates this pattern: motion is reserved for physics-based interactions, while Tailwind handles static state changes.
Have a question about this repo?
These articles cover the highlights, but your codebase questions are specific. Give your agent direct access to the source. Share this with your agent to get started:
curl -s "https://instagit.com/install.md" Maintain an open-source project? Get it listed too →