An Idiosyncratic Blog

🌐 Holy Grail layouts in Gatsby

Published on
4 minutes read

When we look at most modern web apps, they follow a Holy Grail layout. At its core, the Holy Grail Layout is a page with a header, footer, and three columns. The center column contains the main content, and the left and right columns contain supplemental content like ads or navigation.

There are many articles around how to create a Holy Grail layout, so I'll not go into that. Instead, I'll show how to use such a layout for an app which uses GatsbyJS as a framework.

The Layout

For the sake of brevity, let's assume that the NavBar and SiteFooter components are developed (or imported from a library like Material UI 🙄). The PageLayout component is then

import React from 'react'
import { NavBar, SiteFooter } from 'components'

const PageLayout = ({ children }) => {
  return (
    <div className="layout-container">
      <NavBar />
      <main>{children}</main>
      <SiteFooter />
    </div>
  )
}
export default PageLayout

And we have our layout ready. children here are the page content which will get rendered when we wrap this layout around a page.

The Gatsby Connection

Pages in Gatsby live in the src/pages folder. A Page maps to a route, say if you have a file at src/pages/about/index.js then your users can access that page using https://yoursite.com/about. Checkout the docs for more info.

If we want to apply a layout to a page, we will need to include the PageLayout component and wrap the page in it. For example, here is how we can wrap the about page in our layout

import React from 'react'
import PageLayout from '../components/layout'

const Home = () => {
  return (
    <Layout>
      <h1>I’m in a layout!</h1>
    </Layout>
  )
}
export default Home

If the app starts growing with multiple pages we need to repeat this for every page and template that needs this layout. Not very DRY. We like to keep things DRY.

Another problem with this approach is that Gatsby does not, by default, automatically wrap pages in a layout component. The “top level” component is the page itself. As a result, when the “top level” component changes between pages, React will re-render all children. This means that shared components like navigations will unmount and remount. This will break CSS transitions or React state within those shared components.

Keeping things DRY

Fortunately for us, Gatsby exposes an API which makes things easy for us. wrapPageElement lets us use wrapper components around pages that won’t get unmounted on page changes.

Let's create our page wrapper,

// Wraps every page in a component
import React from 'react'
import PageLayout from '../components/layout'

const wrapPageElement = ({ element, props }) => {
  return <Layout {...props}>{element}</Layout>
}

export default wrapPageElement

Gatsby uses special files for using the wrapPageElement API which are gatsby-browser.js and gatsby-ssr.js. We will import the above function in both the files.

// ./gatsby-browser.js
export { default as wrapPageElement } from './gatsby/wrap-page-element'
// ./gatsby-ssr.js
export { default as wrapPageElement } from './gatsby/wrap-page-element'

And that's it. Now we have a global shared layout which maintains its state and doesn't get unmounted when the page changes, along with keeping the code DRY. Yay! 🙌🏽