Apollo and GraphQL Make a Great Impression
React server-side rendering has been in an odd place since React was released. It supports the basics, but as soon as you start making async requests inside components you essentially only have two options:
- Ignore the request during the server render. Make request only once the client app loads.
- Fetch needed data before the server render. Use Redux to propagate data to components for the first render.
If the majority of the server rendered content is not retrieved async-ly, the first option might be okay. A small portion of the page could display a loading indicator by default and be replaced with the new content after loading the needed data client-side. But if a majority of the page requires async data, server-side rendering a largely empty page is not ideal.
Fetching the data before the server render and using Redux for data propagation solves this problem, but is it not without issues. It's difficult for the server-side rendering logic to not become coupled with the structure of Redux store on the client. This isn't a deal-breaker for applications that are free to make changes to both the client and server apps, but if you need the server logic to be reusable across different apps it would not be feasible. You're also locked into using Redux anywhere you need async data rendered server-side.
React Suspense looks like it'll be a native React solution for all these issues, and it's supposed be released by the end of the year, but what is the best option until then?
Enter Apollo
Apollo is made up of several libraries that work with GraphQL endpoints. GraphQL services allows a client application to request the exact form of data it needs using its querying language. It has some large benefits over REST services (fewer requests, smaller responses), and because we're able to request the specific fields we need, Apollo makes it simple to write declarative components that are easy to render on the server.
Here's the full render
function for a component that retrieves the most recent post on the homepage:
// inside render of component
return (
// Request data we need using Apollo component
<Query
query={gql`
{
recentPosts(amount: 1) {
title
lookup
timestamp
body
}
}
`}
>
{({ loading, error, data }) => {
// render nothing if it's not available
if (error || loading) {
return null;
}
let post = data.recentPosts[0];
let { title, timestamp, body, lookup } = post;
return (
<PostShow
title={title}
timestamp={timestamp}
body={body}
lookup={lookup}
isTitleALink={true}
/>
);
}}
</Query>
);
All the data fetching logic is contained to the component. We don't need a reducer or an action creator--we request the data where we need it and we render it.
If we didn't need the body
, or didn't need the timestamp
, we could exclude that field from the query and not pull it down. If we wanted to show a spinner or error page depending on the state of the request, we could do that too.
Rendering Apollo on the Server
There's a bit of boilerplate to get everything setup, but as long as the both the client and server are using the same GraphQL schema, any request made in the component tree will be correctly rendered server-side.
// ...
async function serverRender(req) {
// ...
// setup the necessary configuration for the Apollo client
const apolloClient = new ApolloClient({
ssrMode: true,
link: new SchemaLink({ schema }),
cache: new InMemoryCache(),
});
// wrap application in Apollo provider
let context = {};
let ProvidedApp = (
<ApolloProvider client={apolloClient}>
<StaticRouter location={req.url} context={context}>
<App />
</StaticRouter>
</ApolloProvider>
);
// parse components for GraphQL requests to make
// wait for requests to finish and propagate request data to components
await getDataFromTree(ProvidedApp);
// render and save state for client hydration
let html = renderStylesToString(ReactDOMServer.renderToString(ProvidedApp));
let preloadedState = apolloClient.extract();
// pass pre-loaded state to client via 'window'
let finalHtml = buildTemplate({
html,
preloadedState,
});
// ...
}
Final Thoughts
I'm looking forward to using React Suspense, but in the meanwhile, Apollo and GraphQL provide a server-side rendering solution that is tough to beat. There's a few too many modules needed for my liking (apollo-client, apollo-server, react-apollo, apollo-server-express, and there's still a few others), but once it's setup...it just kinda works.
For more information on GraphQL and Apollo, make sure to check out the docs on the Apollo site. GraphQL services can wrap REST services, so it is not necessary to completely rewrite an existing REST service to try it out.