Pentagram Page Transitions – A deep dive with Gatsby.js
Tags: javascript, webdev, tutorial, react
Published: 2019-07-21 23:13:30

---
title: Pentagram Page Transitions – A deep dive with Gatsby.js
description: Learn how to implement beautiful page transitions and fetch remote data with Gatsby
---

_tl;dr In this blog post we built a shallow clone of Pentagram's beautiful portfolio site with Gatsby.js.  Much page transition. Very headless CMS. So React._

https://gatsbygram-clone.netlify.com/
https://github.com/mattrothenberg/gatsbygram

---

Some say "imitation is the sincerest form of flattery."

Throughout my career as a designer/developer, I've tried to employ imitation as an educational tool, dissecting, analyzing, and recreating the images, websites, and applications that have had an impact on me.

Rest assured, this isn't some abstract rationalization of ripping off people's work. On the contrary, the intent behind my imitation has never been to pass off others' work as my own, but rather to use this work as a source of inspiration and education.

I bring this up because today we'll be "imitating" a few details of one of the slicker websites I've seen in the past few years: [Pentagram.com](https://www.pentagram.com/). If you're not familiar with Pentagram, they're a design firm that does killer work for major clients.

![](https://i.imgur.com/FS5L02p.gif)

Specifically, we'll be using [Gatsby](https://gatsbyjs.org) (the static-site generator we all know and love), to recreate those beautiful page transitions as you navigate from project to project, thereby learning a handful of valuable Gatsby skills:

- Scaffolding and configuring a project from scratch
- Styling a site with some of the latest hotness, i.e. `styled-components`
- Populating a site with data from a remote source (e.g., a headless CMS)
- Using Gatsby's built-in image component and plugin system to render optimized images

**Fair warning**: I will be sharing _a lot_ of code. Don't feel like you have to copy it line for line. I've tried my best to chunk out the various "features" we'll be building as branches on the `gatsbygram` [repo](https://github.com/mattrothenberg/gatsbygram), and will be including links to the relevant commits/PRs and the bottom of each section.

## Scaffolding our Gatsby project

Let's start off by installing the Gatsby CLI (if you haven't already), making a new project directory, and installing a few dependencies.

```bash
yarn global add gatsby-cli
mkdir gatsbygram
cd gatsbygram
yarn init
yarn add react-dom react gatsby
```

From there, we can add a few scripts to our `package.json` in order to run the development server locally and the build the project.

```json
"scripts": {
  "develop": "gatsby develop",
  "serve": "gatsby serve",
  "build": "gatsby build"
}
```

## Adding Pages

As of now, our site isn't very functional. That's because we haven't yet told Gatsby what pages to render.

In Gatsby land, the `src/pages` directory is special. JS modules in this directory represent discrete "routes" on our site (e.g., `src/pages/index` -> the "home" page, `src/pages/about` -> the "about" page).

For example, if we add a simple React component to `src/pages/index.js`, we'll see it rendered when we spin up our site by running `yarn develop` and visiting http://localhost:8000.

```jsx
// src/pages/index.js
import React from 'react'

const Home = () => <div>Hello world</div>

export default Home
```

For our website, however, we won't have the luxury of being able to define our pages upfront. The projects in our portfolio all have different titles, different slugs, and as such we'll need to use a mechanism for _dynamically_ generating these pages. Luckily for us, Gatsby offers a great solution for this problem. Enter `gatsby-node.js`.

### gatsby-node.js

> Gatsby gives plugins and site builders many APIs for controlling your site.

In `gatsby-node.js`, we can interact directly with such APIs. For our intents and purposes, we're going to be working with the `createPages` API. This API, as you might have already guessed, allows us to create pages dynamically (e.g., given data that we've fetched from a remote source).

In a future step, we'll return to this file and actually pull data from one such remote source (DatoCMS), but for now let's think about the schema of a "project" and hard-code the data structure that will power our portfolio site. Let's also keep it simple, in the interest of getting the site set up as quickly as possible.

```js
// gatsby-node.js
const projects = [
  {
    title: 'Project #1',
    slug: 'project-1',
  },
  {
    title: 'Project #2',
    slug: 'project-2',
  },
  // etcetera
]
```

With our stub projects dataset complete, let's dive into how the `createPages` function actually works.

File: [`gatsby-node.js`](https://github.com/mattrothenberg/gatsbygram/blob/d1b5a959945a3a22a187eb4937b56b0f4c45a8e2/gatsby-node.js)

You'll notice that we've destructured the arguments passed to `createPages`, picking out an `actions` object that itself contains a `createPage` (singular) function. It's this function that will perform the magic trick of translating our stub project dataset into actual pages.

Effectively, `createPage` expects a few values in order to perform such a translation.

1. The `path` of the page you're trying to build (e.g., `/projects/project-1`).
2. The `component` that we want to render when users visit this path (think of this as a "template" into which we'll slot project data).
3. The `context`, or props that will be passed into this component.

For instance, it might look like this...

```js
// The path to our template component
const projectTemplate = path.resolve(`./src/templates/project.js`)

projects.forEach(project => {
  // Rudimentary way to get the "next" project so we can show a preview at the bottom of each project
  const next = projects[index === projects.length - 1 ? 0 : index + 1]

  createPage({
    path: `/projects/${project.slug}`,
    component: projectTemplate,
    context: {
      ...project,
      next,
    },
  })
})
```

...where our `component`, or template, is yet another simple React component.

```jsx
import React from 'react'

const Project = ({ pageContext: project }) => <div>{project.title}</div>

export default Project
```

With our `createPages` script ready to go, we can restart the development server (via `yarn develop`) and navigate to http://localhost:8000/projects/project-1. Ugly, but it certainly gets the job done.

We now have dynamically generated pages! Time to bring the project page to life.

{% github https://github.com/mattrothenberg/gatsbygram/pull/1 %}

## The Visual & Interaction Design

Onto the fun part! In this section, we'll install and configure our toolset for styling our website.

Personally, I'm a huge fan of [Rebass](https://rebassjs.org/) and by extension, [styled-components](https://www.styled-components.com). Let's get these dependencies installed.

```bash
yarn add rebass gatsby-plugin-styled-components styled-components babel-plugin-styled-components
```

You'll note that one of the dependencies is `gatsby-plugin-styled-components`. Another great feature of Gatsby is its first-class plugin API that developers can hook into in order to extend the library's core functionality. Here, and I'm waving my hand a little, we're adding some code that adds configuration and support for `styled-components`. But installing the dependency is just one step in the process. We need to let Gatsby know to use it.

Enter `gatsby-config.js`.

### gatsby-config.js

This is another "magical" configuration file (à la `gatsby-node.js`, as we saw in a previous step), but essentially it's a manifest of all the plugins that our Gatsby site is using. All we need to do here is specify our plugin and move on.

```js
module.exports = {
  plugins: [`gatsby-plugin-styled-components`],
}
```

### Layout

_Most_ websites these days employ a familiar organizational structure, sandwiching arbitrary "main content" between a header and a footer.

```jsx
// Layout.jsx
const Layout = () => (
  <>
    <Header />
    {
      // main content
    }
    <Footer />
  </>
)
```

We're going to follow a similar pattern, but for two specific reasons –

1. Our visual/interaction design requires us to have a common `Header` between project pages
2. We need a component that initializes `styled-components` with our site's theme, and passes this theme information downward to its children.

So, let's modify our hand-wavy `Layout.jsx` example from above thusly –

```jsx
import React from 'react'
import { ThemeProvider } from 'styled-components'
import { Box } from 'rebass'

// A very simple CSS reset
import '../style/reset.css'

const theme = {
  fonts: {
    sans: 'system-ui, sans-serif',
  },
  colors: {
    grey: '#999',
    black: '#1a1a1a',
    red: '#e61428',
  },
}

const Layout = ({ children }) => (
  <ThemeProvider theme={theme}>
    <React.Fragment>
      <Box as="header">silly header</Box>
      <Box as="main">{children}</Box>
    </React.Fragment>
  </ThemeProvider>
)

export default Layout
```

As for our `theme`, I took a gander at Pentagram's website and pulled out a few design details –

- They use the beautiful [Neue Haas Grotesk](http://www.fontbureau.com/nhg/) typeface, but we'll use a system font instead.
- The only real "colors" on their site are `black`, `grey`, and `red`. The photos themselves convey most of the visual complexity.

### ProjectHeader component

Each project page on Pentagram's website seems to be structured as follows –

```jsx
const Project = () => (
  <>
    {/* Title + description + category + hero image */}
    <ProjectHeader />

    {/* Photo grid */}
    <ProjectPhotos />

    {/* Title + description + category + truncated preview of hero image of NEXT project */}
    <ProjectHeader truncated />
  </>
)
```

Note that `ProjectHeader` appears _twice_. Why does it appear twice, you may be asking? Well, to facilitate that lovely page transition you get when clicking on the preview of the next project (at the bottom of any project page). We will get into the specifics of this transition in a bit, but for now, let's note that our `ProjectHeader` will need to live in two states –

1. A default state where the project's `title`, `description`, `category`, and `hero` image will be visible.
2. A `truncated` state where we hide a good portion of the `hero` image as a teaser of the next project in the list.

I'm thinking our `ProjectHeader` component will look something like this. We'll leverage the `Flex` and `Box` helper components from `rebass`, and use `styled-components` to give some visual styling (e.g., font-weight, font-size, and color) to the respective typographic elements on the page.

```jsx
const ProjectHeader = ({ project, truncated }) => (
  <Box>
    <Flex>
      <Box>
        <Title as="h1">title goes here</Title>
        <Box>
          <Category as="h3">category goes here</Category>
        </Box>
      </Box>
      <Box>
        <Box>
          <Description as="h2">description goes here...</Description>
        </Box>
      </Box>
    </Flex>
    <Hero truncated={truncated} />
  </Box>
)
```

Notice, though, that we're passing our `truncated` prop all the way down to the `Hero` component, which, for now, renders a grey box in an 8:5 aspect ratio. Passing this prop down allows us to render our `ProjectHeader` in the two aforementioned states, default and "truncated."

```jsx
const HeroWrap = styled(Box)`
  ${props =>
    props.truncated &&
    css`
      max-height: 200px;
      overflow: hidden;
    `}
`

const Hero = ({ truncated }) => (
  <HeroWrap mt={[4, 5]} truncated={truncated}>
    <AspectRatioBox ratio={8 / 5} />
  </HeroWrap>
)
```

Rest assured, we'll come back and tweak this component later in the tutorial. For now, though, we have what we need to get started.

Relevant Commit: [`7f0ff3f`](https://github.com/mattrothenberg/gatsbygram/commit/7f0ff3fc901479c3c0491d8a01c95e949492623b)

### ProjectContent

Sandwiched between the two `ProjectHeader` components is, well, the project content!

Given we don't have any "real" project data to work with at the moment, we're going to fake out this section entirely. We'll wire up a beautiful grid of placeholder boxes thusly.

```jsx
import React from 'react'
import { Box } from 'rebass'
import styled from 'styled-components'

import AspectRatioBox from './aspect-ratio-box'

const Grid = styled(Box)`
  display: grid;
  grid-template-columns: repeat(2, 1fr);
  grid-gap: ${props => props.theme.space[4]}px;
`

const ProjectContent = () => (
  <Box my={4}>
    <Grid>
      <AspectRatioBox ratio={8 / 5} />
      <AspectRatioBox ratio={8 / 5} />
      <AspectRatioBox ratio={8 / 5} />
      <AspectRatioBox ratio={8 / 5} />
    </Grid>
  </Box>
)

export default ProjectContent
```

<div style="width: 100%; height: 0px; position: relative; padding-bottom: 55.441%;"><iframe src="https://streamable.com/s/et70p/jkhtl" frameborder="0" width="100%" height="100%" allowfullscreen style="width: 100%; height: 100%; position: absolute;"></iframe></div>


Not bad!

Let's head back to our `Project` template component, add these elements, and commit this.

```jsx
const Project = ({ pageContext: project }) => (
  <Layout>
    <ProjectHeader project={project} />
    <ProjectContent />
    <ProjectHeader project={project.next} truncated />
  </Layout>
)
```

---

{% github https://github.com/mattrothenberg/gatsbygram/pull/2 %}

## The Transition

While we still have _plenty_ to do, let's get started on the fun part: implementing the page transitions we see on Pentagram's website.

![](https://i.imgur.com/FS5L02p.gif)

Before diving into the code side of things, let's try to map out what exactly is happening during this transition.

1. The `ProjectContent` fades out relatively quicly (in a few hundred `ms`).
2. After the content has faded out, the truncated `ProjectHeader` for the next project slides up to the "top" of the page, effectively transitioning into the `ProjectHeader` for the page that's about to load.

Easy, right? The devil is surely in the details 😈.

But lucky for us, a lot of the hard work has already been done for us. Let's use a fantastic library called [`gatsby-plugin-transition-link`](https://transitionlink.tylerbarnes.ca/docs/), which:

> [provides] a simple way of describing a page transition via props on a Link component. For both entering and exiting pages you can specify a number of timing values, pass state to both pages, and trigger a function for each.

```bash
yarn add gatsby-plugin-transition-link
```

And as we've seen before, let's add this plugin to our `gatsby-config.js`

```js
module.exports = {
  plugins: [`gatsby-plugin-styled-components`, `gatsby-plugin-transition-link`],
}
```

Now, in order to get started with this library, we'll need to make some modifications to our `Project` template component.

Indeed, the way this transition plugin works is that it exposes a `TransitionLink` component that we can use in lieu of Gatsby's built-in `Link` component (which has some magic abilities but effectively provides a mechanism for routing between pages).

```jsx
import TransitionLink from 'gatsby-plugin-transition-link'

const Project = ({ pageContext: project }) => {
  const nextProjectUrl = `/projects/${project.next.slug}`

  return (
    <Layout>
      <ProjectHeader project={project} />
      <ProjectContent />
      <TransitionLink to={nextProjectUrl}>
        <ProjectHeader project={project.next} truncated />
      </TransitionLink>
    </Layout>
  )
}
```

Notice that by wrapping our `ProjectHeader` component in a `TransitionLink`, we've effectively made it a hyperlink to the next project in our portfolio. And it works! But it certainly doesn't trigger the beautiful page transition that we saw in the GIF above. For that, we'll need some other tools.

The first thing we need is another component from `gatsby-plugin-transition-link`: TransitionState.

In a nutshell, what this component does is expose a `transitionStatus` prop that describes what _state_ the transition is currently in, be it: `exiting`, `exited`, `entering`, or `entered`. This is useful because it gives us the information we need to _declare_ how our transition should work. While this plugin also exposes hooks for performing our transition in a more _imperative_ manner (e.g., with a library like `gsap`), I'm partial to this declarative approach for reasons that will become clear in the next couple of steps.

Let's refactor our `Project` template component in the following ways in order to start consuming this data.

1. Move the presentational elements of our template to a functional component, `ProjectInner`
2. Introduce `<TransitionState>`, which takes a "function as a child" and passes to it a `transitionStatus` prop describing where the transition is in its lifecycle.

```jsx
const ProjectInner = ({ transitionStatus, project }) => {
  const nextProjectUrl = `/projects/${project.next.slug}`
  return (
    <Layout>
      <ProjectHeader project={project} />
      <ProjectContent />
      <TransitionLink to={nextProjectUrl}>
        <ProjectHeader project={project.next} truncated />
      </TransitionLink>
    </Layout>
  )
}

const Project = ({ pageContext: project }) => {
  return (
    <TransitionState>
      {({ transitionStatus }) => (
        <ProjectInner transitionStatus={transitionStatus} project={project} />
      )}
    </TransitionState>
  )
}
```

And just like that, our `ProjectInner` can now use the `transitionStatus` prop to _declare_ the transition steps we outlined at the beginning of this section (e.g., fading the content, sliding up the header).

As aforementioned, I'm a big fan of the _declarative_ mindset that React pushes you towards. Coming from the days of `jQuery`, where we imperatively told our program to `addClass` here, or `fadeOut` there, React's approach of declaring what we want done and letting the library handle the rest is a breath of fresh air.

That being said, the declarative style can also be totally mind bending, particularly when it comes to animation. If you're anything like me, you might have learned animation with a tool like `TweenMax` from the Greensock library. By and large, `TweenMax` follows a very _imperative_ (and powerful, to be sure) approach. For instance, we might be able to implement our transition with code like this:

```js
// Fade out the main content
TweenMax.to(mainContentEl, 1, { opacity: 0 })

// Slide up the header
TweenMax.to(nextHeaderEl, 1, { y: nextYPos, delay: 250 })

// Profit 😎
```

Today, we're going to eschew this approach in favor of the declarative approach. And to that end, we're going to use one of my favorite React libraries, [`Pose`](https://popmotion.io/pose/).

`Pose` requires us to "declare" our transition with the following API.

```jsx
const FadingBox = posed.div({
  visible: { opacity: 1 },
  hidden: { opacity: 0 },
})
```

We can then use `FadingBox` just as we would any other React component. The difference is, though, that `FadingBox` exposes a `pose` prop that we can pass a string value to. If this string value matches one of the keys defined on the Pose instance (in this case, `visible` or `hidden`), the component will automatically trigger a transition to that particular state.

```
<!-- Now you see me 👀 -->
<FadingBox pose="visible" />

<!-- Now you don't 🙈 -->
<FadingBox pose="hidden" />
```

So why the heck am I giving you all of this context? Well, you might recall that we now have a special `transitionStatus` prop inside of our `ProjectInner` component that effectively _declares_ what state our transition is in. Let's use this prop to implement the first step in our transition, fading out the main content.

The first thing we'll do is build our `Pose` instance.

```jsx
// Transition to {opacity: 0} when pose === 'exiting'
const FadingContent = posed.div({
  exiting: { opacity: 0 },
})
```

And then we'll wrap the current project's header and content in the instance.

```jsx
<FadingContent pose={transitionStatus}>
  <ProjectHeader project={project} />
  <ProjectContent />
</FadingContent>
```

But you'll note that nothing actually happens yet. This is because we need to tell our `TransitionLink` component how long our respective `entry` and `exit` transitions should take, as well as describe what we want to happen when these transitions begin and conclude.

```jsx
// For now, let's use this as a magic number that describes how long our transition should take
const TRANSITION_LENGTH = 1.5

const exitTransition = {
  length: TRANSITION_LENGTH, // Take 1.5 seconds to leave
  trigger: () => console.log('We are exiting'),
}

const entryTransition = {
  delay: TRANSITION_LENGTH, // Wait 1.5 seconds before entering
  trigger: () => console.log('We are entering'),
}

// Let's pass these hooks as props to our TransitionLink component
<TransitionLink
  to={nextProjectUrl}
  exit={exitTransition}
  entry={entryTransition}
/>
```

Save and refresh your browser. Congratulations, you just implemented the first (albeit janky) transition!

![](https://media.giphy.com/media/VfEWTnSrIliuN43Q7h/giphy.gif)

Let's move on to the next transition, which is admittedly a _little_ bit trickier. First things first, we need to remove the `text-decoration` style that `TransitionLink` has added to our `ProjectHeader` at the bottom of the page, insofar as this header should look _exactly_ like the one above, apart from the truncated hero image.

```jsx
<TransitionLink
  style={{
    textDecoration: 'none',
    color: 'inherit',
  }}
/>
```

Next, let's define our `pose` for the sliding `<ProjectHeader />`.

```jsx
const SlidingHeader = posed.div({
  exiting: {
    y: ({ element }) => {
      // This is an alternative API that allows us to dynamically generate a "y" value.

      // When scrolling back to the top, how far should we actually go? Let's factor the height of our site's header into the equation.
      const navbar = document.querySelector('header')
      const navbarDimensions = navbar.getBoundingClientRect()
      const distanceToTop =
        element.getBoundingClientRect().top - navbarDimensions.height

      // And return that aggregate distance as the dynamic "y" value.
      return distanceToTop * -1
    },
    transition: {
      ease: [0.59, 0.01, 0.28, 1], // Make the transition smoother
      delay: 250, // Let's wait a tick before starting
      duration: TRANSITION_LENGTH * 1000 - 250, // And let's be sure not to exceed the 1.5s we have allotted for the entire animation.
    },
  },
})
```

There's a lot going on in that code block. The main takeaway here is that you can dynamically generate your `Pose` transition states. You need not hard code those values, especially if you need to perform some sort of calculation (like our `distanceToTop`) before triggering the transition.

We need to cast a few other incantations as well, though.

First, let's wrap our truncated `ProjectHeader` in our newly created Pose.

```jsx
<SlidingHeader pose={transitionStatus}>
  <ProjectHeader project={project.next} truncated={shouldTruncate} />
</SlidingHeader>
```

You'll note that instead of hard-coding a truthy value for `truncated`, we're now passing a variable called `shouldTruncate`. We're doing this because now, we only want to truncate the hero image of the next project _once_ we've transitioned into the next page. For the duration of the transition, we want the truncated content to show, as it gives the transition a natural feel.

```js
const shouldTruncate = ['entering', 'entered'].includes(transitionStatus)
```

Finally, we need to add a _few_ small imperative details (hypocrite, I know) to our `TransitionLink` component in order to smooth over the actual transition.

```jsx
const exitTransition = {
  length: TRANSITION_LENGTH,
  trigger: () => {
    if (document) {
      // Preventing overflow here make the animation smoother IMO
      document.body.style.overflow = 'hidden'
    }
  },
}

const entryTransition = {
  delay: TRANSITION_LENGTH,
  trigger: () => {
    if (document && window) {
      // Ensuring we're at the top of the page when the page loads
      // prevents any additional JANK when the transition ends.
      window.scrollTo(0, 0)
      document.body.style.overflow = 'visible'
    }
  },
}
```

And there we have it.

![](https://media.giphy.com/media/gjCz97t28j8FYJdRtl/giphy.gif)

{% github https://github.com/mattrothenberg/gatsbygram/pull/3 %}

## Adding Polish

In our haste to add these beautiful page transitions, we've neglected a few design details.

- Our `<header>` is too small, doesn't fade in when the page loads, and still has the text "silly header" 🙈
- We omitted the "Next Project" heading above the truncated footer that fades when the transition begins.

### Logo & Header Transition

Let's add a cheap, horrible copy of Pentagram's logo (sorry, Pentagram) to our project in `/src/components/logo.svg`. We can then import it and add it to our `Layout` component. While we're there, though, let's go ahead and rig up another Pose instance so that we can fade the header in & out as the page transitions.

```jsx
import { Box, Image } from 'rebass'

const FadingHeader = posed.header({
  exiting: { opacity: 0 },
  exited: { opacity: 0 },
  entering: { opacity: 1 },
  entered: { opacity: 1 },
})

const Layout = ({ children, transitionStatus }) => (
  <ThemeProvider theme={theme}>
    <React.Fragment>
      <FadingHeader pose={transitionStatus}>
        <Box px={[3, 5]} py={4}>
          <Image src={Logo} alt="Gatsbygram Logo" height={32} />
        </Box>
      </FadingHeader>
      <Box as="main" px={[3, 5]}>
        {children}
      </Box>
    </React.Fragment>
  </ThemeProvider>
)
```

Wait a second...How did `transitionStatus` make its way as a prop to this component? Remember how we use the `Layout` component inside of our `Project` component template? From there, we can simply pass this value down as a prop and let `Layout` do whatever it wants with it.

```jsx
<Layout transitionStatus={transitionStatus}>project content goes here</Layout>
```


### 'Next Project' heading and transition

Now that we're seasoned transition experts, let's show off our skills by adding an additional design element above the truncated `ProjectHeader`.

```jsx
// next-project-heading.jsx

import React from 'react'
import { Box, Text } from 'rebass'
import styled from 'styled-components'

const Heading = styled(Text)`
  color: ${props => props.theme.colors.red};
  font-family: ${props => props.theme.fonts.sans};
  font-size: ${props => props.theme.fontSizes[3]}px;
  font-weight: normal;
`

const Rule = styled.hr`
  background: #e3e4e5;
  height: 1px;
  border: 0;
`

const NextProjectHeading = () => (
  <Box mb={5}>
    <Rule />
    <Heading pt={3}>Next Project</Heading>
  </Box>
)

export default NextProjectHeading
```

And finally, let's slot it in inside of our `Project` template component.

```jsx
const FadingNextProjectHeading = posed.div({
  exiting: { opacity: 0 },
})

<TransitionLink
  style={{
    textDecoration: 'none',
    color: 'inherit',
  }}
  to={nextProjectUrl}
  exit={exitTransition}
  entry={entryTransition}
>
  <FadingNextProjectHeading pose={transitionStatus}>
    <NextProjectHeading />
  </FadingNextProjectHeading>
  <SlidingHeader pose={transitionStatus}>
    <ProjectHeader project={project.next} truncated={shouldTruncate} />
  </SlidingHeader>
</TransitionLink>
```

![](https://media.giphy.com/media/VdJWm6LRWX5i0eBMej/giphy.gif)

{% github https://github.com/mattrothenberg/gatsbygram/pull/4 %}


## Remote Data via DatoCMS

One of my favorite CMS at the moment is [DatoCMS](https://datocms.com). Why, you may ask?

1. They have a generous free tier.
2. They have a first-class Gatsby plugin (for sourcing data).
3. It's compatible with [Gatsby Preview](https://www.gatsbyjs.org/blog/2018-07-17-announcing-gatsby-preview/)

With a free account, we'll be able to replace the hard-coded set of project data with _actual_ data from a headless CMS! 

Once your account is created, log in and create a `Project` model with a schema that looks something like this.

```js
const project = {
  title: 'single-line-string',
  description: 'multiple-paragraph-text',
  featuredPhoto: 'single-file',
  photos: 'multiple-files',
  slug: 'seo',
  category: 'link',
}
```

![](https://thepracticaldev.s3.amazonaws.com/i/yag4d8z6rflb06ftha7p.png)

With your model in place, go ahead and add a few different projects. They don't need to be perfect. Just add a few, making sure to specify the title, description, category, and add photos.

Once you've added a few projects, we can turn our attention to building our Gatsby site with this remote data.

```bash
yarn add gatsby-source-datocms gatsby-transformer-sharp gatsby-plugin-sharp gatsby-image
```

Now, before we head into `gatsby-config.js` to add this plugin, we need to add an `.env.development` and `.env.production` file to our root directory, as well as ensure that these files are _ignored_ from version control (so we don't accidentally leak our DatoCMS credentials to the world). Go ahead and get your Dato API key as well as your site URL and add these values to the respective `.env` files you created. For now, these credentials will be the same for both development and production, but you can always generate separate credentials.

```
DATO_API_TOKEN=""
```

Then, at the top of `gatsby-config.js`, let's require the `dotenv` library so that these environment variables get pulled into memory and become available in the module.

```js
require('dotenv').config({
  path: `.env.${process.env.NODE_ENV}`,
})

module.exports = {
  plugins: [
    `gatsby-transformer-sharp`, // for eventual image manipulation
    `gatsby-plugin-sharp`, // for eventual image manipulation
    {
      resolve: `gatsby-source-datocms`,
      options: {
        apiToken: process.env.DATO_API_TOKEN,
        apiUrl: 'https://site-api.datocms.com',
      },
    },
    ...etcetera,
  ],
}
```

Now, the next time you run `yarn develop`, pay extra special attention to the output that reads

```
View GraphiQL, an in-browser IDE, to explore your site's data and schema
⠀
  http://localhost:8000/___graphql
```

Gatsby comes with a built-in GraphQL explorer (featuring all sorts of goodies, including a killer autocompletion), which we can use to build up a query for fetching our project data. Throw the following query in the left-hand side and watch your DatoCMS data appear before your eyes!

```graphql
{
  projects: allDatoCmsProject {
    edges {
      node {
        title
        slug
      }
      next {
        title
        slug
      }
    }
  }
}
```

But our site isn't yet using this data to dynamically create our project pages. In order to wire that up, we'll need to head back into `gatsby-node.js` and make a few adjustments.

First things first, let's destructure `graphql` as an additional argument to our `createPages` method.

```js
exports.createPages = async ({ graphql, actions }) => {}
```

Then, let's iterate on and save the GraphQL query from above as a local variable. Notice how we're not pulling _all_ the information for each project (e.g., description, featured photo, etc?). I'm a big fan of deferring that sort of data fetching to our `Project` component template. More on that in a moment.

```js
const query = `{
  projects:allDatoCmsProject {
    edges {
      node {
        title
        slug
        description
      }
      next {
        title
        slug
      }
    }
  }
}`
```

Finally, let's actually run that Graphql query and adjust our page creation logic to accommodate the new shape/structure of the data that DatoCMS returns back.

```js
// Run the query
const result = await graphql(query)

// Abort if there were errors
if (result.errors) {
  throw new Error(result.errors)
}

const projects = result.data.projects.edges

const createProjectPage = project => {
  // Our GraphQL response actually tells us what the "next" node is, which is great! In the case of the last project in the list, let's default "next" to the very first project so we have a nice "carousel" of projects on our site.
  const next = project.next || projects[0].node

  createPage({
    path: `/projects/${project.node.slug}`,
    component: projectTemplate,
    context: {
      nextSlug: next.slug,
      ...project.node,
    },
  })
}

projects.forEach(createProjectPage)
```

If all is well, that should run without errors and you should be able to crack open your browser to http://localhost:8000/projects/SLUG, where slug is one of the auto-generated slugs that DatoCMS created from your project's title.

![](https://media.giphy.com/media/S5cUCPnaJQ9RGr1SI1/giphy.gif)

We're getting very close to the finish line! But we still need to fetch the remaining details about a project. Off to the `Project` component template we go!

You might be asking, "Wait, why don't we just fetch all of that data right here"? In my opinion, there's an even better place to get the rest of our project data than `gatsby-node.js`. Indeed, this file is meant moreso to build the "edges" of our website, enumerating what the different pages are without fetching _all_ of the data we need for each of them. Think of what we just did as building the "shell" of our website. From here, we can return to our `Project` template component and fetch what we need to bring this page to life.

In `project.js`, let's go ahead and import Gatsby's built-in `graphql` function. And at the bottom of the file, let's go ahead and write + export another query that gets the remaining data we need.

```jsx
import { graphql } from 'gatsby'

// component boilerplate
export const query = graphql`
  query($slug: String!, $nextSlug: String!) {
    project: datoCmsProject(slug: { eq: $slug }) {
      description
      category {
        title
      }
      featuredPhoto {
        fluid {
          ...GatsbyDatoCmsFluid
        }
      }
      photos {
        fluid {
          ...GatsbyDatoCmsFluid
        }
      }
    }
    next: datoCmsProject(slug: { eq: $nextSlug }) {
      title
      slug
      description
      category {
        title
      }
      featuredPhoto {
        fluid {
          ...GatsbyDatoCmsFluid
        }
      }
    }
  }
`
```

A few notes.

- I lied. We're actually writing _two_ queries. One to get _all_ of the the current project's data, and another to get the information needed to render `ProjectHeader` or the _next_ project in the queue. Notice how these queries are aliased with `project:` and `next:` respectively?
- `GatsbyDatoCmsFluid` is a GraphQL fragment (effectively a shared piece of query logic) that comes from `gatsby-source-datocms`. This fragment returns an object that slots in seamlessly to Gatsby's `Img` component, which is responsible for rendering images. Why use this library, you might ask? Well...

> It combines Gatsby’s native image processing capabilities with advanced image loading techniques to easily and completely optimize image loading for your sites. gatsby-image uses gatsby-plugin-sharp to power its image transformations.

So, we've exported this verbose GraphQL query but still nothing is happening! That's because under the hood, Gatsby is injecting a `data` prop into our page component, `project.js`, but we're not actually doing anything with it. Let's go ahead and build up a real `project` – full of data from our remote DatoCMS – and pass _this_ to `ProjectInner`.

```jsx
const Project = ({ pageContext: projectShell, data }) => {
  const { project, next } = data // GraphQL results
  const aggregateProject = {
    ...projectShell,
    ...project,
    next,
  }

  return (
    <TransitionState>
      {({ transitionStatus }) => (
        <ProjectInner
          transitionStatus={transitionStatus}
          project={aggregateProject}
        />
      )}
    </TransitionState>
  )
}
```

Given that our `project` data structure has changed, we'll need to refactor a few presentational components accordingly. Let's start off with `ProjectHeader`.

Before, we were hardcoding the `category`, `description`, and `hero` image. For the first two values, we can simply pluck the fields off our `project` prop, e.g,

```jsx
<Category as="h3">{project.category.title}</Category>
```

For our `hero` image, however, we'll actually need to pass `project.featuredPhoto` as prop and leverage the `Img` component from `gatsby-image` library to render the actual image.

```jsx
const Hero = ({ photo, truncated }) => {
  return (
    <HeroWrap mt={[4, 5]} truncated={truncated}>
      <AspectRatioBox ratio={8 / 5}>
        <Img fluid={photo.fluid} />
      </AspectRatioBox>
    </HeroWrap>
  )
}
```

The next component we need to fix up is `ProjectContent`, as it's currently hard-coded to return a grid of 8:5 placeholders. We need to pass `project.photos` as a prop, iterate over the collection, and render `Img` components accordingly.

```jsx
const ProjectContent = ({ photos }) => {
  return (
    <Box my={4}>
      <Grid>
        {photos.map((photo, index) => {
          return (
            <AspectRatioBox key={index} ratio={8 / 5}>
              <Img fluid={photo.fluid} />
            </AspectRatioBox>
          )
        })}
      </Grid>
    </Box>
  )
}
```

And just like that, our Project page is complete.

{% github https://github.com/mattrothenberg/gatsbygram/pull/5 %}

## Adding a Home Page

The _one_ thing that's missing from our website is a nice home page that lists out all of the projects. Luckily, by this point, we're Gatsby experts and should have no issue wiring up a home page to this end.

Let's start by writing a GraphQL query that will get us all of the data we need.

```js
export const query = graphql`
  {
    projects: allDatoCmsProject {
      edges {
        node {
          slug
          title
          featuredPhoto {
            fluid {
              ...GatsbyDatoCmsFluid
            }
          }
        }
      }
    }
  }
`
```

And then it's simply a matter of iterating over our dataset and rendering some items on the page!

```jsx
const Home = ({ data }) => {
  const projects = data.projects.edges
  return (
    <Layout>
      <Grid>
        {projects.map(project => (
          <ProjectGridItem key={project.node.title} project={project.node} />
        ))}
      </Grid>
    </Layout>
  )
}
```

```jsx
const ProjectGridItem = ({ project }) => {
  return (
    <AniLink
      style={{ textDecoration: 'none' }}
      fade
      to={`/projects/${project.slug}`}
      duration={0.2}
    >
      <Box>
        <Img fluid={project.featuredPhoto.fluid} />
        <Box mt={3}>
          <Description>{project.title}</Description>
        </Box>
      </Box>
    </AniLink>
  )
}
```

This time around, I've opted to use the `AniLink` component from `gatsby-plugin-transition-link`.

> AniLink is a wrapper around the TransitionLink component and provides four default transitions: fade, paintDrip, swipe, and cover.

Under the hood, it uses [`gsap`](https://greensock.com/gsap) for performing the actual transitions.

I would definitely recommend using `AniLink` over `TransitionLink` if you're looking to add simple page transitions to your site (or at least certain parts of it). 


Commit: https://github.com/mattrothenberg/gatsbygram/commit/ffdcc67ad1aa02f2d6ca85ea58ebdc900bb2c0fc

## Conclusion

Phew, that was fun! I hope our tour of setting up a Gatsby site from scratch gave you the confidence to go out and build a non-imitation site of your own!

Your feedback on how I can make this article even better/more helpful is most appreciated!

I'm also interested in hearing what other Gatsby topics you'd like me to write about!