🌐 Holy Grail layouts in Gatsby
- Published on
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! 🙌🏽