Build SPA with Gatsby

January 25, 2022

programming
article cover
Image by Sandro Katalina

Gatsby doesn’t generate SPA

Gatsby is a React-based SSG (Static Site Generator) that generates a very fast static site. Since it’s only SSG which generates HTML, it’s not routing on a client-side with Javascript, which means the site is not SPA, strictly speaking. Because of that Gatsby is often considered as just a tool to build such as blogs. But of course Gatsby CAN build SPA as well. Perhaps, some may ask me ‘what makes you insist on Gatsby?‘. Personally, I think Gatsby is totally better than React. There is nothing that Gatsby can’t do and React can. When you use Gatsby as SPA builder you still have advantages such as image optimization and powerful/convenient plugins. There’s no reason not to choose Gatsby!

Stay on the root

As default Gatsby generates paths based on files created under pages folder. When a user visits a certain path Gatsby will return the certain HTML.

We can’t let it be when it comes to building SPA with Gatsby. Otherwise, Gatsby tries to find non-existing HTML and eventually returns a 404 error. So we need to tell Gatsby to only refer to the root. Because, as you know, SPA just pretends to move paths but actually not move from the root.

Edit gatsby-node.js

// gatsby-node.js
exports.onCreatePage = ({ page, actions }) => {
  const { createPage } = actions
  if (page.path === `/`) {
    page.matchPath = `/*`
    createPage(page)
  }
}

This is telling Gatsby to always return the root HTML no matter the URL path.

Use @reach/router

Gatsby uses @reach/router under the hood. So use this as a router.

Put components inside <Router> then specify which component represent which path by path={”} property.

import React from 'react'
import { Landing } from './landing'
import { UserPage } from './userPage'
import { PostList } from './postList'

export const App = () => {
  return (
    <Router>
      <Landing path="/" />
      <UserPage path="/user" />
      <PostList path="/posts" />
    </Router>   
  )
}

Only render necessary components

If you just want to build SPA, meaning routing on a client-side, it’s already done. But we can improve performance by rendering only the right component at the right time.

First of all, use React.lazy to dynamically import component.

const UserPage = React.lazy(() => import('./userPage'))
const PostList = React.lazy(() => import('./postList'))

We need to wrap a component that returns Promise by React.Suspense, so let’s make the special component to render dynamically imported components.

const LazyComponent = ({ component: Component, ...props }) => {
  return (
    <React.Suspense fallback={<Spinner />}>
      <Component {...props} />
    </React.Suspense>
  )
}

After all, it looks like this.

import React from 'react'
import { Landing } from './landing'
const UserPage = React.lazy(() => import('./userPage'))
const PostList = React.lazy(() => import('./postList'))

export const App = () => {
  return (
    <Router>
      <Landing path="/" />
      <LazyComponent component={UserPage} path={'/user'} />
      <LazyComponent component={PostList} path={'/posts'} />
    </Router>   
  )
}

Note

When you import a component using React.lazy, the component need to be exported as default.

React.lazy(() => import('./test'))

// test.jsx

export const Test = () => <div>This won't work</div>

const Test = () => <div>This one works</div> 
export default Test

Depending on the setting of gatsby-node.js it is possible to build partial SPA within a static site. I hope Gatsby will be more popular.


Profile picture

Photographer / Web engineer working in Berlin.
A Japanese living with a Slovak wife and two German cats.