To achieve this in a React stack, it is necessary to couple crucial parts in your application: the routing logic and the animation tooling.
As a simple example of a use case, we can try to animate a modal or dialog - you know, the type of element that benefits from animations that allow it enter and leave smoothly.
Once the transition hooks are called on each location change, we can run a method and use any animation library to build more complex scenes for our transitions.
You can use other animation libraries instead of GreenSock, but the important detail is that we build the timeline beforehand so that our main play method can decide which one should run for each route.
I've used this approach on lots of projects, and though it doesn't present obvious performance issues for inner navigations, I did notice a concurrency issue between the browser's initial DOM tree build and the first route animation.
We can simply call each of these animations after the promise has resolved using then along with our transitions.
No animation is better than crappy animations If you can't achieve a good animation, then removing it is a fair sacrifice.