Gatsby: Adding async to SSR to help with store hydration

Created on 12 Sep 2017  路  9Comments  路  Source: gatsbyjs/gatsby

First, thanks for all of the hard work! It's a very exciting project and really well done.

I'm a big Apollo fan, but with the current SSR API, I can't hydrate the store because replaceRenderer can't take an async function. I suspect this is also a problem hydrating an Redux store with an API call. Here's what I'd like to do...

Apollo's getDataFromTree walks the component tree looking for GraphQL queries. It sends all GraphQL queries and then updates its store. This would allow Apollo users to have one set of queries for both the client and server! Huge win.

Here's how I'd imagine it working...

// gatsby-ssr.js
import React from 'react'
import { renderToString } from 'react-dom/server'
import ApolloClient, { createNetworkInterface } from 'apollo-client'
import { ApolloProvider, getDataFromTree } from 'react-apollo'

const networkInterface = createNetworkInterface({
  uri: process.env.GRAPHCOOL_API
})

const client = new ApolloClient({
  ssrMode: true,
  networkInterface,
  dataIdFromObject: o => o.id
})


exports.replaceRenderer = async ({ bodyComponent, replaceBodyHTMLString }) => {

  const ApolloStore = await getDataFromTree(bodyComponent)
      .then(() => {
        return () => (
            <ApolloProvider client={client}>
              {bodyComponent}
            </ApolloProvider>
        )
      })
  replaceBodyHTMLString(renderToString(<ApolloStore />))
}

A PR is probably above my pay grade 馃槂, but I'm not opposed to flailing around trying.

Most helpful comment

2423 is live. Thanks for your help and consideration. It's exciting for a novice like me to have any input into such an incredible project!

All 9 comments

So this isn't my favorite idea as this slows server rendering a lot + makes it impossible for Gatsby to know if a page needs rebuilt or not so any current or future optimizations around skipping rendering pages aren't possible.

I'd much rather work towards a solution where Apollo/Relay can build their store from the data Gatsby provides but that's a ways off so I guess an intermediate solution like allowing server rendering to be async makes sense.

I'd be happy to look at a PR from someone adding support for this.

Hi Kyle,

Thank you for your consideration and input! If I can push back a little... Conceptually, I'd like to give Gatsby enough data to figure out pages and routing, but I'd like to keep the data in Apollo. I don't have to sync my data queries across Gatsby and Apollo. I write them once on the client, and Apollo takes care of the server/client sync.

In my application, Gatsby queries the backend for product pages. Apollo then walks bodyComponent for all queries, makes the calls, and hydrates the store. At runtime, Apollo client can diff the raw HTML against its runtime API query and update the site. Then it can attach its realtime subscriptions on top -- really powerful. Combine that with a Netlify rebuild web hook, and I think it's an incredible solution.

I have a hacked version of my proposal that works with gatsby-dev, but I'm not experienced enough to know what else I've screwed up and why my hack is generally bad. I used the async function from Gatsby-node for api-runner-ssr (https://github.com/LawJolla/gatsby/blob/topics/ssr-async/packages/gatsby/cache-dir/api-runner-ssr.js) and simply wrapped the business code of static-entry. (https://github.com/LawJolla/gatsby/blob/topics/ssr-async/packages/gatsby/cache-dir/static-entry.js)

And gatsby-ssr

exports.replaceRenderer =  ({ bodyComponent, replaceBodyHTMLString }) => {

  return new Promise((resolve, reject) => {
    const ApolloQueries = (
        <ApolloProvider client={client}>
          {bodyComponent}
        </ApolloProvider>
    );
    // getDataFromTree walks ApolloQueries tree for all Apollo GQL queries
    // It runs the queries and mutates client object
    getDataFromTree(ApolloQueries)
      .then(() => {
        const ConnectedBody = () => (
            <ApolloProvider client={client}>
              {bodyComponent}
            </ApolloProvider>
        )
        resolve(replaceBodyHTMLString(renderToString(<ConnectedBody/>)))
      })
  })
}

I'm hopeful a more experienced dev can take this on!

For anyone who's curious why this is important, I wrote a blog post explaining my issues.

https://medium.com/@dwalsh.sdlr/gatsby-apollo-graphcool-netlify-the-webs-promised-land-6dd510efbd72

If I've missed something obvious and should have gone another route, your feedback is greatly appreciated!

@LawJolla you want to put up your PR? Doing data fetching during SSR slows things down but after reflecting on this, it's an important escape hatch to have.

@KyleAMathews Sorry for the delay in replying.

I'm happy to submit my working hack now as a PR, but for (hopefully) less work on your end, I can take some time to understand Gatsby better and (hopefully) make a more thoughtful PR. Let me know what works for you!

Thank you for your consideration. SSR data fetching allowed me to keep my queries on the client and not worry about mismatched data. Huge win for me and hopefully others.

Hey no worries! Yeah, definitely put up your code now as a preliminary PR so I and others can provide some quick feedback. Much faster this way.

Had a conversation with another potential big user of Gatsby recently who's using GraphQL a lot internally and we talked a lot about a way of stitching together external GraphQL APIs with Gatsby's internal GraphQL schema so you could use your external schema natively within Gatsby but then easily make it live on the client.

2423 is live. Thanks for your help and consideration. It's exciting for a novice like me to have any input into such an incredible project!

Hey, closing out old issues. Please re-open if you have additional questions, thanks!

Was this page helpful?
0 / 5 - 0 ratings

Related issues

3CordGuy picture 3CordGuy  路  3Comments

kalinchernev picture kalinchernev  路  3Comments

benstr picture benstr  路  3Comments

totsteps picture totsteps  路  3Comments

KyleAMathews picture KyleAMathews  路  3Comments