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

in #utopian-io7 years ago (edited)

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.

<h3>Server-Side Rendering <p dir="auto">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.<br /> One of our priorities lately was to fix this issue, so users could share their content without any obstacles. <p dir="auto">To make it a little bit easier to understand you can check those awesome illustrations from equally <a href="https://medium.com/walmartlabs/the-benefits-of-server-side-rendering-over-client-side-rendering-5d07ff2cefe8" target="_blank" rel="noreferrer noopener" title="This link will take you away from hive.blog" class="external_link">awesome article from Walmart. <h4>SSR <p dir="auto"><img src="https://images.hive.blog/768x0/https://res.cloudinary.com/hpiynhbhq/image/upload/v1514910135/vlcoggsdifuobfbgeeu7.png" alt="image.png" srcset="https://images.hive.blog/768x0/https://res.cloudinary.com/hpiynhbhq/image/upload/v1514910135/vlcoggsdifuobfbgeeu7.png 1x, https://images.hive.blog/1536x0/https://res.cloudinary.com/hpiynhbhq/image/upload/v1514910135/vlcoggsdifuobfbgeeu7.png 2x" /> <h4>CSR <p dir="auto"><img src="https://images.hive.blog/768x0/https://res.cloudinary.com/hpiynhbhq/image/upload/v1514910167/cjda1syf2pvszbnsyr9j.png" alt="image.png" srcset="https://images.hive.blog/768x0/https://res.cloudinary.com/hpiynhbhq/image/upload/v1514910167/cjda1syf2pvszbnsyr9j.png 1x, https://images.hive.blog/1536x0/https://res.cloudinary.com/hpiynhbhq/image/upload/v1514910167/cjda1syf2pvszbnsyr9j.png 2x" /> <p dir="auto">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. <p dir="auto">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. <p dir="auto">One of such bottlenecks that got fixed was the way, we pared numbers while sorting votes. Previously, we used <code>parseInt to parse <code>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 <a href="https://github.com/busyorg/busy/pull/1025" target="_blank" rel="noreferrer noopener" title="This link will take you away from hive.blog" class="external_link">fixing this way an obvious choice. <p dir="auto">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 <a href="https://reactjs.org/blog/2017/09/26/react-v16.0.html" target="_blank" rel="noreferrer noopener" title="This link will take you away from hive.blog" class="external_link">this article on React blog. <p dir="auto">Upgrading React should be fairly easy as long as you use frequently updated dependencies. Built-in <code>React.PropTypes was removed and now you have to use separate <code>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 <a href="https://github.com/busyorg/busy/pull/1068" target="_blank" rel="noreferrer noopener" title="This link will take you away from hive.blog" class="external_link">effectively did for ReduxInfiniteScroll. <p dir="auto">After those preparations, we were ready to start implementing the logic on the server. Rendering React app on the server is simple thanks to <code>renderToString method from <code>ReactDOM. It works almost the same way standard <code>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 <code>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.<br /> Few things to note: <ol> <li><code>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. <li>If you want to load some content on both server and client you should do it in <code>componentDidMount (it get's only called on the client), and in some static method that can be called on the server. <li>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. <p dir="auto">Almost everything we had inside <code>componentWillMount could just be moved to <code>componentDidMount. If you load some data asynchronously inside <code>componentWillMount <strong>there is no way that it will load before component gets rendered. <p dir="auto">If we want some component to load data asynchronously, we add static <code>fetchData method that does just that. Those methods return promises, so we can wait for it to complete using <code>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 <code>renderToString. <p dir="auto">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. <p dir="auto">If you want to see PR that added SSR <a href="https://github.com/busyorg/busy/pull/1089" target="_blank" rel="noreferrer noopener" title="This link will take you away from hive.blog" class="external_link">click here. Keep in mind it's quite big because we decided to split our app into three folders: <code>client, <code>common, and <code>server. <p dir="auto">List of all contributions in November and December: <ul> <li><a href="https://github.com/busyorg/busy/pull/989" target="_blank" rel="noreferrer noopener" title="This link will take you away from hive.blog" class="external_link">Update TopicSelector styles <li><a href="https://github.com/busyorg/busy/pull/992" target="_blank" rel="noreferrer noopener" title="This link will take you away from hive.blog" class="external_link">Remove storybooks <li><a href="https://github.com/busyorg/busy/pull/993" target="_blank" rel="noreferrer noopener" title="This link will take you away from hive.blog" class="external_link">Add babel-polyfill <li><a href="https://github.com/busyorg/busy/pull/1001" target="_blank" rel="noreferrer noopener" title="This link will take you away from hive.blog" class="external_link">Use production and development index files <li><a href="https://github.com/busyorg/busy/pull/1002" target="_blank" rel="noreferrer noopener" title="This link will take you away from hive.blog" class="external_link">Remove not used images from assets <li><a href="https://github.com/busyorg/busy/pull/1007" target="_blank" rel="noreferrer noopener" title="This link will take you away from hive.blog" class="external_link">Merge previous jsonMetadata when saving post <li><a href="https://github.com/busyorg/busy/pull/1008" target="_blank" rel="noreferrer noopener" title="This link will take you away from hive.blog" class="external_link">Add voting slider to comments <li><a href="https://github.com/busyorg/busy/pull/1015" target="_blank" rel="noreferrer noopener" title="This link will take you away from hive.blog" class="external_link">Display image preview for short posts with image <li><a href="https://github.com/busyorg/busy/pull/1017" target="_blank" rel="noreferrer noopener" title="This link will take you away from hive.blog" class="external_link">Image upload validation <li><a href="https://github.com/busyorg/busy/pull/1018" target="_blank" rel="noreferrer noopener" title="This link will take you away from hive.blog" class="external_link">Fix StoryFull actions: save and edit <li><a href="https://github.com/busyorg/busy/pull/1020" target="_blank" rel="noreferrer noopener" title="This link will take you away from hive.blog" class="external_link">Handle deleted posts <li><a href="https://github.com/busyorg/busy/pull/1025" target="_blank" rel="noreferrer noopener" title="This link will take you away from hive.blog" class="external_link">Improve votes sorting <li><a href="https://github.com/busyorg/busy/pull/1065" target="_blank" rel="noreferrer noopener" title="This link will take you away from hive.blog" class="external_link">Use proper steps in Dockerfile build <li><a href="https://github.com/busyorg/busy/pull/1068" target="_blank" rel="noreferrer noopener" title="This link will take you away from hive.blog" class="external_link">Update Busy to React 16.1.0 <li><a href="https://github.com/busyorg/busy/pull/1069" target="_blank" rel="noreferrer noopener" title="This link will take you away from hive.blog" class="external_link">Remove parseInt from sortVotes (#1025) <li><a href="https://github.com/busyorg/busy/pull/1089" target="_blank" rel="noreferrer noopener" title="This link will take you away from hive.blog" class="external_link">Server-Side Rendering <li><a href="https://github.com/busyorg/busy/pull/1122" target="_blank" rel="noreferrer noopener" title="This link will take you away from hive.blog" class="external_link">Add Sentry's Raven for error handling <li><a href="https://github.com/busyorg/busy/pull/1123" target="_blank" rel="noreferrer noopener" title="This link will take you away from hive.blog" class="external_link">Release 2.1.0 <li><a href="https://github.com/busyorg/busy/pull/1124" target="_blank" rel="noreferrer noopener" title="This link will take you away from hive.blog" class="external_link">Update package version <li><a href="https://github.com/busyorg/busy/pull/1125" target="_blank" rel="noreferrer noopener" title="This link will take you away from hive.blog" class="external_link">Add HeroBanner <li><a href="https://github.com/busyorg/busy/pull/1130" target="_blank" rel="noreferrer noopener" title="This link will take you away from hive.blog" class="external_link">Respect stickPosition in Affix calculations <li><a href="https://github.com/busyorg/busy/pull/1139" target="_blank" rel="noreferrer noopener" title="This link will take you away from hive.blog" class="external_link">Don't load avatars on server <li><a href="https://github.com/busyorg/busy/pull/1145" target="_blank" rel="noreferrer noopener" title="This link will take you away from hive.blog" class="external_link">Update error parsing <li><a href="https://github.com/busyorg/busy/pull/1144" target="_blank" rel="noreferrer noopener" title="This link will take you away from hive.blog" class="external_link">Add feed fetched property <li><a href="https://github.com/busyorg/busy/pull/1151" target="_blank" rel="noreferrer noopener" title="This link will take you away from hive.blog" class="external_link">Check if LOGIN is a refresh <li><a href="https://github.com/busyorg/busy/pull/1161" target="_blank" rel="noreferrer noopener" title="This link will take you away from hive.blog" class="external_link">Try to load real avatar by default <li><a href="https://github.com/busyorg/busy/pull/1162" target="_blank" rel="noreferrer noopener" title="This link will take you away from hive.blog" class="external_link">Log initial page view <li><a href="https://github.com/busyorg/busy/pull/1168" target="_blank" rel="noreferrer noopener" title="This link will take you away from hive.blog" class="external_link">use targetUsername instead of username in userActions <li><a href="https://github.com/busyorg/busy/pull/1171" target="_blank" rel="noreferrer noopener" title="This link will take you away from hive.blog" class="external_link">Migrate to new image provider <li><a href="https://github.com/busyorg/busy/pull/1172" target="_blank" rel="noreferrer noopener" title="This link will take you away from hive.blog" class="external_link">Update error parsing (#1143) <li><a href="https://github.com/busyorg/busy/pull/1176" target="_blank" rel="noreferrer noopener" title="This link will take you away from hive.blog" class="external_link">Log initial page view (#1162) <li><a href="https://github.com/busyorg/busy/pull/1180" target="_blank" rel="noreferrer noopener" title="This link will take you away from hive.blog" class="external_link">Migrate from Steemjs to LightRPC <li><a href="https://github.com/busyorg/busy/pull/1186" target="_blank" rel="noreferrer noopener" title="This link will take you away from hive.blog" class="external_link">Hotfix translations <li><a href="https://github.com/busyorg/busy/pull/1199" target="_blank" rel="noreferrer noopener" title="This link will take you away from hive.blog" class="external_link">Server-side rendering for feed and proper error handling <li><a href="https://github.com/busyorg/busy/pull/1204" target="_blank" rel="noreferrer noopener" title="This link will take you away from hive.blog" class="external_link">Fix all feed <li><a href="https://github.com/busyorg/busy/pull/1205" target="_blank" rel="noreferrer noopener" title="This link will take you away from hive.blog" class="external_link">Add AMP <li><a href="https://github.com/busyorg/busy/pull/1208" target="_blank" rel="noreferrer noopener" title="This link will take you away from hive.blog" class="external_link">Make posts loading state scoped to certain post <li><a href="https://github.com/busyorg/busy/pull/1228" target="_blank" rel="noreferrer noopener" title="This link will take you away from hive.blog" class="external_link">Disable curation rewards on comments <li><a href="https://github.com/busyorg/busy/pull/1229" target="_blank" rel="noreferrer noopener" title="This link will take you away from hive.blog" class="external_link">Update posts state from feed <li><a href="https://github.com/busyorg/busy/pull/1231" target="_blank" rel="noreferrer noopener" title="This link will take you away from hive.blog" class="external_link">Use yarn <li><a href="https://github.com/busyorg/busy/pull/1233" target="_blank" rel="noreferrer noopener" title="This link will take you away from hive.blog" class="external_link">Save reward and upvote settings in metadata <li><a href="https://github.com/busyorg/busy/pull/1237" target="_blank" rel="noreferrer noopener" title="This link will take you away from hive.blog" class="external_link">Load translations asynchronously <li><a href="https://github.com/busyorg/busy/pull/1238" target="_blank" rel="noreferrer noopener" title="This link will take you away from hive.blog" class="external_link">Release 2.2 <li><a href="https://github.com/busyorg/busy/pull/1240" target="_blank" rel="noreferrer noopener" title="This link will take you away from hive.blog" class="external_link">Don't update post state after liking. Fixes #1222 <li><a href="https://github.com/busyorg/busy/pull/1241" target="_blank" rel="noreferrer noopener" title="This link will take you away from hive.blog" class="external_link">Don't update post state after liking. Fixes #1222 <li><a href="https://github.com/busyorg/busy/pull/1242" target="_blank" rel="noreferrer noopener" title="This link will take you away from hive.blog" class="external_link">Update font style <li><a href="https://github.com/busyorg/busy/pull/1244" target="_blank" rel="noreferrer noopener" title="This link will take you away from hive.blog" class="external_link">Increase Feed load threshold to 1500 <li><a href="https://github.com/busyorg/busy/pull/1245" target="_blank" rel="noreferrer noopener" title="This link will take you away from hive.blog" class="external_link">Display text-image if there is image in metadata. Fixes #1224 <li><a href="https://github.com/busyorg/busy/pull/1246" target="_blank" rel="noreferrer noopener" title="This link will take you away from hive.blog" class="external_link">Include rate in vote value calculation <li><a href="https://github.com/busyorg/busy/pull/1247" target="_blank" rel="noreferrer noopener" title="This link will take you away from hive.blog" class="external_link">Add noindex, nofollow if not running at busy.org <li><a href="https://github.com/busyorg/busy/pull/1255" target="_blank" rel="noreferrer noopener" title="This link will take you away from hive.blog" class="external_link">Add development scripts <li><a href="https://github.com/busyorg/busy/pull/1261" target="_blank" rel="noreferrer noopener" title="This link will take you away from hive.blog" class="external_link">Don't index feed pages <li><a href="https://github.com/busyorg/busy/pull/1233" target="_blank" rel="noreferrer noopener" title="This link will take you away from hive.blog" class="external_link">Don't broadcast comment_options when updating <li><a href="https://github.com/busyorg/busy/pull/1264" target="_blank" rel="noreferrer noopener" title="This link will take you away from hive.blog" class="external_link">Update username style <li><a href="https://github.com/busyorg/busy/pull/1265" target="_blank" rel="noreferrer noopener" title="This link will take you away from hive.blog" class="external_link">Support iframe for AMP pages <li><a href="https://github.com/busyorg/busy/pull/1266" target="_blank" rel="noreferrer noopener" title="This link will take you away from hive.blog" class="external_link">AMP error handling <li><a href="https://github.com/busyorg/busy/pull/1267" target="_blank" rel="noreferrer noopener" title="This link will take you away from hive.blog" class="external_link">AMP error handling <li><a href="https://github.com/busyorg/busy/pull/1268" target="_blank" rel="noreferrer noopener" title="This link will take you away from hive.blog" class="external_link">Improve server building process <li><a href="https://github.com/busyorg/busy/pull/1269" target="_blank" rel="noreferrer noopener" title="This link will take you away from hive.blog" class="external_link">Remove trailing comma <li><a href="https://github.com/busyorg/busy/pull/1275" target="_blank" rel="noreferrer noopener" title="This link will take you away from hive.blog" class="external_link">Add vote value to user profile <li><a href="https://github.com/busyorg/busy/pull/1276" target="_blank" rel="noreferrer noopener" title="This link will take you away from hive.blog" class="external_link">Updates styles <li><a href="https://github.com/busyorg/busy/pull/1277" target="_blank" rel="noreferrer noopener" title="This link will take you away from hive.blog" class="external_link">Reformat codebase <li><a href="https://github.com/busyorg/busy/pull/1280" target="_blank" rel="noreferrer noopener" title="This link will take you away from hive.blog" class="external_link">Profile settings <p dir="auto">I think I should create script for generating those ^^ <p dir="auto">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. <p dir="auto">Thanks for everyone in a team and for every Busy user that helps make us something really big. I appreciate your help! <p dir="auto"><br /><hr /><em>Posted on <a href="https://utopian.io/utopian-io/@sekhmet/my-busy-org-november-and-december-report-server-side-rendering-amp-and-a-lot-more" target="_blank" rel="noreferrer noopener" title="This link will take you away from hive.blog" class="external_link">Utopian.io - Rewarding Open Source Contributors<hr /><p>
Sort:  

Which CSS strategy is busy.org using? I have some issue with SSR when I use styled-component alongside with React, the css just won't show up.

We use .less files (due to the fact we use antd and we have to use Less to modify it), and just use webpack import's to import them. After we build our app, we have .html files that includes all stylesheets. This file is then used as a template on the server.

<p dir="auto">If you want to use <code>styled-components on the server you have to wrap your entire application in <code>ServerStyleSheet<span> collector that would collect all of your styles from components. After it's done you can just retrieve normal style tags from it and add it to your page. Take a look here: <a href="https://www.styled-components.com/docs/advanced#server-side-rendering" target="_blank" rel="noreferrer noopener" title="This link will take you away from hive.blog" class="external_link">https://www.styled-components.com/docs/advanced#server-side-rendering

I had been using sass for a while. Transition to styled-component took me sometime to get used to it haha.

I like the way styled-jsx works. It feels like normal CSS but has some great features (scoped to current component, works on SSR).

If you are working on new app and want to have SSR I recommend checking out Next.js.

Currently I am still using Express or Koa for SSR haha.

I am also looking on this jss where Material-UI is using this.

It's incredible to me, that such thing like implicit conversion could speed up our lovely Busy that much!
Have you encounterd any other 'performance hacks' during process of implementing SSR?

I've noticed couple of potential improvements that we could make to make rendering faster. I didn't implement them yet, because it won't make much of a difference. Currently the slowest thing on the server is actually Steem API. It can even take more than a second to fetch data (for example post contents). Rendering our app doesn't take that much compared to that.

We plan to migrate to our own API, so we can fetch data in exact format we need. In addition it should be way faster than Steem API, so after we migrate to our own solution we can work on improving the performance even more.

The most obvious one I notice (and it is worth considering no matter if you are running React server-side or client-side) is to process and render data only on-demand. Take sorting votes as an example. In feed you have 20 posts, sometimes they have more than 10 thousands of votes. Does it make sense to sort them right now, even though user might not even consider taking a look at them? I don't think so. It makes sense to do this when we know this is necessary - when user opens votes list. We can show loading icon for a while and present user a final result.

This is very simple change, but can make a difference. You might have slow-running tasks, but if you don't run them (unnecessarily) every time user visits a website it's not big deal.

Yeah, definitely agreed. 'Caching' things that are uncritical to the current demands of user are is strongly unefficient - in the big picture, obviously.

I imagine that migrating an API to your own is going to be quite a challenge. But getting under consideration all the things you've already done I think you will manage :)

Nonetheless, thank you for answer! It's very satisfying to get answers about the Busy straight from its developer.

There is always a room for improvement. We are constantly working to make Busy and entire platform even better.

Glad you liked it, see you soon 😄

Thanx sekhmet for sharing valueable informations.. Nice post.. Keep it up

Very best tutorial

Thank you for the contribution. It has been approved.

You can contact us on Discord.
[utopian-moderator]

Hey @sekhmet I am @utopian-io. I have just upvoted you!

Achievements

  • You have less than 500 followers. Just gave you a gift to help you succeed!
  • You are generating more rewards than average for this category. Super!;)
  • Seems like you contribute quite often. AMAZING!

Community-Driven Witness!

I am the first and only Steem Community-Driven Witness. Participate on Discord. Lets GROW TOGETHER!

mooncryption-utopian-witness-gif

Up-vote this comment to grow my power and help Open Source contributions like this one. Want to chat? Join me on Discord https://discord.gg/Pc8HG9x