Animating page transitions in Next.js for Capacitor

Capacitor is a fantastic solution to running web apps as native. Unfortunately it can also be quite hard to give your front-end that extra polish to make it look truly native. In this article we'll go over implementing page transitions in Capacitor and Next.js

A green illustration with the title "Animating page transitions in Next.js for Capacitor"

None of the examples in this article actually require you to use Capacitor, so if you're just looking to jazz up your Next.js app this article is for you!

If you're reading this I'm going to assume you've managed to get your Next.js project to run on Capacitor (perhaps using one of our guides πŸ‘€) and now you're looking to give your app that truly native feel by adding some kind of page transition. This can be pretty complex as, unlike in truly native frameworks, this isn't automatically handled.

Luckily for us it doesn't actually require too much work and is pretty easy to implement (given you know how Next.js works) even in mature projects. I'm going to split this article into the two fundamentals of native transitions:

  • Animating page transitions between Next.js routes
  • Animating shallow navigations

How to animate page transitions in Next.js

If you've worked with Next.js before you're probably aware that _app.js is the central component your app is built from. By creating a custom _app.js in your pages folder you can essentially customize your app's wrapper.

Your custom _app.js will look something like this:

function MyApp({ Component, pageProps }) {
  return <Component {...pageProps} />;
}

export default MyApp;

Here <Component /> mounts and unmounts your pages. See where I'm going with this?

Since <Component /> is just mounting and unmounting pages we can easily wrap it with another component that handles transitions, something like react-spring's <Transition />. Here's a nice basic example to get you started:

I haven't been able to reproduce this using react-spring's useTransition hook and I suspect it could be a hook limitation. But either way this works perfectly well and will automatically apply to all your page transitions without any further setup.

Feel free to customise the transition values in _app.js and see what transitions you can come up with. It's really easy!

Animating shallow transitions in Next.js

Here's my main issue with Next.js: the default router sucks. Okay maybe it doesn't suck, but it is extremely limited.

Let me explain myself. Every time you use Next's <Link> component to navigate to another page, the entire page is unmounted and the new one is loaded in. This is especially annoying if you're on a list of items and you're trying to load one of them without leaving the page as that becomes impossible.

The way I see it there are two ways around this:

  • Use query strings to execute a shallow navigation and load new content
  • Use a new router on top of Next.js' native one

Animating Next.js' shallow routing (Recommended)

The basics of this are simple:

  • We add our routes as absolutely positioned unmounted elements
  • We use the router to change our query params
  • We listen to those changes and trigger our pages in and out of view

This took me a bit to figure out so here's my solution in action:

I ended up creating a reusable component which is divided into three parts:

  • An optional <Rootpage /> component to wrap the main page and animate it whenever one of the secondary Routes is active.
  • A <Switch /> component which listens for changes to a specific router.query parameter.
  • A <Route /> component, which renders its children if its path matches the previously mentioned query.

Thanks to all the above, whenever one of our query parameters changes, our initial page is animated (but not unmounted) and our new page is displayed on top of it.

Now, don't get me wrong, it's far from being the perfect solution and could use more work. However, it's solid and hasn't failed me yet.

You could easily combine this with lazy loading your components for maximum performance. An even better improvement would be to use React Portals to render your pages. (But I'll leave these improvements to you or to whenever I get time to improve it!).

Using a third-party router to extend Next.js' functionality

The second (and less recommended) solution to animating transitions would be to use a lightweight routing solution like wouter to extend Next.js.

You could theoretically use a different router to navigate from http://localhost/ to http://localhost/page without causing Next to load a new page, then use that third party router to mount your extra pages.

This, however, does present a challenge: if someone were to reload the page after navigating, Next.js would try to use its native routing to load http://localhost/page, which might not exist.

Unfortunately this really clashes with Capacitor's need to have static pages to navigate to. You can work around it if you're running a server to handle your page-rendering on the fly, but it's pretty much a no-go for statically exported apps so I would advise against it.

Let me know if this article has been helpful or if you have any improvements to offer on the examples I've posted here. I'm sure there are solutions I haven't thought of so I'd love to hear your own workarounds!

Also don't forget to check out our guide on how to run Capacitor on Next.js!