skip to Main Content

I’m adding page transitions to a Gatsby site I’m working on using the framer-motion library. Currently, it works great on my local setup, but when I push to production, the exit animation no longer fires.

I’m wrapping my pages in the AnimatePresence component using gatsby-browser.js:

import React from 'react';
import {motion, AnimatePresence} from 'framer-motion';

export const wrapPageElement = ({element}) => (
  <AnimatePresence exitBeforeEnter>{element}</AnimatePresence>
);

And then have a motion.main component around the page content in my layout.js file:

/** @jsx jsx */
import * as React from "react"
import { Global } from "@emotion/react"
import { Box, Container, jsx } from "theme-ui"
import Seo from "./seo"
import Header from "./header"
import Footer from "./footer"
import CodeStyles from "../styles/code"
import SkipNavLink from "./skip-nav"
import { motion, AnimatePresence } from 'framer-motion'

type LayoutProps = { children: React.ReactNode; className?: string }

const Layout = ({ children, className = `` }: LayoutProps) => (
  <React.Fragment>
    <Global
      styles={(theme) => ({
        "*": {
          boxSizing: `inherit`,
        },
        html: {
          WebkitTextSizeAdjust: `100%`,
        },
        img: {
          borderStyle: `none`,
        },
        pre: {
          fontFamily: `monospace`,
          fontSize: `1em`,
        },
        "[hidden]": {
          display: `none`,
        },
        "::selection": {
          backgroundColor: theme.colors.text,
          color: theme.colors.background,
        },
        a: {
          transition: `all 0.3s ease-in-out`,
          color: `text`,
        },
      })}
    />
    <Seo />
    <SkipNavLink>Skip to content</SkipNavLink>
    <Container>
      <Header />
      <motion.main
          key={location.pathname}
          initial={{ opacity: 0, y: 2.5 }}
          animate={{ opacity: 1, y: 0 }}
          exit={{ opacity: 0, y: -2.5 }}
          transition={{
            type: "spring",
            damping: 8,
            mass: .6,
            stiffness: 70,
          }}
        >
        <Box id="skip-nav" sx={{ ...CodeStyles }} className={className}>
          {children}
        </Box>
      </motion.main>
      <Footer />
    </Container>
  </React.Fragment>
)

export default Layout

Does anyone have ideas as to why this might not be working when I deploy? (FYI I am using Netlify). I’ve tried many different semantic variations of this idea to no avail. Could it be how I’m declaring the key for the motion component? How I’m wrapping the AnimatePresence and motion components?

2

Answers


  1. Try making motion.main a direct descendent of AnimatePresence.

    E.g.

    <AnimatePresence exitBeforeEnter>
         <motion.main
              key={location.pathname}
              initial={{ opacity: 0, y: 2.5 }}
              animate={{ opacity: 1, y: 0 }}
              exit={{ opacity: 0, y: -2.5 }}
              transition={{
                type: "spring",
                damping: 8,
                mass: .6,
                stiffness: 70,
              }}
            >
            <Box id="skip-nav" sx={{ ...CodeStyles }} className={className}>
              {children}
            </Box>
          </motion.main>
    </AnimatePresence>
    

    This is written in the docs. Probably because there are too many wrappers before your motion.main component.

    The children of AnimatePresence can also be custom components. The
    only requirement is that somewhere within this component is at least
    one motion component with an exit prop.

    Note: The custom component being removed from the DOM must still be a
    direct descendant of AnimatePresence for the exit animation(s) it
    contains to trigger.

    Login or Signup to reply.
  2. wrapPageElement is a shared API between Gatsby Browser (gatsby-browser.js) and Gatsby SSR (gatsby-ssr.js). Try adding the same wrapping component in the gatsby-ssr.js:

    import React from 'react';
    import {motion, AnimatePresence} from 'framer-motion';
    
    export const wrapPageElement = ({element}) => (
      <AnimatePresence exitBeforeEnter>{element}</AnimatePresence>
    );
    

    In the docs:

    Note: […] It is recommended to use both APIs together.

    The fact that works in the local environment and not in Netlify leads me to think that you can potentially have mismatching Node versions between both environments, causing a different dependency tree. If building the site locally makes your animation work, the issue is on Netlify’s side (or in the Node versions installed, configuration). If the local built site doesn’t (same behavior as Netlify’s) the issue is on your code.

    You can try setting in both environments the same Node version. To do so automatically, you can run:

    node -v > .nvmrc
    

    This will create a .nvmrc file containing the Node version (node -v). When Netlify finds this file at the root of your project, it uses the Node version as a base for installing the dependencies (docs: https://docs.netlify.com/configure-builds/manage-dependencies/#node-js-and-javascript)

    Try both workarounds and see how’s the debugging. As I said, if both environments has the exact same configuration the built shouldn’t work either locally so the issue should be in your code (missing wrapPageElement wrapping in gatsby-ssr.js, etc).

    Login or Signup to reply.
Please signup or login to give your own answer.
Back To Top
Search