Edit Page

#Reduce bundle size

Learn what makes up Motion's bundle size, and how to reduce it.

As of version 4.0, the full gzipped and minified size of Framer Motion is just under 30kb.

However, modern JavaScript bundlers like Rollup and Webpack are capable of "tree shaking", which means that only the code you import is shipped to consumers.

For example, if you're only importing AnimatePresence and usePresence to implement your own exit animations with CSS or a third-party library, you'd only add 1.4kb to your bundle.

import { AnimatePresence, usePresence } from "framer-motion" // 1.4kb
Note: All sizes quoted in this guide are from Rollup -generated bundles. Webpack is less effective at tree-shaking and should generate slightly larger bundles.

The main import from Framer Motion is the motion component and, by default, this can add around 25kb to your bundle.

import { motion } from "framer-motion" // ~25kb

Framer Motion's simple, props-driven API is the reason for this extra size. Unused features aren't tree-shaken, as it isn't currently possible to detect which features you're going to use at build time.

However, by using the m and LazyMotion components, we can bring this down significantly, to just under 5kb for the initial render.

#How to reduce bundle size

Instead of importing motion, import the slimmer m component.

import { m } from "framer-motion"

m is used in the exact same way as motion, but unlike motion, the m component doesn't come preloaded with features like exit animations or drag.

Instead, we load these in manually via the LazyMotion component. This lets you choose which features you load in, and whether you load them synchronously or asynchronously.

import { LazyMotion, domAnimation } from "framer-motion"

// Load only the domAnimation package
function App({ children }) {
  return (
    <LazyMotion features={domAnimation}>
      {children}
    </LazyMotion>
  )
}

#Available features

There are currently two feature packages you can load:

  • domAnimation: This provides support for animations, variants, exit animations, and tap/hover/focus gestures. (~13kb)
  • domMax: This provides support for all of the above, plus pan/drag gestures and layout animations. (~18kb)

In the future it might be possible to offer more granular feature packages, but for now these were chosen to reduce the amount of duplication between features, which could result in much more data being downloaded ultimately.

#Synchronous loading

By passing one of these feature packages to LazyMotion, they'll be bundled into your main JavaScript bundle.

import { LazyMotion, domAnimation } from "framer-motion"

function App({ children }) {
  return (
    <LazyMotion features={domAnimation}>
      {children}
    </LazyMotion>
  )
}

#Async loading

If you're using a bundler like Webpack or Rollup, we can pass a dynamic import function to features that will fetch features only after we've performed our initial render.

First, create a file that exports only the features you want to load.

// features.js
import { domMax } from "framer-motion"
export default domMax

Then, pass features a function that will dynamically load that file.

import { LazyMotion, m } from "framer-motion"

// Make sure to return the specific export containing the feature bundle.
const loadFeatures = () =>
  import("./features.js").then(res => res.default)

// This animation will run when loadFeatures resolves.
function App() {
  return (
    <LazyMotion features={loadFeatures}>
      <m.div
        initial={{ opacity: 0 }}
        animate={{ opacity: 1 }}
      />
    </LazyMotion>
  )
}

#Developer tools

Because the normal motion component still preloads all of its functionality, including it anywhere will break the benefits of using LazyMotion.

To help prevent this, the strict prop can be set on LazyMotion. If a motion component is loaded anywhere within, it will throw with a reminder to render the m component instead.

function App() {
  // This will throw!
  return (
    <LazyMotion strict>
      <motion.div />
    </LazyMotion>
  )
}