Add page transitions to Gatsby

Although by default Gatsby manages navigation without reloading the entire page, I wanted the experience to be more seamless instead of a harsh change of content, so I set about looking to add animated transitions between pages.

Initially I tried using the page transitions plugins, and although it worked locally, it would split out an error during the build about an unsupported browser API, which would cause my CI/CD builds to fail with the following error

error
Your plugins must export known APIs from their gatsby-browser.js.
The following exports aren't APIs. Perhaps you made a typo or your plugin is outdated?
See https://www.gatsbyjs.org/docs/browser-apis/ for the list of Gatsby Browser APIs
- The plugin "gatsby-plugin-page-transitions@1.0.7" is exporting a variable named "replaceHistory" which isn't an API.

The problem

The problem wasn’t with the plugin itself, but with some breaking changes in Gatsby V2. In V1, a layout wrapped the content for a page, which made it possible to hook any transitions to that. In V2, the layout is now part of the page content, which means when you change the route, it unmounts the layout within the page and mounts a new one, preventing you from having transitions.

After some digging around, I found a solution based on this issue on GitHub.

The solution

For this to work, I needed to re-implement the original layout implementation and then add the CSS transitions.

Adding the layout

Add the layout plugin to your site by running the following command:

yarn add gatsby-plugin-layout

And then add 'gatsby-plugin-layout' to the list of plugins in gatsby-config.js.

By default, this will look in src/layouts/index.js, but you can always customize this by passing in extra options to the plugin registration. So let’s create the layout next:

import React from 'react';

import Transition from '../components/Transition';

const Layout = ({ children, location }) => (
  <Transition location={location}>
    {children}
  </Transition>
);

export default Layout;

We’ll get to the Transition component in the next section. It needs to wrap everything in the layout so that those have the transitions applied to them.

For the time being, I have kept by layout very simple, as I’m still using the V2 method of including the real layout from within the page components. The purpose of this is purely to wrap everything in transitions.

Creating the transitions

Next up, we need to create the transition, which relies on React Transition Group, so install that:

yarn add react-transition-group

You can then create the src/components/Transition.js component:

import React from 'react';
import { TransitionGroup, Transition as ReactTransition } from 'react-transition-group';

// The duration for each phase of the transition
// So the total duration will be _twice_ this
const timeout = 200;

const styles = {
  entering: {
    position: 'absolute',
    opacity: 0,
  },
  entered: {
    transition: `opacity ${timeout}ms ease-in-out`,
    opacity: 1,
  },
  exiting: {
    transition: `opacity ${timeout}ms ease-in-out`,
    opacity: 0
  },
};

const Transition = ({ children, location }) => (
  <TransitionGroup>
    <ReactTransition key={location.pathname} timeout={timeout}>
      {(status) => (
        <div style={styles[status]}>
          {children}
        </div>
      )}
    </ReactTransition>
  </TransitionGroup>
);

export default Transition;

This wraps all the content in a transition and will apply a simple fade in/out animation, but you can change the CSS transitions to whatever you want.

At this point, you should have a nice transition as you navigate your Gatsby site.

Making the transition smoother

One problem with the current solution, is when you change pages, it jumps to the top and then does the transition, which can still be a little jarring. One way around this is to use scroll-behavior. Although browser support isn’t the best for this, it works in Chrome, Firefox and Opera. Given that this is progressive enhancement rather than core behaviour, and most of my users use Chrome/Firefox, this isn’t an issue for me.

To make this work, you need to add scroll-behavior: smooth to the HTML element. You can either do this by copying the html.js into your src and editing it, or use the HTML attributes plugin to add an inline style, which is the route I took.

Now, when navigating, it will simultaneously do the fade transition and smooth scroll the user to the top of the page.

© 2020 - Built with Gatsby