Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Can't get shared layout animations to work with Next.js 13 #1850

Closed
maurocolella opened this issue Dec 26, 2022 · 88 comments
Closed

Can't get shared layout animations to work with Next.js 13 #1850

maurocolella opened this issue Dec 26, 2022 · 88 comments
Labels
bug Something isn't working wontfix This will not be worked on

Comments

@maurocolella
Copy link

1. Read the FAQs 👇

2. Describe the bug

With Next 13, using separation between server and client components, shared layout animations don't work when implementing the seemingly trivial navigation menu underline. Using either the deprecated or the modern approach (ie. layoutId on the target components).

3. IMPORTANT: Provide a CodeSandbox reproduction of the bug

This issue could be heavily dependent upon styles and nesting(s). The structure honors HTML and React semantics, but it runs complex animations. Hence: the repro is complete, interactive, commentable and with full source code.

Live:
https://portfolio-2023-git-feature-implement-layout-maurocolella.vercel.app/

Implementation: https://github.com/maurocolella/portfolio_2023/pull/2/files#diff-f7c999b982bfdbf5f3790e5b148e2343ffb7611b9ba579756dabab9fc76cb2e7

4. Steps to reproduce

On the example page:
https://portfolio-2023-git-feature-implement-layout-maurocolella.vercel.app/

Simply click navigation items. Transition doesn't take place.

5. Expected behavior

I would expect a smooth transition to occur as in the examples.

6. Video or screenshots

N/A.

7. Environment details

Ubuntu Linux 20.04, Chrome beta, Chrome stable, Firefox.

FAQs

Framer Motion won't install

Framer Motion 7+ uses React 18 as a minimum. If you can't upgrade React, install the latest version of Framer Motion 6.

height: "auto" is jumping

Animating to/from auto requires measuring the DOM. There's no perfect way to do this and if you have also applied padding to the same element, these measurements might be wrong.

The recommended solution is to move padding to a child element. See this issue for the full discussion.

Type error with AnimateSharedLayout

AnimateSharedLayout was deprecated in 5.0. Refer to the upgrade guide for instructions on how to remove.

Preact isn't working

Framer Motion isn't compatible with Preact.

AnimatePresence isn't working

Have all of its immediate children got a unique key prop that remains the same for that component every render?

// Bad: The index could be given to a different component if the order of items changes
<AnimatePresence>
  {items.map((item, index) => <Component key={index} />)}
</AnimatePresence>
// Good: The item ID is unique to each component
<AnimatePresence>
  {items.map((item, index) => <Component key={item.id} />)}
</AnimatePresence>

Is the AnimatePresence correctly outside of the controlling conditional? AnimatePresence must be rendered whenever you expect an exit animation to run - it can't do so if it's unmounted!

// Bad: AnimatePresence is unmounted - exit animations won't run
{isVisible && (
  <AnimatePresence>
    <Component />
  </AnimatePresence>
)}
// Good: Only the children are unmounted - exit animations will run
<AnimatePresence>
  {isVisible && <Component />}
</AnimatePresence>
@maurocolella maurocolella added the bug Something isn't working label Dec 26, 2022
@maurocolella maurocolella changed the title Can't get shared layout animations don't work with Next.js 13 Can't get shared layout animations to work with Next.js 13 Dec 26, 2022
@thanhtutzaw
Copy link

yo I also have this issue in Next js . I solved it for my first time use but , I deleted that animation . Now I want to enable in next js and issue happening .

@mattgperry
Copy link
Collaborator

Hi there, that source code link isn’t working for me. Please reopen with a sandbox or repo

@maurocolella
Copy link
Author

@mattgperry was private, sorry.

Here is the sandbox:

https://codesandbox.io/s/hopeful-wozniak-hkf8yx

@mattgperry
Copy link
Collaborator

That is weird although I think I have them working here https://codesandbox.io/p/sandbox/charming-pine-zz12kx

@mattgperry
Copy link
Collaborator

When I've got a bit more time I can look into this, there's definietly something different about Next 13

@maurocolella
Copy link
Author

Thanks a lot @mattgperry . It's early days for this project, and it's going to undergo some changes (maybe Remix, some css tweaks, and I will definitely lose the cheerio parsing once I connect it with a CMS back-end), but I thought I'd raise it as an edge case.

I am not blocked by all means, and if I figure out why it's working this way I'll be happy to share back.

Appreciate the help.

@MrUltimate
Copy link

MrUltimate commented Jan 10, 2023

Hey folks, I'm running into the same issue here. I'm not sure how to even wrap AnimatePresence in NextJs's new App directory because it's throwing all kinds of issues. I'm combining it with styled-components and seems like there's some stuff that's happening there too.

Here's how I've currently implemented my layout.js:

'use client'
import { AnimatePresence } from 'framer-motion'
import { GridOverlay } from '../components/GridOverlay'
import Header from '../components/Header'

import StyledComponentsRegistry from './styles/Registry'
import { ThemeContext } from './styles/ThemeContext'

export default function RootLayout({ children }) {
  return (
    <html lang="en">
      <body>
        <StyledComponentsRegistry>
          <ThemeContext>
            <AnimatePresence mode="popLayout">
              <GridOverlay />
              <Header />
              {children}
            </AnimatePresence>
          </ThemeContext>
        </StyledComponentsRegistry>
      </body>
    </html>
  )
}

One of the biggest errors I'm getting is:

Warning: Function components cannot be given refs. Attempts to access this ref will fail. Did you mean to use React.forwardRef()?

Check the render method of `PopChild`.
    at OuterLayoutRouter (webpack-internal:///./node_modules/next/dist/client/components/layout-router.js:18:11)
    at PopChildMeasure (webpack-internal:///./node_modules/framer-motion/dist/es/components/AnimatePresence/PopChild.mjs:13:1)
    at PopChild (webpack-internal:///./node_modules/framer-motion/dist/es/components/AnimatePresence/PopChild.mjs:33:21)
    at PresenceChild (webpack-internal:///./node_modules/framer-motion/dist/es/components/AnimatePresence/PresenceChild.mjs:15:26)
    at AnimatePresence (webpack-internal:///./node_modules/framer-motion/dist/es/components/AnimatePresence/index.mjs:72:28)
    at Fe (webpack-internal:///./node_modules/styled-components/dist/styled-components.browser.esm.js:30:17299)
    at ThemeContext (webpack-internal:///./app/styles/ThemeContext.js:16:11)
    at StyledComponentsRegistry (webpack-internal:///./app/styles/Registry.js:19:11)
    at body
    at html
    at RootLayout (webpack-internal:///./app/layout.jsx:18:11)
    at ReactDevOverlay (webpack-internal:///./node_modules/next/dist/client/components/react-dev-overlay/internal/ReactDevOverlay.js:53:9)
    at HotReload (webpack-internal:///./node_modules/next/dist/client/components/react-dev-overlay/hot-reloader-client.js:19:11)
    at Router (webpack-internal:///./node_modules/next/dist/client/components/app-router.js:96:11)
    at ErrorBoundaryHandler (webpack-internal:///./node_modules/next/dist/client/components/error-boundary.js:28:9)
    at ErrorBoundary (webpack-internal:///./node_modules/next/dist/client/components/error-boundary.js:40:11)
    at AppRouter
    at ServerRoot (webpack-internal:///./node_modules/next/dist/client/app-index.js:113:11)
    at RSCComponent
    at Root (webpack-internal:///./node_modules/next/dist/client/app-index.js:130:11)

@Murkrage
Copy link

Murkrage commented Jan 18, 2023

That is weird although I think I have them working here https://codesandbox.io/p/sandbox/charming-pine-zz12kx

Your example uses the pages directory. @maurocolella uses the new /app directory: https://beta.nextjs.org/docs. While still experimental it's definitely the new way of creating Next apps.

There are seemingly a couple of issues with transitions. Most notable: the exit animations do not work (see video). I'm not sure whether the issue is with Framer or with Next. Next does seem to unmount the layout immediately upon navigation. I've tried adding usePresence to allow Framer to remove an element after a timeout but even that doesn't work.

The props on the component doing the transition are:

initial={{ opacity: 0, x: 100 }}
animate={{ opacity: 1, x: 0 }}
exit={{ opacity: 0, x: 100 }}
transition={{ duration: 2 }}
key={pathname}
Screen.Recording.2023-01-18.at.11.05.16.mov

@Murkrage
Copy link

Per #1775 (reply in thread) I have also tried using a template.tsx file but that doesn't seem to work either, even though the Next docs specifically mention transitions to be a use-case for it: https://beta.nextjs.org/docs/routing/pages-and-layouts#templates.

@maurocolella
Copy link
Author

maurocolella commented Jan 18, 2023 via email

@dumbravaandrei22
Copy link

dumbravaandrei22 commented Feb 5, 2023

I have the same kind of issue in the Next 13.
The Animate Presence is unable to freeze the exiting (children) component.

In the Root Layout, which is a server component, I have this:
image

In Main (client component) I have this:
image

In Inner (client component as well):
image

I added Freeze(react-freeze) as a workaround, but I don't like it.

It seems that both (the new component + the existing one) point somehow to the same component.

@Murkrage
Copy link

Murkrage commented Feb 5, 2023

It seems that both (the new component + the existing one) point somehow to the same component.

This is, presumably, solved using template.js (see: https://beta.nextjs.org/docs/routing/pages-and-layouts#templates). From the docs:

...routes that share a template, a new instance of the component is mounted, DOM elements are recreated...

@jasonkylefrank
Copy link

After reading the Next.js v13 docs regarding the new Templates feature, I tried it out to house page-transition Framer Motion code.

Unfortunately the exit animation does not work (it's just completely ignored).

This is the file that contains my page-transition code.

Also see my template.tsx and layout.tsx file for more context.

@jasonkylefrank
Copy link

Another finding: When using <AnimatePresence> in template.tsx, the onExitComplete() does not fire.

@dumbravaandrei22
Copy link

dumbravaandrei22 commented Feb 6, 2023

@Murkrage Some time ago I tried to use template.js but it didn't work. i will try again.

@jasonkylefrank For me the exit animation works.
Try to wrap your motion.div into another component (client component).
For that component provide key=pathname as you do.
See my structure here: #1850 (comment)

in the inner component please use:
const [isPresent, safeToRemove] = usePresence();
My next js version: ^13.1.7-canary.4

Now, the problem is that the old component and the new one are pointing to the same instance (they are the same component). I also find that the old component sometimes it doesn't get unmounted.

Screen.Recording.2023-02-06.at.11.45.40.PM.mov

@Galanthus
Copy link

I have it working for like 90%. The animation on Exit is or too fast loading animate-in or the exit is is too fast or too slow. The exit and enter works... but still works better using "pages" instead of the new "app" directory.

layout.tsx:

"use client";

import "@styles/globals.css";

import { anton, roboto } from "lib/fonts";

import { Footer, MainNavigation, MaxWidthWrapper } from "@components/ui";
import { AnalyticsWrapper, DefaultHead } from "@components/shared";
import Newsletter from "@components/sections/Newsletter";
import { AnimatePresence, motion } from "framer-motion";
import { usePathname } from "next/navigation";

export default function RootLayout({
  children
}: {
  children: React.ReactNode;
}) {
  const pathname = usePathname();

  return (
    <html lang="en" className={`${anton.variable} ${roboto.variable}`}>
      <DefaultHead />
      <body className="min-h-screen text-gray-100 bg-midnight">
        <MainNavigation />

        <MaxWidthWrapper type="main">
          <AnimatePresence
            mode="wait"
            initial={false}
            onExitComplete={() => window.scrollTo(0, 0)}
          >
            <motion.div
              key={pathname}
              initial={{ opacity: 0, y: 25 }}
              animate={{ opacity: 1, y: 0 }}
              exit={{ opacity: 0, y: 25 }}
              transition={{
                type: "spring",
                stiffness: 140,
                damping: 20
              }}
            >
              {children}
            </motion.div>
          </AnimatePresence>
        </MaxWidthWrapper>

        <AnalyticsWrapper />
        <Newsletter />
        <Footer />
      </body>
    </html>
  );
}

@khuezy
Copy link

khuezy commented Feb 26, 2023

I got exit animation to work using appDir (13.2.2-canary1). template.tsx is a lie, so ignore that until they fix it.
What you want to do is in your layout.tsx (which is ideally rsc), wrap a Client component around your {children} and have that be wrapped in the <AnimatePresence> component.

// client.tsx
'use client'
import { motion, AnimatePresence } from 'framer-motion'
import { usePathname } from 'next/navigation'

// Client wraps any client/rsc components with AnimatePresence
export default function Client({children}: { children: ReactNode}) {
  const pathname = usePathname()
  return <AnimatePresence mode='wait' onExitComplete={doYourThing}>
     <motion.div key={pathname}
          initial={{}}
          animate={{}}
          exit={{}}
      >
        {children}
     </motion.div>
   </AnimatePresence>
}
// layout.tsx, this should ideally be `rsc`, so don't use `use client`
import Client from './client'
export default function Layout({children}: { children: ReactNode}) {
  return <Client>{children}</Client>
}

@beamercola
Copy link

@khuezy in my instance pathname falls into an infinite loop and no route change happens

@khuezy
Copy link

khuezy commented Feb 26, 2023

@beamercola hmm, what does your app structure look like? My solution is a hack until they fix template.tsx.

@dumbravaandrei22
Copy link

@khuezy What is your framer-motion version?
I still didn't manage to solve this issue.
I am using Next 13.2.2-canary1 and framer-motion: ^10.0.1.
I did exactly what you did.

@khuezy
Copy link

khuezy commented Feb 27, 2023

@dumbravaandrei22 I'm using 9.0.3, I'll update to latest and let you know. Although the example above gets exit animations to work, the current problem is that nextjs replaces the contents before the exit animation plays. I believe once Vercel fixes template.tsx, it will behave correctly.
Edit: 10.0.1 exit animations work, but the premature navigation is still a problem. Lee from Vercel is aware of it so maybe it's on their plate.

@dumbravaandrei22
Copy link

Understood!
Thank you!

@dumbravaandrei22
Copy link

dumbravaandrei22 commented Feb 27, 2023

@khuezy I tried this: #1850 (comment). Is working, but it seems that the loading UI has a delay.

@khuezy
Copy link

khuezy commented Feb 27, 2023

Ah I see, sorry missed your comment up there. We have the exact same problem.

@nmitic
Copy link

nmitic commented Aug 3, 2023

If someone could provide a link with example of native implementation with just CSS I would be grateful. Also is there a way to achieve this with just plain JS? Like some kind of hooks, which would allow me to use gsap or any other animation library?

@maurocolella I've seen you stating that finding those examples of effects in just CSS is easy, but to be honest, all I could find was Framer Motion staff :D Seems like a standard in React community (coming from Vue.js background).

In order to do native JS css anim we need routing guards which has been removed or planned to come in the app dir.

Also, even without framer you would still need to hack things around.

I honestly prefer to wait for issue to be fixed on next js.

@pzpsofficial
Copy link

If someone could provide a link with example of native implementation with just CSS I would be grateful. Also is there a way to achieve this with just plain JS? Like some kind of hooks, which would allow me to use gsap or any other animation library?
@maurocolella I've seen you stating that finding those examples of effects in just CSS is easy, but to be honest, all I could find was Framer Motion staff :D Seems like a standard in React community (coming from Vue.js background).

In order to do native JS css anim we need routing guards which has been removed or planned to come in the app dir.

Also, even without framer you would still need to hack things around.

I honestly prefer to wait for issue to be fixed on next js.

Thank's for reply. What I am thinking about (which is of course hacking things around) - is assigning an ID to children, that should be wrapped by Framer Motion / any other "delay component unmount" and using portal, wouldn't it do the trick?

@maurocolella
Copy link
Author

@pzpsofficial I will turn off notifications for this thread after this reply, as I receive my fair share of notifications and cannot keep up with everything, but the actual standards of internet and front-end dev should always be CSS, HTML and ECMAscript first. By that I mean: it's critical to have a good grasp on them, and frameworks are just sugar coating.

That said, people do rely on Framer Motion because it abstracts these away, and simplifies the task of translating a visual effect into CSS/JavaScript under the hood, but it's really (almost) all that it does.

Does it very nicely, but still.

Whether using Vue or Next or any other framework, any effect you see can be accomplished using web standards.

Here using pure CSS. It is not optimal, but it is pure CSS:
https://codepen.io/rm/pen/AXpmja

Here is another example that uses more elegant coding techniques along with a step-by-step breakdown:
https://vitaliy-kirenkov.medium.com/sliding-underline-in-navigation-b95399fc4601

Also, visual effects like this do not and should not depend on routing, but on user interaction (so you could trigger them independently on separate events as well, like mouseenter/mouseleave).

Effects are a styling concern, independently from routing, navigation, or content. Either should be able to work without the other.

Finally, I found this by searching for "sliding underline nav" on Google - Google is adaptive, returns results based on what you were searching before. Hence: keep it simple.

Back to basics works.

@pzpsofficial
Copy link

Thanks for reply, I think there was a misunderstanding as I had page transitions in mind the whole time.

In cases like this simple underline the basics are even more than enough ;)

Kind regards,
Bartek

@Gaurav4604
Copy link

Gaurav4604 commented Sep 18, 2023

Hi everyone,
I followed this thread and came across a hacky solution, It seems to work well for me

Check it out, might be useful.

using

  • nextJS (v 13.4.19)
  • framer-motion (v 10.16.4)

The thread itself has a working example, but I've implemented a solution with much less code as well.

My working example (it is a frontendmentor.io challenge solution)

Repo link

This is the FrozenRouter implementation that makes exit animations possible

Here is where the FrozenRouter component is being used

I hope this is helpful to all facing this issue.

@joshdavenport
Copy link

Feels like npostulart is right in their asessment that this feels hacky but it does seem to work pretty well. Has anybody tried this in the wild and found any weirdness or conversely that it just works with no real issue?

@Gaurav4604
Copy link

@joshdavenport I’ve added a personal example to my comment, where I’ve used it for a page transition itself, it does seem to work perfectly fine.

@joshdavenport
Copy link

joshdavenport commented Sep 18, 2023

Indeed @Gaurav4604, it's your example that triggered me wondering about it - seeing more people use the technique I mean. I'm wondering if anybody has managed to try this in prod in an app with many moving parts, complex routing, complex state, etc.

@Gaurav4604
Copy link

Gaurav4604 commented Sep 18, 2023

Ah ok, that makes sense, I'll let you all know via this thread if I do so myself as well. Does seem to get the job done for time-being.

@cdebotton
Copy link

Indeed @Gaurav4604, it's your example that triggered me wondering about it - seeing more people use the technique I mean. I'm wondering if anybody has managed to try this in prod in an app with many moving parts, complex routing, complex state, etc.

The biggest concern is more so that the technique uses code that isn’t publicly exported from nextjs, which would mean it’s not intended for anything but internal use. A minor or patch update could break this functionality without notice under semver.

@cdebotton
Copy link

cdebotton commented Sep 19, 2023

Just a follow up, I'm trying this approach in Next 13.5 which just came out today and I'm finding that any components underneath the Frozen Router Component won't update via HMR which makes it pretty much unusable.

@Gaurav4604
Copy link

Just a follow up, I'm trying this approach in Next 13.5 which just came out today and I'm finding that the any components underneath a Frozen Router won't update via HMR which makes it pretty much unusable.

@cdebotton I just updated my import for frozenRouter for it to work with AnimatePresence on nextJS 13.5.1,
You can find it here
but I get it, this needs a much more permanent fix, since these are not publicly exported components from nextJS.

@joshdavenport
Copy link

joshdavenport commented Sep 19, 2023

Just a follow up, I'm trying this approach in Next 13.5 which just came out today and I'm finding that the any components underneath a Frozen Router won't update via HMR which makes it pretty much unusable.

Ouch. Yes, this is a deal breaker. I guess not too bad if you're only working on something small but for anything else this drastically affects the development experience.

Seems like we're in a kind of limbo with this, framer obviously can't change what Next is doing, and there's not really been any movement on the Next side on this (vercel/next.js#49279) which is a shame but understandable - they have more demanding priorities for sure.

@baptistebriel
Copy link

Hi there! Whereas this solution worked fine for me to get page transitions to work with Next.js 13, I am getting the following error from Framer Motion's AnimatePresence & PopChildMeasure. Any fixes existing to avoid this error? The lifecycle of animations is working fine, just the error is being thrown.

Warning: Function components cannot be given refs. Attempts to access this ref will fail. Did you mean to use React.forwardRef()?

Check the render method of `PopChild`.

@Frumba
Copy link

Frumba commented Sep 20, 2023

@baptistebriel Check the new version of the Child with the forwardRef here https://github.com/npostulart/nextgram-with-page-transitions/blob/main/src/app/ClientLayout.tsx#L12 😄

@Gaurav4604
Copy link

Hi there! Whereas this solution worked fine for me to get page transitions to work with Next.js 13, I am getting the following error from Framer Motion's AnimatePresence & PopChildMeasure. Any fixes existing to avoid this error? The lifecycle of animations is working fine, just the error is being thrown.

Warning: Function components cannot be given refs. Attempts to access this ref will fail. Did you mean to use React.forwardRef()?

Check the render method of `PopChild`.

Take a look at the docs for the same
It mentions that if you're to use a custom component as a child of AnimatePresence with "popLayout", you need to pass a ref accordingly, so as to let framer motion track the component.

@baptistebriel
Copy link

Thank you @Frumba and @Gaurav4604 — that worked, thank you for the quick reply.

@su-pull
Copy link

su-pull commented Sep 22, 2023

Hello.
I haven't seen it for a while, but has the exit motion is working?

@wangwailok
Copy link

Hello. I haven't seen it for a while, but has the exit motion is working?

just tried with 13.5.3 and "framer-motion": "^10.16.4",
still not working

@devchitect
Copy link

devchitect commented Sep 28, 2023

Actually i got an idea and it quite a bit not a hacky solution,
Instead of using exit animation, because next router and ssr works different than react.
I use 2 enter animation, this requires router.push() instead of using href in component.

1 for exit, i use setTimeOut() to create a delay before it actually changes in url,
before url change, i change state which display an component for exit only.

and when the url changed, i use useEffect to change the state again to hide that exit component.
then the default enter animation come to work.

  • trigger exit:
const navigate = (currpath : string) => {
        const navigateDelay = 1000;
        dispatch(transition('exit'));
        setTimeout(()=>{
            router.push(currpath, { scroll: false });        
        }, navigateDelay)
 }
  • hide exit, trigger enter:
 useEffect(() => {
        dispatch(transition('enter'));
    },[dispatch,path])

    return (
       <>
            <div>
                {transitionValue === 'exit' && <ExitAnimation/>}

                <motion.div
                key={path}
                style={{background: "#000000"}}
                className='fixed top-full right-0 left-0 w-screen h-screen z-30 '
                variants={slidedown}
                initial='initial'
                animate='animate'
                transition={{delay: 0, duration: 1, ease: 'easeInOut'}}
                />
               
                {children}
                {/* <ScrollTrigger/> */}
            </div>
        </>
    )

@evankirkiles
Copy link

Here's my experimental branch of next.js that enables exit transitions with AnimatePresence by adding in a custom Glue component (supplied in a glue.tsx) where we can monitor the lifecycle of routes:

Draft PR: vercel/next.js#56591
Discussion: vercel/next.js#56594

The final solution in next.js is going to have to inevitably be something of this sort, as this enables route transitions even within parallel routes (where you can't rely on pathname or global navigation events to perform the transitions).

@MrUltimate
Copy link

Has anyone tried this with NextJS 14?

@rfros
Copy link

rfros commented Dec 28, 2023

Has anyone tried this with NextJS 14?

yes -- issue still present in 14.0.4; am hopeful draft PR from @evankirkiles gets some eyeballs on it... seems to have gone somewhat stale, though 😞 vercel/next.js#56591

@mattgperry
Copy link
Collaborator

Closing here as framework-specific integrations isn't something I'm going to concentrate on this year.

@mattgperry mattgperry added the wontfix This will not be worked on label Jan 4, 2024
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
bug Something isn't working wontfix This will not be worked on
Projects
None yet
Development

No branches or pull requests