Every React.js based website owner who cares about SEO should have a Server Side rendering setup around, which requires a lot of resources, even with some caching layer, sometimes it just blocks entire infrastructure, especially if it’s a website like ours (hexometer.com) with a dynamic traffic increase for a specific times over day.

The main problem with SSR is that it is a blocking operation, and while it is in execution the entire Node.js process is not getting any new requests, that’s why we have to have a lot of Node.js SSR processes to handle incoming requests. CDN cache of-course helping to reduce SSR overload, but sometimes when there is a new update or we have to deliver content for authenticated users SSR is just killing our infrastructure, because it also needs some data from our API.

Basically every time when users navigating to our website, it sends 1 SSR request, then SSR sends 1 API request, then in browser, our react app sending second API request to compare cached data. It is obvious that React SSR is just wasting a lot of resources just to get SEO pages available :/

So we started to research other solutions, that will require ONLY static content without having SSR available. And as you can guess we found Gatsby.js!

Why Gatsby.js is different

The main idea behind Gatsby.js is to build statically compiled React + GraphQL application which you can compile once and serve it over CDN cache without having any SSR backend. We have GraphQL backend already, so that was perfect for our use case.

You can’t plug Gatsby.js to an existing React + Apollo application. It requires its own setup and because it is compiling to static rendered HTML + JS, you have to make custom Gatsby GraphQL endpoints to deliver your API data during Gatsby.js build process.

// gatsby-node.js
const query = `
  query {
    .... your custom query here from your API endpoint
  }
`;

exports.sourceNodes = ({ actions, createNodeId, createContentDigest }, configOptions) => {
  return fetch(getQueryURL(query))
    .then(response => response.json())
    .then(res => {
      createNode({
        ... node data from response JSON
      })
    });
}

NOTE Gatsby.js has 2 stages

  1. Building to static content, including external data, download and rendering as an html content

  2. Running in Browser as a regular React application, where you can use React Apollo to make a dynamic GraphQL request, including authentication!

How we migrated our UI app to Gatsby

I would be honest it wasn’t a fast migration process, it required a lot of code migration and things to consider, especially we learned a lot about how to design React components to require static building in mind.

First, we made a basic Gatsby.js app which just had all building blocks for our app migration, then we just started to copy/paste our React components, including specific NPM packages that were a requirement for our app. There wasn’t that match to worry in this stage, thank god we had a very atomic component base and we really got a huge advantage there!

Secondly, we had to move some of our static queries which were part of our React app rendering, specifically, we have some dynamic content like Top Tech Stack apps listing, which has to be available for search engine bots. So we just added static queries and tweaked a little bit page query to meet Gatsby.js page static query concept.

The rest of the application was the same, we had React Apollo and it is still React Apollo for dynamic stuff like website analysis requests or user authentication, and surprisingly, we even didn’t need to change anything there.

The most problematic thing was dynamic navigation because we had page navigation based on API content and structure, and because Gatsby.js itself statically navigated, it was hard to get a concept that you actually have to create a page based on API content in other to have that URL available.

const pageResult = await fetchQuery(TechStackCategoryQuery(category));
await createPage({
    path: `most-popular-tech-stacks/${cleanURLSpace(category)}`,
    component: path.resolve(`./src/components/tech-stack/stackByCategory.tsx`),
    context: {
      ...pageResult.data.TechStack,
      name: category,
      isDetails: false,
    }
});

Other than this, there were no issues in-app migration. Which is fantastic if you think, how match time you can actually save if you will just make your components atomic!

The actual savings

Right now we don’t have server-side rendering, so we actually don’t have any servers or services to handle initial UI content request, it is fully served over Cloudflare CDN! It is just a static file containing pre-rendered full HTML content.

On average we saved 400$/month only just for migrating to Gatsby.js and removing the Server Side Rendering process from our infrastructure. And as you can imagine our website started to load match more quickly from many different regions.

If you like this story please share your claps and a tweet with the link! 👏👏👏👏