How do Framer animations carry over to Next.js?

Framer animations in Next.js: transitions, appear effects, hover states, variants and page transitions run on motion/react. How to edit them.

Published
Updated
Reading time
6 min
Deframer cover: “Every spring, still springy.” Beside it, a list of Appear effects, Hover states, Variants and Page transitions with Appear effects selected
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)

const transition1 = { … } as const near the top of the component or view file

Appear effects

Animation objects in the view (animation, animation1 …) applied through kit helpers

Hover and press states

Variant states on the component, played by motion

Component variants

Human-readable names such as variant === "Desktop" with a CSS class per variant

Page transitions

pageEffects in src/site/routes.ts

Reduced-motion site setting

reducedMotion in src/site/routes.ts

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):

tsx
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:

tsx
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.

tsx
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:

tsx
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.

ts
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.

  1. Run the project locally with npm install and npm run dev, and open the page with the menu.
  2. Find the component file. Components sit in src/components/, named after the designer's components and folders (for example src/components/nav-menu.tsx). Searching the project for the layer name usually gets you there fastest.
  3. 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.
  4. Change one value at a time. Raising stiffness makes the spring faster and snappier; raising damping reduces overshoot; raising mass makes it feel heavier and slower to settle.
tsx
   // 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;
  1. 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:

tsx
"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:

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.

  1. 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.
  2. Check every breakpoint. Variants often differ between Desktop, Tablet and Phone. The breakpoints guide explains how those layouts are split.
  3. Test reduced motion. In Chrome DevTools, open the Rendering panel and emulate prefers-reduced-motion: reduce, then reload.
  4. Test a production build. Run npm run build and npm run start. Development mode adds overhead, so judge smoothness on the production build.
  5. 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.

Keep reading

All posts
  • Deframer cover: “No export? Four ways out.” Beside it, four tiles, Rebuild, Snapshot, Converter and Reverse proxy, with Converter selected

    Export6 min read

    Can you export code from Framer?

    Framer has no code export. Here are the four real routes to your site's code (rebuild, HTML snapshot, converter, reverse proxy) and how to pick one.

  • Deframer cover: “To Next.js, step by step.” Beside it, a list of Framer, Next.js, TypeScript and Deploy anywhere with Next.js selected

    Migration6 min read

    How to export a Framer site to Next.js

    Export a Framer site to Next.js step by step: convert, compare, verify ownership and download an App Router TypeScript project, then check it.