Dealing with Asynchronous APIs in Server-rendered React

If you’ve ever made a basic React app page, it probably suffered from poor SEO and performance issues on slower devices. You can add back traditional server-side rendering of web pages, typically with NodeJS, but this isn’t a straightforward process, especially with asynchronous APIs.

The two main benefits you get from rendering your code on the server are:

  • increased performance in load times
  • improving the flexibility of your SEO.

Remember that Google does wait for your JavaScript to load, so simple things like title content will change without issue. (I can’t speak for other search engines, though, or how reliable that is.)

In this post, I’ll discuss getting data from asynchronous APIs when using server-rendered React code. React code has the entire structure of the app built in JavaScript. This means that, unlike traditional MVC patterns with a controller, you don’t know what data you need until the app is rendered. With a framework like Create React App, you can quickly create a working app of very high quality, but it requires you to handle rendering only on the client. There’s a performance issue with this, as well as an SEO/data issue, where traditional templating engines you can alter the head as you see fit.

The Problem

React renders synchronously for the most part, so if you don’t have the data, you render a loading screen and wait for the data to come. This doesn’t work so well from the server, because you don’t know what you need until you’ve rendered, or you know what you need but you’ve already rendered.

Check out this stock-standard render method:

ReactDOM.render(
  <provider store={store}>
    <browserrouter>
      <app></app>
    </browserrouter>
  </provider>
, document.getElementById('root')
)

Issues:

  1. It’s a DOM render looking for a root element. This doesn’t exist on my server, so we have to separate that.
  2. We don’t have access to anything outside our main root element. We can’t set Facebook tags, title, description, various SEO tags, and we don’t have control over the rest of the DOM outside the element, especially the head.
  3. We’re providing some state, but the server and client have different states. We need to consider how to handle that state (in this case, Redux).

So I’ve used two libraries here, and they’re pretty popular, so hopefully it carries over to the other libraries you’re using.

Redux: Storing state where your server and client are synced is a nightmare issue. It’s very costly, and usually leads to complex bugs. On the server side, ideally, you don’t want to do anything with Redux apart from just enough to get things working and rendering correctly. (You can still use it as normal; just set enough of the state to look like the client.) If you want to try, check out the various distributed systems guides as a starting point.

React-Router: FYI, this is the v4 version, which is what is installed by default, but it’s significantly different if you’ve got an older existing project. You need to make sure you handle your routing server side and client side and with v4 — and it’s very good at this.

After all, what if you need to make a database call? Suddenly this becomes a big issue, because it’s async and it’s inside your component. Of course, this isn’t a new issue: check it out on the official React repo.

You have to render in order to determine what dependencies you need — which need to be determined at runtime — and to fetch those dependencies before serving to your client.

Existing Solutions

Below, I’ll review the solutions that are currently on offer to solve this problem.

Next.js

Before we go anywhere, if you want production, server-side-rendered React code or universal app, Next.js] is where you want to go. It works, it’s clean, and it’s got Zeit backing it.

However, it’s opinionated, you have to use their toolchain, and the way they handle async data loading isn’t necessarily that flexible.

Check out this direct copy from the Next.js repo documentation:

import React from 'react'
export default class extends React.Component {
  static async getInitialProps ({ req }) {
    return req
      ? { userAgent: req.headers['user-agent'] }
      : { userAgent: navigator.userAgent }
  }
  render () {
    return <div>
      Hello World {this.props.userAgent}
    </div>
  }
}

getInitialProps is the key there, which returns a promise that resolves to an object that populates props, and only on a page. What’s great is that’s just built in to their toolchain: add it and it works, no work required!

So how do you get database data? You make an api call. You don’t want to? Well, that’s too bad. (Okay, so you can add custom things, but you have to fully implement it yourself.) If you think about this, though, it’s a very reasonable and, generally speaking, good practice, because otherwise, your client would still be making the same API call, and latency on your server is virtually negligible.

You’re also limited in what you have access to — pretty much just the request object; and again, this seems like good practice, because you don’t have access to your state, which would be different on your server versus client anyways. Oh, and in case you didn’t catch it before, it only works on top-level page components.

Redux Connect

Redux Connect is a very opinionated server-side renderer, with a decent philosophy, but if you don’t use all the tools they describe, this might not be for you. There’s a lot to this package, but it’s so complex and not yet upgraded to React Router v4. There’s a lot of setup to this, but let’s take the most important part, just to learn some lessons:

// 1. Connect your data, similar to react-redux @connect
@asyncConnect([{
  key: 'lunch',
  promise: ({ params, helpers }) => Promise.resolve({ id: 1, name: 'Borsch' })
}])
class App extends React.Component {
  render() {
    // 2. access data as props
    const lunch = this.props.lunch
    return (
      <div>{lunch.name}</div>
    )
  }
}

Decorators aren’t standard in JavaScript. They’re Stage 2 at the time of writing, so use at your discretion. It’s just another way of adding higher-order components. The idea is pretty simple: the key is for what to pass to your props, and then you have a list of promises, which resolve and are passed in. This seems pretty good. Perhaps an alternative is simply this:

@asyncConnect([{
  lunch: ({ params, helpers }) => Promise.resolve({ id: 1, name: 'Borsch' })
}])

That seems doable with JavaScript without too many issues.

react-frontload

The react-frontload repo doesn’t have a lot of documentation, or explanation, but perhaps the best understanding I could get was from the tests (such as this one)
and just reading the source code. When something is mounted, it’s added to a promise queue, and when that resolves, it’s served. What it does is pretty good, though it’s hard to recommend something that’s not well documented, maintained or used:

const App = () => (
  <frontload isServer >
    <component1 entityId='1' store={store}></component1>
  </frontload>
)

return frontloadServerRender(() => (
  render(<app></app>)
)).then((serverRenderedMarkup) => {
  console.log(serverRenderedMarkup)
})

Continue reading %Dealing with Asynchronous APIs in Server-rendered React%


Source: Sitepoint