# When to Use Framer Motion in a Next.js Project: Lessons from the woosal1337 Blog

> Learn when to use Framer Motion in a Next.js project. Discover how to leverage it for physics-based animations and complex sequences while keeping simple transitions in CSS.

- Repository: [Ege Chelebi/blog](https://github.com/woosal1337/blog)
- Tags: tutorial
- Published: 2026-08-06

---

**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`](https://github.com/woosal1337/blog/blob/main/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:

```typescript
// 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`](https://github.com/woosal1337/blog/blob/main/components/blocks/polaroid.tsx) component implements a photo card that rotates randomly when idle, then springs to zero rotation and scales up on hover:

```typescript
// 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`](https://github.com/woosal1337/blog/blob/main/components/blocks/number-ticker.tsx) uses `useMotionValue` combined with `useSpring` to drive the animation:

```typescript
// 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`](https://github.com/woosal1337/blog/blob/main/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`](https://github.com/woosal1337/blog/blob/main/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:

```typescript
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`](https://github.com/woosal1337/blog/blob/main/components/blocks/contribution-graph.tsx) | Staggered grid animation using variants |
| [`components/blocks/polaroid.tsx`](https://github.com/woosal1337/blog/blob/main/components/blocks/polaroid.tsx) | Interactive rotation and scaling on hover |
| [`components/blocks/number-ticker.tsx`](https://github.com/woosal1337/blog/blob/main/components/blocks/number-ticker.tsx) | Spring-based numeric interpolation |
| [`components/blocks/github-stars.tsx`](https://github.com/woosal1337/blog/blob/main/components/blocks/github-stars.tsx) | Micro-animation for tooltip feedback |
| [`components/blocks/post-toc.tsx`](https://github.com/woosal1337/blog/blob/main/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.