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:

Share the following with your agent to get started:
curl -s "https://instagit.com/install.md"

Works with
Claude Codex Cursor VS Code OpenClaw Any MCP Client

Maintain an open-source project? Get it listed too →