DEV Community

loading...
Cover image for Using Gatsby like Single Page Application

Using Gatsby like Single Page Application

hoshikitsunoda profile image Hosh'ki Tsunoda Originally published at hoshki.me Updated on ・4 min read

Upon creating my portfolio site, I had no intention to have a blog there. The site was supposed to be just a page where you can download my resume and find my contact info.

Only recently, I began to realize benefits of having a tech blog as a developer which eventually led me to decide on adding a blog section.

Identifying the problem

This website is made with Gatsbyjs, Blazing fast static site generator for React. Checkout their website if you aren't familiar with it.

Gatsby generates a page for each URL path, meaning, Gatsby generates an html file for each corresponding page file ( e.g. index.js-> index.html ). But what I want to achieve is:

  • update a section of index page based on the menu item that is clicked
  • update URL path and push it to history so the user can navigate with 'go back' and 'go forward' button on the browser while staying on the same index page.

With my initial implementation, UI gets updated however the path stays the same.

The problem is somewhere in the routing. It needs a routing but only on client-side because updating the URL path triggers Gatsby to use backend routing by default and a new page gets generated.

Tell Gatsby to stay on the page

Gatsby comes with Link and Router components which are provided by @reach/router. These components enables internal linking. You can use them like this:

// pages/index.js
<Router>
  <Projects path="/" />
  <Blog path="/blog" />
  <Resume path="/resume" />
</Router>

// components/Menu.js
<Link to="/">
<Link to="/blog">
<Link to="/resume">

The /(root) path shows a list of projects. The /blogpath will render a list of recent blog posts. And the /resumepath for resume download view.

This creates an unknown path issue. Every time a menu item is clicked, it ends up in 404 page. It is because Gatsby tries to find a page file that's corresponding to an updated path.

The solution for this is actually fairly simple, you just need to add following to your gatsby-node.js so Gatsby knows it needs to stay inside index.html.

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

In Gatsby official docs, it says:

page.matchPath is a special key that's used for matching pages with corresponding routes only on the client.

This ensures Gatsby to use Reach Router by passing matchPath parameter to the page at build time so it can navigate the page with client-side routing.

Rendering only a necessary component(s)

The code above will be sufficient if you just want to enable client-side routing. But to give it a better performance, you want to enable a lazy loading. Lazy loading is a technique to only render necessary component(s) when it's needed. More on React Lazy Loading here.

First, the components you wish to lazy load need to be imported dynamically using React.lazy like so:

// pages/index.js
const Blog = React.lazy(() => import("../components/Blog/Blog"))
const Resume = React.lazy(() => import("../components/Resume/Resume"))

In my case, a default view should be <Project /> therefore it should be rendered normally.

Dynamically imported components need to be wrapped in React.Suspense which suspends rendering until a condition is met. To make it more React way and give it a reusability, let's create a LazyLoadComponent. Your code should look like this:

// pages/index.js
const LazyLoadComponent = ({ Component, ...props }) => (
  <React.Suspense fallback={<Spinner />}>
    <Component {...props} />
  </React.Suspense>
)

In the code above, React.Suspense renders loading <Spinner /> until it receives props, then once props are received corresponding component gets rendered. By the way, I grabbed a spinner from this awesome project called Single Element CSS Spinners.

Once that's created, all you need to do is to wrap these LazyLoadComponents with the <Router /> like so:

// pages/index.js
<Router>
 <Projects path="/" />
 <LazyLoadComponent Component={Blog} path="/blog" />
 <LazyLoadComponent Component={Resume} path="resume" />
</Router>

There you go! Now Blog and Resume components are only rendered when the corresponding menu item is clicked. If you open up Network tab on your browser console, you'll see JS files being loaded only when the menu item is clicked.

Conclusion

Here are the key takeaways for turning Gatsby into an SPA:

  1. You need to tell Gatsby to stay on the index page so it doesn't use backend routing to switch the page.
    • Add matchPath parameter in your gatsby-node.js so routing happens on client side.
  2. To achieve better performance, SPAs should render components only when it's needed:
    • Use React.lazy and React.Suspense so it lazy loads the components.

For now, only an index page of my site works like an SPA. Blog section is still generated in a traditional Gatsby way. Maybe I will turn whole thing into an SPA eventually but for now, I'm happy with the way it turned out:)

Thanks for reading and please share if you like what you just read!

Discussion (0)

pic
Editor guide