Gatsby + Apollo + GraphCool + Netlify: The Web’s Promised Land

Few things excite my nascent programming life like the possibility of serving static HTML files on a CDN that loads a single page React/Apollo(GraphQL) app. It’s my nerd dream. The promised land.

As a solo entrepreneur, I need to stand on the shoulders of giants to get there. And until Gatsby, I didn’t think I’d ever get there. But the promise of a Gatsby (a static site generator), React, Apollo (a GraphQL client), GraphCool (GraphQL as a service backend), and Netlify (static site builder and CDN) led me through the valley and I’m happy and amazed to say that I’ve found the...

… and it’s a road sign. *sigh*

This post details my journey, struggles, and success in the stack. I’m building the second version of my online car dealership’s dealer management system. It’ll be a multi-tenant app for my dealership and other dealers. This blog’s goal is to share my knowledge in areas with few tutorials, hopefully help a few people, and get feedback on my mistakes — I don’t claim to be an expert.

I’ll first discuss why this stack makes me excited. Then I’ll chronicle the bumps and triumphs in my implementation. If I had you at hello and just want code, scroll down.

My Project’s Goals: Fast and Intuitive

Gee, those are the goals for every project from every genre. How enlightened! 🤗 🙄

…But hear me out! We all know the advantages and wonder of the single page app (“SPA”) or you wouldn’t be here — you’d find some “Rails doesn’t scale” blog instead. However, I think we foolishly ignore many of the SPA’s costs. And I think the most glaring is the initial load time.

We Need Static Site Generators #becausewordpress

What powers the web? Wordpress. Duh. You knew that. And what’s Wordpress? It’s PHP with magic ✨. Err plugins.

Wordpress makes dirt look young. But I argue that it’s still here and running damn near everything because it’s relatively fast and kinda easy (... ok… and it has a ton of developer inertia). Most Wordpress sites load reasonably quickly, but most of our React apps…? We stick our heads in the sand. “Oh user, just you wait until you get into my slick page transition, no page refreshing, live data amazingness of my SPA!” But many won’t wait long enough for that goodness to load. 💨

Here’s the problem. In the normal client side React render world, apps typically ship with the following markup…

<div id=“app”></div>

The user must then wait for all of the javascript to load. Once loaded, APIs make their long winded calls. Upon return, the browser must convert the API into HTML markup and then render/stuff it inside “app”. For many sites, this takes upwards of 15 seconds.

But unlike the typical React app, Gatsby introduces a compile step where the page is rendered with the data and markup. In contrast, here’s what a GatsbyJS + React + Apollo app might send down the wire.

  // minified css
  // minified js

{ “apollo”: { “data”: { “allVehicles”: [{id: 1 … }, …]}}}
    <div id=“app”>
      <figure>2014 Nissan GTR</figure>

Browsers are insanely fast at one thing — rendering HTML. And with Gatsby static site generated goodness, we’ve just fed the browser straight HTML. Then while the user views the site, our SPA loads in the background. API calls are transparently fired and the page data syncs with the server.

Because we’re generating static HTML files, our site can quickly be deployed to a CDN. And with a GraphCool backend powered by AWS Lambda functions, this is infinitely scalable from second one. And it’s all with minimal developer overhead.

A single page app that paints as fast as static markup with infinite scalability and little developer work to achive it. That’s incredible!

Now How to Make the Damn Thing Work

It all sounds good in a vacuum. But blending an SPA with a static site generator still has its hiccups!

Gatsby: Create Pages Programmatically

First thing’s first… let’s see if I can even get this thing to create a few static pages from the GraphCool API. I couldn’t find a tutorial to create pages via an API — they’re all contrived blog markdown examples. (Does anyone really store blog posts as MD files? I thought that’s why God invented Postgres?) I want to programmatically generate my vehicle pages from my GraphCool GraphQL endpoint. So how do I call an endpoint from Gatsby?

The Gatsby tutorial road felt nice until…

Gatsby is big on plugins. Unfortunately, there isn’t a GraphQL query plugin, and making a plugin is above my paygrade. The good news is you can write it out using Gatsby’s gatsby-node.js file. Here’s what I did.

// gatsby-node.js

The gist (pun intended) is to query your data inside `sourceNodes`. Then create nodes using `createNode` to query later. `createPages` queries the previously created nodes and… creates pages. Mind 💥

This worked really well! Within an hour of my Gatsby deep dive, I had it generating static files for all queried vehicles.

Apollo SSR + Gatsby

Now the pain begins. To restate my goals, I want Gatsby to generate the pages and Apollo to handle my data. But Gatsby seems to want my data too. Its APIs are structured to get all data from the nodes and pass it into the page. What now? The idea of maintaining duplicate queries across Gatsby and Apollo isn’t appealing. I don’t want to bifurcate my client data layer. I want Gatsby to build pages and route, and I want Apollo to handle client data fetching and the cache. Humm.

Gatsby’s SSR API provides two methods — replaceRenderer and onRenderBody. The former replaces the Gatsby supplied router component. The latter allows you to set various React components into the HTML. Sounds like everything we need! Here’s the code with Styled Components added.

// gatsby-ssr.js

Looks great! Just one problem.

gatsby build // error: client not found. Wrap component in Apollo Provider

Client isn’t found because Gatsby SSR won’t accept an async function. The build process doesn’t wait for the resolve. I opened an issue.

Gatsby’s creator Kyle Matthews said he’d take a PR for it. I have a PR for SSR data fetching pending…

But waiting for a merge…?

So what now? How about a fork and a hack! So that’s what I did. Enter:

Even statue Jesus likes a good hack

I changed two files: api-runner-ssr.js & static-entry.js. For api-runner-ssr, I added the async function from api-runner-browser. For static-entry, I imported the new apiRunnerAsync from api-runner-ssr, chained onRenderBody and replaceRouter, and wrapped the remaining code in `then.` Hack complete.

I then changed my package.json to point to my forked repo and gave it the old `gatsby build && gatsby serve` and…

It worked!

Well, sort of. The build worked. And it showed the page properly. But Apollo wasn’t firing queries or managing the client state. It turns out there’s one more API to conquer.. gatsby-browser.

This one is straight forward. `replaceRouterComponent` wraps the page’s React tree on the client. In this case, I wrapped the React children in the Apollo Provider with the initial state hydrated from the SSR onRenderBody.

Then in your React Apollo components, the fresh-data secret is to set Apollo’s query fetchPolicy option to:

 fetchPolicy: ‘cache-and-network’

Apollo will use the cached data initially, run the query in the background, and then diff the results. In other words, raw html with data -> Apollo initializes and syncs with the existing raw html -> fires GraphQL api calls -> updates stale data. So helpful! Two tips..

  • Don’t conditionally render on data.loading. When Apollo fetches the new data, it will set the loading flag to true causing your UI to hide the SSR data.
  • If the Apollo query is only useful at runtime, e.g. user specific data, pass the option: `ssr: false` That tells Apollo to skip the query during server side rendering. The component will be in its loading state at runtime.

What Just Happened?

We created a statically generated SPA with a client that automatically updates the data at runtime. And it only took three extra files above your normal Apollo Client app!

Netlify to the Next Level

Netlify popularized the “JAM Stack”, so why not give them and their CDN a whirl? The deployment process was outrageously simple. I signed up, linked it to my Bitbucket repo, told it to build on push to master, added a .nvmrc (v8.5.0) for build, and gave it “gatsby build” as the script.

It’s a jam… stack. I mean, I get it. It’s not funny… but I get it.

I pushed to master and……. error. Of course. After error. After error. And the errors weren’t making sense. My rule is when build errors don’t make sense, switch to Yarn. Netlify will use Yarn if it sees a Yarn.lock file. I committed my Yarn.lock, pushed to master, and…. 💥 it worked! Successful build. I stopped asking questions.

So what does it look like?

(relax… it’s a proof of tech concept, not the next web award design winner)

You may notice the mileage on the 2006 Ford intitally shows 0 and then updates to 13400. I updated the mileage in GraphCool after the static build. That’s an example of Apollo running the client side and updating the data. If you view source, you’ll see:

<div data-reactid=”15">
<! — react-text: 16 →0<! — /react-text →<! — react-text: 17 → mi<! — /react-text →

Without any work on my end, Apollo knew which element to update. And for its next trick, click on the link. You’ll see the mileage on the vehicle detail page is correct (not zero) even though the HTML mark up is 0. Apollo updated its universal cache on the initial load with the correct mileage, and that’ll apply to all future instances of that vehicle’s data.

… or just great work from the MDG / Apollo team!

But these data updates may be acceptable… or not. In the event they aren’t, we need to rebuild the static site.

And as chance would have it, Netlify gives us a rebuild build hook!

From GraphCool, simply use a GraphCool/Lambda function to call the rebuild hook whenever needed.

Is This the Promised Land?

Pioneers get the arrows, settlers get the land. There’s no question arrows are still flinging. But the promise is so great that I’m ok taking a few shots to be near the front of the pack.

I have a clear development path forward. That path starts today after I demonstrated to myself that it could be done. I can’t wait!

What do you think? Am I completely off base? Let me know in the comments!

Like what you read? Give Dennis Walsh a round of applause.

From a quick cheer to a standing ovation, clap to show how much you enjoyed this story.