Motion Journey
I recently decided to dive into animation and bring some motion into my developer portfolio. I was especially excited to use Framer Motion for smooth page and component transitions in my Next.js 14/15 projects.
But as my pages multiplied and my codebase grew, I started seeing the same animated blocks over and over:
import { motion } from "framer-motion";
<motion.div initial={...} animate={...} transition={...}>
{/* cool stuff */}
</motion.div>
I began thinking, there must be a cleaner, reusable pattern, especially for larger apps and teams. So, I set off on a weekend of experimenting, refactoring, and (mis)reading docs. Here’s what I discovered, and why I landed on the MotionWrapper.tsx
approach below.
The Problem: Repetition, Rigidity, and Scalability
Framer Motion’s API is direct:
- Import motion
- Pick your tag (motion.div)
- Pass your animation props
For quick demos, it’s great. But on a big Next.js site, you start to want:
- DRY code: Avoid rewriting the same initial, animate, and transition blocks everywhere.
- Centralized changes: Animation defaults in one place.
- Easy migration: What if we ever want to swap out Framer Motion, or add metrics/analytics?
- Team conventions: Make it obvious how to animate anything, without copy/pasting docs into every PR.
My Experiment: Wrapping motion.div
After poking around the official docs and a handful of GitHub threads, I found little on "wrapping motion components." But I tried it anyway.
Step one was a TypeScript-first wrapper:
// components/MotionWrapper.tsx
'use client';
import { motion, ComponentPropsWithoutRef } from 'framer-motion';
/**
* A reusable, type-safe wrapper for motion.div.
*/
export type MotionDivProps = ComponentPropsWithoutRef<typeof motion.div>;
export const MotionDiv = (props: MotionDivProps) => <motion.div {...props} />;
Why this approach?
- It forwards all props, including
initial
,animate
,transition
, and any HTML div attributes. - The types always match whatever changes in framer-motion’s internals.
- It gives you a single place to set defaults, instrument analytics, or swap animation libraries.
Putting It Into Practice: Animated Profile Card
This component card is from my NeatDev Template. With the MotionWrapper
animating any component is as simple as:
import { MotionDiv } from '@/components/MotionWrapper';
export function ProfileCard() {
return (
<MotionDiv
initial={{ opacity: 0, y: 20 }}
animate={{ opacity: 1, y: 0 }}
transition={{ duration: 0.5, ease: 'easeOut' }}
className="your-styles"
>
{/* Your entire component/section/etc. */}
</MotionDiv>
);
}
And, for example, your entire page:
export default function HomePage() {
return (
<MotionDiv initial={{ opacity: 0 }} animate={{ opacity: 1 }}>
<h1>Hello, Next.js + Framer Motion!</h1>
{/* ... */}
</MotionDiv>
);
}
This means every developer now knows exactly where to look for animation conventions, and any project-wide change is a one-liner.
Best Practices I Landed On
Through trial and error, here are my takeaways:
- Only Wrap When You Need To: If you’re building a tiny site, direct
motion.div
is fine. Wrapping pays off as code grows or teams scale. - Type Carefully: Use
ComponentPropsWithoutRef<typeof motion.div>
to keep types as tight as possible (for future-proofing). - Centralize Defaults Sparingly: If you want all motion divs to share an animation (e.g., a fade-in), set it in
MotionDiv
. Otherwise, pass props per usage. - Document the Pattern: Leave a comment in the component about why you’re wrapping, so new devs understand when to use it.
- Reference the Docs: Stay up-to-date, the Framer Motion’s API evolves, and keeping your wrapper simple makes maintenance easy. Official docs: Framer Motion React Quick Start.
Why I Use This in Production
After several weeks, this pattern has made animations more consistent, easier to refactor, and more approachable. It also opened the door to other wrappers (like for transitions, modals, or even “motion.section” elements).
Animation should be fun and simple, not another source of tech debt. A single, dedicated MotionWrapper
lets you focus on the fun part: creating vibrant interfaces.
Final Thoughts: When to [Not] Use This
This isn’t a silver bullet. If you only animate one or two things, the wrapper may add unnecessary indirection. But for modern Next.js, TypeScript-heavy, or team-based projects, I find this consistently leads to cleaner, more maintainable code.
If nothing else, experimenting with your own MotionWrapper will teach you a lot about TypeScript, React props, and scalable front-end patterns. Try it, test it, and build something that’s not just animated, but maintainable, too.
You Might Also Like...
- NeatDev: The Portfolio I Wish Existed (So I Built It For Free)
- LaunchKit Template: Build Your SaaS, Not Your Landing Page
- Snippify This! - A Tailwind CSS UI Snippets Collection
- Astro + Tailwind + React: A Simple Link-in-Bio Project
- TypeSavior - Your AI JavaScript to TypeScript Converter
- Building ImgxLab: An Open-Source Lab for Photographers
- 4 Custom Link-In-Bio HTML Templates