My Busy.org November and December report: Server-Side Rendering, AMP, and a lot more

This post contains a little bit of story of how I handled Server-Side Rendering and some tips that I wish I knew before starting to work on this. If you are interested in implementing SSR (Server-Side Rendering) you might want to read it, if not you can just ignore that. A condensed list of stuff I did is at the bottom of this post.

Server-Side Rendering

As probably most people know, we use React (with Redux and other great libraries) to build website behind Busy.org. React is a great framework for building client-side applications in JavaScript (and it's pretty common in our ecosystem - Steemit and Utopian are using it as well). Problem with client-side applications is that they are rendered entirely in the browser, so they perform worse at SEO (Search Engine Optimization) than traditional websites. Computers don't tend to run fully-fledged browsers just to figure out how does it look like for users and just read it as rather an empty HTML file. This was a bummer for us. Our goal is to build a platform for content creators, that they can use to share their work. However, sharing was always a problematic aspect of Busy - when you share a link to your Busy article on some social platform (Twitter, Facebook, Telegram, Messenger, Discord et.al), instead of a preview of your work you would get just generic message that most likely won't bring a wide audience.
One of our priorities lately was to fix this issue, so users could share their content without any obstacles.

To make it a little bit easier to understand you can check those awesome illustrations from equally awesome article from Walmart.

SSR

image.png

CSR

image.png

Just a few years ago the only solution was to use some sort of service that would visit your website periodically, render HTML code and serve that instead of your normal website if you detect that it's visited by some kind of bot (Google, Facebook etc.). Luckily, with recent work made by React community, React can be used in non-browser environments such as Node.js.

One challenge of running React on the server is that if you don't handle some kind of error properly, you don't only crash the website in user's browser, but the most likely entire server, effectively blocking thousands of users from accessing your website. For this reason, we had to prepare our code to handle potential unhandled errors and find bottlenecks.

One of such bottlenecks that got fixed was the way, we pared numbers while sorting votes. Previously, we used parseInt to parse rshares to Number, but it's really slow compared to converting it implicitly. After the change instead of sorting votes for whopping 1.3 seconds every time a new page was loaded in the feed it only took relatively small 70ms. Not fixing this bottleneck could potentially increase response time by 1 second, so fixing this way an obvious choice.

Another thing that is worth doing is to upgrade React to the latest version - React 16. It not only has way better support for SSR, but it works way faster in the browser as well. If you want to read more about what has changed in React 16 you can read this article on React blog.

Upgrading React should be fairly easy as long as you use frequently updated dependencies. Built-in React.PropTypes was removed and now you have to use separate prop-types package instead. We already were using it in our codebase, however, some of our libraries were not updated recently, so we had to either use something else or fork it and update it ourselves, which is what I effectively did for ReduxInfiniteScroll.

After those preparations, we were ready to start implementing the logic on the server. Rendering React app on the server is simple thanks to renderToString method from ReactDOM. It works almost the same way standard render method works. You potentially could have functional SSR implemented in few lines of code, but if you are using dynamic data (e.g. loaded from API) it just won't be there. The reason why is that renderToString doesn't wait for your Promises to fulfil. It just renders document tree and returns it. If you want to use asynchronously loaded content - you have to handle this yourself.
Few things to note:

  1. componentWillMount is the only lifecycle method that would be called on the server. If you are loading data here you can't really access it. If you do - it will just use your server resources for nothing.
  2. If you want to load some content on both server and client you should do it in componentDidMount (it get's only called on the client), and in some static method that can be called on the server.
  3. You probably want to have some kind of global store to save your state. You could use global variables for this, but Redux makes everything way easier.

Almost everything we had inside componentWillMount could just be moved to componentDidMount. If you load some data asynchronously inside componentWillMount there is no way that it will load before component gets rendered.

If we want some component to load data asynchronously, we add static fetchData method that does just that. Those methods return promises, so we can wait for it to complete using Promise.all - it either resolves when every promise is resolved or rejects when one of them is rejected. When Promise.all resolves we can feed our store that got created when loading data to our React app and render it to a string using renderToString.

The last step is just to turn that string into the full website, add additional markup and pass an entire store, so it can be retrieved on client-side so you don't notice any inconsistencies.

If you want to see PR that added SSR click here. Keep in mind it's quite big because we decided to split our app into three folders: client, common, and server.

List of all contributions in November and December:

I think I should create script for generating those ^^

This was supposed to be monthly post, but I was short on time (I'm currently full-time student). When I got some free time it was already 15th, so I decided to create one post for two months.

Thanks for everyone in a team and for every Busy user that helps make us something really big. I appreciate your help!



Posted on Utopian.io - Rewarding Open Source Contributors

H2
H3
H4
3 columns
2 columns
1 column
13 Comments