On this page6 sections
When Deframer converts a published Framer site into a Next.js App Router project, the site's transitions, appear effects, hover and press states, variants and page transitions come along. They are played by the motion package (imported as motion/react, the library formerly published as framer-motion) together with a small runtime kit in your project, and springs keep their stiffness, damping and mass. Every animation ends up as plain TypeScript you can read and change, with no Framer runtime involved.
This guide shows where each kind of animation lives in the converted project, how to tune a spring, how reduced motion is handled, and how to check that everything still feels right.
What carries over, and where it lives
Framer stores animations as properties on layers and components. In the converted project they become named constants and props in ordinary .tsx files.
In Framer | In the converted project |
|---|---|
Transitions (spring or tween) | |
Appear effects | Animation objects in the view ( |
Hover and press states | Variant states on the component, played by |
Component variants | Human-readable names such as |
Page transitions | |
Reduced-motion site setting | |
The kit lives in src/lib/deframer and is imported as @/deframer. It is an original, compact reimplementation of the runtime APIs the generated code calls, not Framer's runtime, and only the modules your site uses are kept. Deframer is an independent tool and is not affiliated with Framer.
If you want the full tour of the folders first, read what you get in a converted Next.js project.
Transitions
A transition in a converted component looks like this (real output from a converted template):
const transition1 = { damping: 30, delay: 0, mass: 1, stiffness: 500, type: "spring" } as const;
const transition2 = { damping: 30, delay: 0.1, mass: 1, stiffness: 80, type: "spring" } as const;Tweens keep their duration and cubic-bezier easing:
const transition1 = { delay: 0, duration: 0.45, ease: [0.44, 0, 0.56, 1], type: "tween" } as const;Some Framer springs are defined by duration and bounce rather than physics values, and those stay that way too, for example { bounce: 0.2, delay: 0, duration: 0.4, type: "spring" }. The as const keeps the literal types, so TypeScript knows type is exactly "spring".
Appear effects
Appear animations become pairs of objects in the page view: a starting state and a target state with its transition.
const transition1 = { damping: 30, delay: 0.05, mass: 1, stiffness: 80, type: "spring" } as const;
const animation = { opacity: 1, rotate: 0, scale: 1, x: 0, y: 0, transition: transition1 /* … */ } as const;
const animation1 = { opacity: 0.001, rotate: 0, scale: 1, x: 0, y: 40 /* … */ };The server render paints the starting state, so visitors don't see a flash of final content, and motion plays the target state as soon as the page hydrates. Appear effects set to play on scroll start when the layer enters the viewport.
Hover, press and variants
Components that react to the pointer declare which variants have gestures enabled, and the variant names stay readable:
const enabledGestures = {
Button: { hover: true },
"Button Small": { hover: true },
};The element itself is a motion element (<motion.a>, <motion.div>), and the CSS module holds a class per variant, so many visual differences between states are plain CSS you can inspect in the browser.
Page transitions
Page transitions are site-wide data in src/site/routes.ts: a global enter effect plus per-route overrides.
export const pageEffects = {
global: {
enter: {
opacity: 0,
scale: 1,
transition: { duration: 0.2, ease: [0.44, 0, 0.56, 1], type: "tween" /* … */ },
// …
},
},
};How to edit a spring
Say a menu opens too slowly. Here is the safe way to tune it.
- Run the project locally with
npm installandnpm run dev, and open the page with the menu. - Find the component file. Components sit in
src/components/, named after the designer's components and folders (for examplesrc/components/nav-menu.tsx). Searching the project for the layer name usually gets you there fastest. - Find the transition constant the element uses. Look for
transition={transition2}or__fx_animate={{ transition: transition2 }}and follow it to the constant at the top of the file. - Change one value at a time. Raising
stiffnessmakes the spring faster and snappier; raisingdampingreduces overshoot; raisingmassmakes it feel heavier and slower to settle.
// before
const transition2 = { damping: 30, delay: 0.1, mass: 1, stiffness: 80, type: "spring" } as const;
// after: quicker, still without overshoot
const transition2 = { damping: 30, delay: 0.1, mass: 1, stiffness: 200, type: "spring" } as const;- Save and watch the hot reload. If the same feel should apply elsewhere, move the constant into a shared file (for example add
src/styles/motion.ts) and import it, rather than copying numbers around.
Constants are per file, so transition1 in one component is unrelated to transition1 in another. Renaming them to something descriptive (menuOpen, cardHover) is a good first refactor once you know which is which.
Adding a new animation
You are not limited to what came from Framer. Any component that already runs in the browser (it starts with "use client") can use motion directly:
"use client";
import { motion } from "motion/react";
export function FadeIn({ children }: { children: React.ReactNode }) {
return (
<motion.div
initial={{ opacity: 0, y: 16 }}
whileInView={{ opacity: 1, y: 0 }}
viewport={{ once: true }}
transition={{ type: "spring", stiffness: 300, damping: 30 }}
>
{children}
</motion.div>
);
}This is a new file you would add yourself, for example src/components/fade-in.tsx. Keep server components free of motion and wrap only the parts that animate.
Reduced motion
Framer has a site setting for whether animations respect the visitor's reduced-motion preference, and the converted project keeps your choice in src/site/routes.ts:
/** "user": honour prefers-reduced-motion (site setting); "never": always animate. */
export const reducedMotion = "never";The kit passes this value to motion's MotionConfig. With "user", visitors who ask their operating system for less motion get transforms that jump straight to their end state while opacity still fades. If your site was set to always animate, consider switching to "user": it's a one-word change and an accessibility improvement for people sensitive to movement.
Testing animations after conversion
Animations are where small differences are easiest to feel, so check them deliberately.
- Compare side by side. When a live preview is available, the compare view shows the original and the converted site together at 1440, 768 and 390 pixels wide, with synced scrolling. A live preview lasts 10 minutes once it is ready, so plan what you want to check first. Otherwise, open the original beside the project running locally.
- Check every breakpoint. Variants often differ between Desktop, Tablet and Phone. The breakpoints guide explains how those layouts are split.
- Test reduced motion. In Chrome DevTools, open the Rendering panel and emulate
prefers-reduced-motion: reduce, then reload. - Test a production build. Run
npm run buildandnpm run start. Development mode adds overhead, so judge smoothness on the production build. - Read the notices. The result page lists anything that needed attention, such as components that render only in the browser or Framer features replaced by placeholders. If an animated element is affected, it will be named there.
If an animation looks janky on a real device, the cause is usually heavy images or third-party scripts rather than the animation itself; performance tuning a converted site covers how to measure that.
FAQ
Do I need framer-motion installed?
No. The project depends on motion, the same library under its current package name, imported as motion/react. It is pinned in package.json along with Next.js, React and the site's other packages.
Are spring values exactly the same as in Framer?
Yes, springs keep their stiffness, damping and mass, and tweens keep their duration and easing. The result is close to the original but not guaranteed identical, so compare the pages that matter most, as the FAQ recommends.
Where are page transitions configured?
In src/site/routes.ts, under pageEffects. The global entry applies to every navigation, and per-route overrides follow it.
Can I remove an animation completely?
Yes. Set the starting state equal to the end state, or render a plain element instead of the motion one. Because the code is ordinary TypeScript, you can delete what you don't need, and your editor will flag constants that are no longer used.
