I'm going to show you how to do full page transitions with Framer Motion and Next.js
Basic familiarity with Framer Motion and next.js is good to have, but the code is short and not complicated so everyone should be able to follow along.
Check out the demo: https://nextjs-full-page-transitions.netlify.app/
Fork the repository: https://github.com/ivandotv/nextjs-page-transitions
What we are building
Not only we are going to enable full page transitions but we are also going to set up a few different transitions for demo purposes.
The code
Spoiler alert, this is all the code that is needed to enable page transitions in Next.js!
// _app.tsx
function MyApp({ Component, pageProps, router }: AppProps) {
return (
<div className="app-wrap">
<LazyMotion features={domAnimation}>
<AnimatePresence exitBeforeEnter>
<m.div
key={router.route.concat(animation.name)}
className="page-wrap"
initial="initial"
animate="animate"
exit="exit"
variants={animation.variants}
transition={animation.transition}
>
<Component {...pageProps} />
</m.div>
</AnimatePresence>
</LazyMotion>
</div>
)
}
Now let us go through it step by step.
First, you will notice that I'm using LazyMotion
component, instead of regular Motion
component, this is just for reducing the size of the bundle. Framer is not a small library
(around 25kb full), and since this code goes into the Next.js _app
component, which is the main component in the framework, everything that is imported there, will be bundled in the initial bundle download
When you build the project this is indicated by the
First Load JS shared by all
item in the Next.js build report.
Granted, LazyMotion
will not slice the size of the bundle by much (around 5-7kb) but why not use it when it's available. Also, the folks that support Framer
are working on reducing the bundle size even more, so you are ready to receive more size savings in the future.
You will notice that we are using
features={domAnimation}
on theLazyMotion
component which means that we are only bundling a subset of Framer functionality. Things like pan and drag gestures and layout animations will not be available. Check out the official documention for the component to know more.
AnimatePresence Component
AnimatePresence
component is used for animating child components when they are removed from the React tree. It
allows the component to defer unmounting until after the animation is complete. The most crucial property for this component is
exitBeforeEnter
, it allows Framer to animate one component at a time.
So the page, which is the Component
in this whole setup, after a route change will animate out and then the new page (which is also a Component
) will animate in. So there will be no overlap and we only get to see one page at any given time.
UPDATE:
I have updated the demo to show how transitions work when exitBeforeEnter
is set to false. When this property is false
it will enable animations on both pages (new and old at the same time). Make sure to enable the "overlap page transitions" checkbox.
m.div component
Motion
and m
components are the main building blocks for Framer animations. Anything you want animated should go inside these components.
m.div
component is very similar to regular FramerMotion
component. It is an optimized version that works in tandem withLazyMotion
.
By default, the motion component comes pre-bundled with all of its features. The m
component can be used in the same way as Motion
, but it comes with no features preloaded. These are then provided by LazyMotion
.
Animations
Framer supports a lot of different ways to create the actual animations, in this demo we are going to use the labels
functionality of Framer to animate the components. Take a look at this basic example, it will animate ComponentToAnimate
opacity from 0 to 1 and then back to 0.
function MyApp() {
return (
<m.div
initial={{ opacity: 0 }}
animate={{ opacity: 1 }}
exit={{ opacity: 0 }}
transition={{ duration: 0.7 }}
>
<ComponentToAnimate />
</m.div>
)
}
-
initial
- How the element should look when the element is mounted into the React tree (before the animation starts) -
animate
- How it should look when the animation ends (basically when it's animated into position) -
exit
- How it should look when it's animated out (just before it's removed from the React tree ) -
transition
- how will the actual animation behave duration, easing, etc. It is similar to thecss
transitions property.
Our page animations are a little bit more complicated than that, and we are also dynamically changing animations in the demo, so we are going to add another property to the m.div
component
-
variants
- this will allow us to organize animations as objects, and refer to them by name, and switch between them on demand.
Simple example is worth 1000 words:
const myAnimation = {
initial: {
opacity: 0
},
animate: {
opacity: 1
},
exit: {
opacity: 0
},
transition: {
duration: 0.7
}
}
function Component() {
return (
<m.div
initial="initial"
animate="animate"
exit="exit"
transition={myAnimation.transition}
variants={myAnimation}
/>
)
}
So now we can easily switch animations by providing a different object to the variants
property (myAnimation). In the demo, we are doing this via the HTML dropdown elements and simple useState
hook. You can refer to the animations.ts
file to see all the animations that are used in the demo
// animations.ts excerp
const slideUp = {
name: 'Slide Up',
variants: {
initial: {
opacity: 0,
top: '100vh',
scale: 0.4
},
animate: {
opacity: 1,
top: '0vh',
scale: 1
},
exit: {
opacity: 0,
top: '100vh',
scale: 0.4
}
},
transition: {
duration: 0.7
}
}
const slideRight = {
name: 'Slide Right',
variants: {
initial: {
opacity: 0,
left: '-100%',
scale: 0.6
},
animate: {
opacity: 1,
left: 0,
scale: 1
},
exit: {
opacity: 0,
left: '100%',
scale: 0.6
}
},
transition: {
duration: 0.7
}
}
And that's it. As you can see, full-page transitions in Next.js with Framer Motion library are not that complicated :)
Check out the demo: https://nextjs-full-page-transitions.netlify.app/
Fork the repository: https://github.com/ivandotv/nextjs-page-transitions
Top comments (5)
Hi Ivan, this is wonderful but I am having issue.
I followed this and I have the enter animations working. However I can't seem to get the Exit animations working.
The only thing I did different from you is that I didn't implement framer motion in my _app.tsx but rather in a sub page component. Could this be the reason why?
@raymolla7 Did you get this working? I ask because I have the same need. I do NOT want to animate the top level as my animations are for a feature within the app, not the whole app... like only a section of the site. So, I only want to animate only a small section of the site.. So, did you get this working? If so, do you have a repo I can look at? Thanks a bunch.
Probably. My code only animates top level components (pages).
Thanks for this I found it useful however when trying to build and run the repo I get this:
ready - started server on 0.0.0.0:3000, url: localhost:3000
info - Using webpack 5. Reason: Enabled by default nextjs.org/docs/messages/webpack5
Error: error:0308010C:digital envelope routines::unsupported
at new Hash (node:internal/crypto/hash:71:19)
at Object.createHash (node:crypto:133:10)
at BulkUpdateDecorator.hashFactory (/home/charliepi/nextjs-page-transitions/node_modules/next/dist/compiled/webpack/bundle5.js:138971:18)
at BulkUpdateDecorator.update (/home/charliepi/nextjs-page-transitions/node_modules/next/dist/compiled/webpack/bundle5.js:138872:50)
at OriginalSource.updateHash (/home/charliepi/nextjs-page-transitions/node_modules/next/dist/compiled/webpack-sources3/index.js:1:10264)
at NormalModule._initBuildHash (/home/charliepi/nextjs-page-transitions/node_modules/next/dist/compiled/webpack/bundle5.js:68468:17)
at handleParseResult (/home/charliepi/nextjs-page-transitions/node_modules/next/dist/compiled/webpack/bundle5.js:68534:10)
at /home/charliepi/nextjs-page-transitions/node_modules/next/dist/compiled/webpack/bundle5.js:68628:4
at processResult (/home/charliepi/nextjs-page-transitions/node_modules/next/dist/compiled/webpack/bundle5.js:68343:11)
at /home/charliepi/nextjs-page-transitions/node_modules/next/dist/compiled/webpack/bundle5.js:68407:5
Error: error:0308010C:digital envelope routines::unsupported
at new Hash (node:internal/crypto/hash:71:19)
at Object.createHash (node:crypto:133:10)
at BulkUpdateDecorator.hashFactory (/home/charliepi/nextjs-page-transitions/node_modules/next/dist/compiled/webpack/bundle5.js:138971:18)
at BulkUpdateDecorator.update (/home/charliepi/nextjs-page-transitions/node_modules/next/dist/compiled/webpack/bundle5.js:138872:50)
at OriginalSource.updateHash (/home/charliepi/nextjs-page-transitions/node_modules/next/dist/compiled/webpack-sources3/index.js:1:10264)
at NormalModule._initBuildHash (/home/charliepi/nextjs-page-transitions/node_modules/next/dist/compiled/webpack/bundle5.js:68468:17)
at handleParseResult (/home/charliepi/nextjs-page-transitions/node_modules/next/dist/compiled/webpack/bundle5.js:68534:10)
at /home/charliepi/nextjs-page-transitions/node_modules/next/dist/compiled/webpack/bundle5.js:68628:4
at processResult (/home/charliepi/nextjs-page-transitions/node_modules/next/dist/compiled/webpack/bundle5.js:68343:11)
at /home/charliepi/nextjs-page-transitions/node_modules/next/dist/compiled/webpack/bundle5.js:68407:5
node:internal/crypto/hash:71
this[kHandle] = new _Hash(algorithm, xofLen);
^
Error: error:0308010C:digital envelope routines::unsupported
at new Hash (node:internal/crypto/hash:71:19)
at Object.createHash (node:crypto:133:10)
at BulkUpdateDecorator.hashFactory (/home/charliepi/nextjs-page-transitions/node_modules/next/dist/compiled/webpack/bundle5.js:138971:18)
at BulkUpdateDecorator.update (/home/charliepi/nextjs-page-transitions/node_modules/next/dist/compiled/webpack/bundle5.js:138872:50)
at OriginalSource.updateHash (/home/charliepi/nextjs-page-transitions/node_modules/next/dist/compiled/webpack-sources3/index.js:1:10264)
at NormalModule._initBuildHash (/home/charliepi/nextjs-page-transitions/node_modules/next/dist/compiled/webpack/bundle5.js:68468:17)
at handleParseResult (/home/charliepi/nextjs-page-transitions/node_modules/next/dist/compiled/webpack/bundle5.js:68534:10)
at /home/charliepi/nextjs-page-transitions/node_modules/next/dist/compiled/webpack/bundle5.js:68628:4
at processResult (/home/charliepi/nextjs-page-transitions/node_modules/next/dist/compiled/webpack/bundle5.js:68343:11)
at /home/charliepi/nextjs-page-transitions/node_modules/next/dist/compiled/webpack/bundle5.js:68407:5 {
opensslErrorStack: [ 'error:03000086:digital envelope routines::initialization error' ],
library: 'digital envelope routines',
reason: 'unsupported',
code: 'ERR_OSSL_EVP_UNSUPPORTED'
}
Node.js v18.12.1
same problem i have.....don't know what's the cause.