NextJS - Animated Page Transitions
_app.tsx
import { CSSTransition, TransitionGroup } from 'react-transition-group';
function App<P>({ Component, pageProps }: AppProps<P>) {
const router = useRouter();
return (
<TransitionGroup className="app_transition_group">
<CSSTransition
key={router.pathname}
classNames={animationClass}
timeout={700}
onExiting={() => {
const mainElements = document.getElementsByTagName('main');
if (mainElements.length > 1) {
mainElements[1].style.marginTop = `${-scrollOffset.current}px`;
}
}}
onEntered={() => {
window.scrollTo(0, 0);
}}
>
<Component {...pageProps} />
</CSSTransition>
</TransitionGroup>
);
}
Major Issue - CSS modules are unloaded too early and an unstyled version of the page being navigated away from flashes unstyled. This thread discusses the issue and possible workarounds: https://github.com/vercel/next.js/issues/17464
Workaround
usePageTransitionFix.ts
import { useRouter } from 'next/router';
import { useEffect, useRef } from 'react';
export const usePageTransitionFix = () => {
const router = useRouter();
const currentPathRef = useRef<string>(router.asPath);
currentPathRef.current = router.asPath;
useEffect(() => {
// Disable server side style sheet removal labeled with data-n-p
const serverStyleSheets = document.querySelectorAll('link[data-n-p]');
serverStyleSheets.forEach((styleElement) =>
styleElement.removeAttribute('data-n-p'),
);
// Disable styles removal done by setting media to 'x'
const styleElements = document.querySelectorAll('style[media="x"]');
styleElements.forEach((styleElement) =>
styleElement.removeAttribute('media'),
);
}, [router.asPath]);
};
Another issue is scroll restoration - when navigating back, the page is scrolled to the top rather than back to where the user was last viewing:
https://github.com/vercel/next.js/issues/20951