ImEmo
ImEmo

Reputation: 131

Next.js Page Transition only works on refresh

I want to create transitions between my pages with Framer Motion in my Next.js 14 project, which is why I'm using the Page router.

I've created a layout to apply my transition effects to all my pages:

MainLayout.tsx

export default function MainLayout({
  children,
}: {
  children: React.ReactNode;
}) {
  const animation = (variants) => ({
    initial: 'initial',
    animate: 'animate',
    exit: 'exit',
    variants,
  });

  const pagesVariants = {
    initial: {
      opacity: 0,
    },
    animate: {
      opacity: 1,
      transition: {
        duration: 6, // I put a high value intentionally to see the animation
      },
    },
    exit: {
      opacity: 1,
    },
  };

  return (
    <>
      <Header />
      <AnimatePresence mode="wait">
        <motion.div
          id="main"
          className="flex grow flex-col"
          {...animation(pagesVariants)}
        >
          {children}
        </motion.div>
      </AnimatePresence>
      <Footer />
    </>
  );
}

_app.tsx

export default function App({ Component, pageProps, router }: AppProps) {
  return (
    <div
      id="app"
      className={`${amarante.variable} ${metal.variable} ${viaodaLibre.variable} ${philosopher.variable} ${styles.app} font-text`}
    >
      <MainLayout>
        <Component {...pageProps} key={router.route} />
      </MainLayout>
    </div>
  );
}

Unfortunately, the fade effect only works when I refresh the page. When I change the page via the Link component, the effect doesn't work. No matter where I place my AnimatePresence component (in my layout file or in my app file), it makes no difference. I've also added 'use client' to the top of each of my pages and layouts, but that doesn't change anything.

Upvotes: 0

Views: 558

Answers (1)

ImEmo
ImEmo

Reputation: 131

The problem was due to misuse of my MainLayout component. I thought this would be enough to make the animation happen to all my children components, but no.

For a start, the code in the _app.tsx file shouldn't change that much. I may have to wrap my header and footer, but here's the code:

export default function App({ Component, pageProps, router }: AppProps) {
  return (
    <div
      id="app"
      className={`${amarante.variable} ${metal.variable} ${viaodaLibre.variable} ${philosopher.variable} ${styles.app} font-text`}
    >
      <Header />
      <AnimatePresence mode="wait">
        <Component key={router.route} {...pageProps} />
      </AnimatePresence>
      <Footer />
    </div>
  );
}

I've renamed my MainLayout component to AnimationProvider to make it more meaningful. The 'animation' and 'pageVariants' variables remain unchanged. Here's what the new component returns:

  return (
    <motion.main id={pageName} {...animation(pageVariants)}>
      {children}
    </motion.main>
  );

Next, I need to wrap each page with my AnimationProvider component:

export default function Contact() {
  return (
    <AnimationProvider pageName="contact">
      <h1>Contact</h1>
    </AnimationProvider>
  );
}

Upvotes: 0

Related Questions