profile picture of Olivier Larose

Olivier Larose

December 13, 2023

/

Medium

Nextjs Page Transition Guide

How to Make Creative Page Transitions using Next.js and Framer Motion

A Next.js tutorial featuring awwwards winning page transition using Framer Motion Animate Presence hook. Features 3 different page transitions, a curve, a stair and a perspective page transition.

Hero Image

App Router vs Page Router

Note: This tutorial uses the Page Router. You can find the source code Here.

As of December 2023, page transitions are not officially supported using the App Router which is a shame. However, I do believe the Page Router covers all the needs of an experimental type of website that needs page transitions.

So in this article, I'll take a look at how we can create almost any kind of creative page transition using Next.js with Framer Motion. I'll recreate 3 awwwards winning transitions from popular websites.

3 Awwwards Page Transitions

I'll take a look at 3 different awwwards winning page transitions:

  • Denis Snellenberg's portfolio: https://dennissnellenberg.com/
  • Alex Tkachev's portfolio: https://alextkachev.com/services
  • K72's agency portflio: https://k72.ca/agence

The Basics

To make page transitions using Framer Motion, we'll use the AnimatePresence Module, which delays the routing of one page to the next. It's extremely easy to use once you understand the concept.

pages/_app.js

1

export default function App({ Component, pageProps, router }) {

2

return (

3

<div className='main'>

4

<AnimatePresence mode='wait'>

5

<Component key={router.route} {...pageProps} />

6

</AnimatePresence>

7

</div>

8

)

9

}
  • Note here, most people forget to add a key to direct children of the AnimatePresence. Without it, the exit animation won't work.

Inner Perspective Transition

Not sure how I should title this transition, but it comes from an awesome portfolio by Alex Tkachev. It's kind of a persective animation, and it's the easiest one on the list.

Here I wrap everything inside the index.js component with a custom Inner component. Everything that's related to the page transition will be inside of this component. I can then re-use it for all the different pages.

pages/index.js

1

<Inner backgroundColor={"#B0AD98"}>

2

<h1>Home</h1>

3

<div className='body'>

4

<p>Lorem ipsum dolor sit amet, consectetur adipiscing elit. Praesent imperdiet nibh sit amet velit dignissim, non tempus nisl pellentesque. Praesent sagittis magna sit amet ex blandit, id pharetra lectus feugiat. Praesent sit amet congue ipsum, in ultrices neque. In dapibus in purus vitae dignissim. Quisque molestie ullamcorper elementum. Sed sodales erat augue. Lorem ipsum dolor sit amet, consectetur adipiscing elit. Duis aliquet quis lectus vitae venenatis. Aliquam erat volutpat. Nulla maximus sodales nibh dapibus congue. Integer nec pharetra felis, quis commodo elit. Fusce et aliquet neque. Vivamus leo diam, pharetra ut lorem eu, suscipit egestas ipsum. Aenean mauris ligula, laoreet ut volutpat sit amet, convallis et turpis.</p>

5

<p>Quisque molestie ullamcorper elementum. Sed sodales erat augue. Lorem ipsum dolor sit amet, consectetur adipiscing elit. Duis aliquet quis lectus vitae venenatis. Aliquam erat volutpat. Nulla maximus sodales nibh dapibus congue. Integer nec pharetra felis, quis commodo elit. Fusce et aliquet neque. Vivamus leo diam, pharetra ut lorem eu, suscipit egestas ipsum. Aenean mauris ligula, laoreet ut volutpat sit amet, convallis et turpis.</p>

6

</div>

7

</Inner>

Then inside the Inner Component, there are 3 different animations to create the final effect:

  • Slide Animation: A sliding div will come and cover the page before transitioning to the next one.
  • Perspective Animation: While the sliding dive comes in, the page will scale down and move up, creating a perspective effect.
  • Opacity Animation: An simple opacity animation for the enter state of the page.

Inner/in...

Inner/an...

Inner/st...

1

import React from 'react'

2

import { motion } from 'framer-motion';

3

import Link from 'next/link';

4

import { slide, opacity, perspective } from './anim';

5

6

const anim = (variants) => {

7

return {

8

initial: "initial",

9

animate: "enter",

10

exit: "exit",

11

variants

12

}

13

}

14

15

export default function Inner({children}) {

16

return (

17

<div className='inner'>

18

<motion.div className='slide' {...anim(slide)}/>

19

<motion.div className='page' {...anim(perspective)}>

20

<motion.div {...anim(opacity)}>

21

<div className='header'>

22

<Link href="/">Home</Link>

23

<Link href="/about">About</Link>

24

<Link href="/contact">Contact</Link>

25

</div>

26

{

27

children

28

}

29

</motion.div>

30

</motion.div>

31

</div>

32

)

33

}

We should have something like this:

Stairs Transition

The stair animation is quite a popular animation that I see pretty everywhere. For example on K72's Agency Portfolio. The stair transition has 2 animated components:

  • Animated columns: a bunch of columns will be animated up and down to create a stair effect.
  • Backround: a simple overlay that will turn black when the stairs are falling.

Stairs/i...

Stairs/a...

Stairs/s...

1

import React from 'react'

2

import { motion } from 'framer-motion';

3

import { opacity, expand } from './anim';

4

5

export default function Layout({children, backgroundColor}) {

6

7

const anim = (variants, custom=null) => {

8

return {

9

initial: "initial",

10

animate: "enter",

11

exit: "exit",

12

custom,

13

variants

14

}

15

}

16

17

const nbOfColumns = 5

18

return (

19

<div className='page stairs' style={{backgroundColor}}>

20

<motion.div {...anim(opacity)} className='transition-background'/>

21

<div className='transition-container'>

22

{

23

[...Array(nbOfColumns)].map( (_, i) => {

24

return (

25

<motion.div key={i} {...anim(expand, nbOfColumns - i)}/>

26

)

27

})

28

}

29

</div>

30

{

31

children

32

}

33

</div>

34

)

35

}

Here I return 5 columns and animate each one of them with a different delay based on the index.

We should have something like this:

Curve Transition

This one is a more complex than the other ones, but it's a very clean animation that you can see on Denis Snellenberg's Portfolio. It consists of multiple parts:

  • Temporary background: A temporary background while the dimension of the window is being set. It's there so there is no flash when the dimension state is initialized.
  • An SVG curve: A custom SVG curve that will be animated to create the curve effect. The whole SVG will be translated and the path inside of it will be curved.
  • A text: a custom text that reflects the current routing.

Curve/in...

Curve/an...

Curve/st...

1

'use client';

2

import React, { useEffect, useState } from 'react'

3

import { motion } from 'framer-motion'

4

import { useRouter } from 'next/router';

5

import { text, curve, translate } from './anim';

6

7

const routes = {

8

"/": "Home",

9

"/about": "About",

10

"/contact": "Contact"

11

}

12

13

const anim = (variants) => {

14

return {

15

variants,

16

initial: "initial",

17

animate: "enter",

18

exit: "exit"

19

}

20

}

21

22

export default function Curve({children, backgroundColor}) {

23

const router = useRouter();

24

const [dimensions, setDimensions] = useState({

25

width: null,

26

height: null

27

})

28

29

useEffect( () => {

30

function resize(){

31

setDimensions({

32

width: window.innerWidth,

33

height: window.innerHeight

34

})

35

}

36

resize();

37

window.addEventListener("resize", resize)

38

return () => {

39

window.removeEventListener("resize", resize);

40

}

41

}, [])

42

43

return (

44

<div className='page curve' style={{backgroundColor}}>

45

<div style={{opacity: dimensions.width == null ? 1 : 0}} className='background'/>

46

<motion.p className='route' {...anim(text)}>

47

{routes[router.route]}

48

</motion.p>

49

{dimensions.width != null && <SVG {...dimensions}/>}

50

{

51

children

52

}

53

</div>

54

)

55

}

56

57

const SVG = ({height, width}) => {

58

59

const initialPath = `

60

M0 300

61

Q${width/2} 0 ${width} 300

62

L${width} ${height + 300}

63

Q${width/2} ${height + 600} 0 ${height + 300}

64

L0 0

65

`

66

67

const targetPath = `

68

M0 300

69

Q${width/2} 0 ${width} 300

70

L${width} ${height}

71

Q${width/2} ${height} 0 ${height}

72

L0 0

73

`

74

75

return (

76

<motion.svg {...anim(translate)}>

77

<motion.path {...anim(curve(initialPath, targetPath))} />

78

</motion.svg>

79

)

80

}

We should have something like this:

Wrapping up

We're offically done with this guide!

You can find the source code Here

There are many debates between Framer Motion and GSAP inside a React app. In my opinion, easy page transitions is a big plus when using Framer Motion and it's one of the reason why I like it so much. Hopefully, we'll be able to do this inside the App Router as well. Hope you learned something :)

-Oli