Swizec Teller - a geek with a hatswizec.com

    Towards a Gatsby+Suspense proof-of-concept

    Messing around with React Suspense gave me a great idea: What if you could fallback to preloaded data instead of a loading spinner? πŸ’‘


    You could leverage Gatsby's approach with React Suspense for a smooth user experience. Load partial data at build time, bake it into the static HTML, then load the rest when a user renders your page.

    Initial experiments look great, where I'm stuck is loading data for subsequent pages. Let me explain

    You can see it in action here πŸ‘‰ https://gatsby-suspense-poc.now.sh/

    Code on GitHub πŸ‘‰ https://github.com/Swizec/gatsby-suspense-poc

    Gatsby with dynamic data, some background

    Let's start with background on using Gatsby for dynamic pages.

    Gatsby is a static site builder. You build a website in React, use GraphQL to connect to data sources, and compile a static HTML page.

    Your GraphQL queries run at compile time and bake data into your static HTML via hardcoded props. Not quite hardcoded props but close enough.

    The result is a page that can work offline and loads without API requests. This makes it fast.

    When your site loads in a browser, however, it becomes a React single page app. React hydration means there's no flash of blank content and no need to re-render the HTML.

    You can add Apollo Client for GraphQL support or use fetch(). It's just a JavaScript app at this point.

    I like to wrap the root node in ApolloProvider and use react-apollo-hooks for the rest.

    Click through for source
    Click through for source

    That gives my entire app access to GraphQL for a specific server. Using Star Wars API for this example.

    Click through for source
    Click through for source

    Loading additional data on render

    Once you have "cached" data baked into your HTML and the ability to load fresh data on render, you're presented with a dilemma.

    How do I show static data, communicate that more data is loading, and show fresh data when I've got it? If the user is offline, what then?

    Those are tough problems. Traditionally, you'd render a component, show a spinner, fetch more data after render, then re-render once done.

    Something like this (this is pseudocode):

    Click through for source
    Click through for source

    STARSHIP_QUERY is a GraphQL query that returns a list of $count starships from StarWarsApi, swapi for short.

    useQuery runs the query and returns a loading state and data.

    Component renders the loading notification and a list of starships from props. Those were baked in at build time. When fresh data arrives from our API call, the component re-renders.

    You can tell this is pseudocode because I'm re-defining the data constant and that won't work :)

    Using React Suspense instead

    With React Suspense, you can make that API call preemptively. While the page is loading.

    Some background on how that works in last week's Experimenting with the new React Concurrent mode article ✌️

    Here's how you might set it up for Gatsby. Or at least how I did.

    Click through for source
    Click through for source

    All this goes in the index.js page. The first page you see in a Gatsby app.

    We start with a static page query for Gatsby. This one runs at build-time and bakes data into our HTML. I tried having a shared query between Gatsby and Apollo, but the graphql (gatsby) and gql (apollo) tags are not compatible.

    Then we've got the starshipQuerySuspender, which runs in the browser when Gatsby turns this page into a JavaScript app.

    The <Starships> component suspends with a data load and renders <StarshipsList> when data shows up. The static page is going to render this same component with its data.

    Page render with suspendering

    In the IndexPage component we can then use <Suspense> to coordinate loading and error states.

    Click through for source
    Click through for source

    Our Gatsby page gets static data via the data prop. This works offline too, you can try, here πŸ‘Œ

    Here's where the fun starts:

    Suspense sees that we're running a GraphQL query (the suspender.read()) so it renders the fallback render prop. But that prop uses our static data to render a list of 5 starships. It's not all of them, but it's better than nothing.


    Even if it all goes to shit, our user gets the most important data they're looking for.

    Once our query finishes, Suspense renders the main list. Now with fresh data from the API.


    That <ErrorBoundary> gives us an extra feature: We can show something useful even if the user is offline and our query fails.


    Same fallback to static data, now with a warning that you're offline ✌️

    Where I'm stuck: subsequent pages

    Now that approach works great, if your site has a single page. Where it breaks down are multi-page sites. Which is most of them.

    I tried replicating the same code on page-2.js and this happens:


    Two swapi requests on initial page load. Makes subsequent pages crazy fast with no hint of data loading, but means that you're loading data for your whole website on initial load.

    That just won't do. We need to hook into the router's lifecycle. Gatsby uses @reach/router by the way.

    And that's where I'm stuck. Tried a few things, none worked.

    Suspend on render, nope

    Loading on render was slow as heck:

    Click through for source
    Click through for source

    Worked but just as slow as running a query without Suspense.

    Reach Router lets you pass state to pages with a state prop in your <Link> component. Whatever you pass shows up in the location prop on the target component.

    Works great for static values, but did not work with this approach:

    Click through for source
    Click through for source

    Not sure why, but trying to pass a function made the whole state value null.

    Hook into onPreRouteUpdate, nyet

    Another approach I tried was using Gatsby's browser APIs. You can hook into various parts of the page lifecycle with special methods in gatsby-browser.js.

    Click through for source
    Click through for source

    Thought I could manipulate the location prop passed into a page and I can, sort of.

    When you console.log that value, it shows that you successfully passed the dataSuspender. This was great news until I realized that data shows up after the page renders.

    How do I know?

    Because console.log(location) shows the value, but console.log(location.dataSuspender) prints undefined. Means the value isn't there when you're printing, but does show up later. Hooray for console.log being dynamic and always showing current object state.

    I tried

    I_tried giphy

    I tried everything I could think of. Some of this stuff is either a bug in Gatsby, a bug in @reach/router, or just not meant to work this way by design.

    Next step: bug people online to find out πŸ˜›

    Happy Monday, ~Swizec

    Did you enjoy this article?

    Published on November 11th, 2019 in Front End, Technical

    Learned something new?
    Want to become an expert?

    Here's how it works πŸ‘‡

    Leave your email and I'll send you thoughtfully written emails every week about React, JavaScript, and your career. Lessons learned over 20 years in the industry working with companies ranging from tiny startups to Fortune5 behemoths.

    Join Swizec's Newsletter

    And get thoughtful letters πŸ’Œ on mindsets, tactics, and technical skills for your career. Real lessons from building production software. No bullshit.

    "Man, love your simple writing! Yours is the only newsletter I open and only blog that I give a fuck to read & scroll till the end. And wow always take away lessons with me. Inspiring! And very relatable. πŸ‘Œ"

    ~ Ashish Kumar

    Join over 14,000 engineers just like you already improving their careers with my letters, workshops, courses, and talks. ✌️

    Have a burning question that you think I can answer?Β I don't have all of the answers, but I have some! Hit me up on twitter or book a 30min ama for in-depth help.

    Ready to Stop copy pasting D3 examples and create data visualizations of your own? Β Learn how to build scalable dataviz components your whole team can understand with React for Data Visualization

    Curious about Serverless and the modern backend? Check out Serverless Handbook, modern backend for the frontend engineer.

    Ready to learn how it all fits together and build a modern webapp from scratch? Learn how to launch a webapp and make your first πŸ’° on the side with ServerlessReact.Dev

    Want to brush up on your modern JavaScript syntax?Β Check out my interactive cheatsheet: es6cheatsheet.com

    By the way, just in case no one has told you it yet today: I love and appreciate you for who you are ❀️

    Created by Swizec with ❀️