What it Takes to Render a Complex Web App in Milliseconds
Tags: webperf, meta, cdn, webdev
Published: 2017-08-18 16:55:58

---
title: What it Takes to Render a Complex Web App in Milliseconds
published: true
description: There's a lot going on with the dev.to home page, especially when you are logged in. So how do we get it all to you instantly?
tags: webperformance, meta, cdn, webdev
---

There's a lot going on with the [dev.to](/) home page, especially when you are logged in. We load the top community posts, the top posts related to the tags and users you follow, the number of reactions and comments on each post, info about top tags, the tags you follow, your profile pic, and of course, all the application code related to actions you might take.

_And we do it all really fast._

We believe in the possibility that the web can be virtually instant for all those who use it. Early on, I approached this from the perspective of "we're just serving blog posts, of course it can be instant". But as we've evolved the platform, it has taken a lot more creativity to keep things pretty damn instant.

## So what does it take to render an instant home page?

In order to render the request as quickly as possible to all corners of the globe, the HTML request is served directly from a Fastly CDN node. So, if you are located in New York City, your page will be fetched from New York City; if you are in Tokyo, the request will hit the Tokyo server. If you are in Egypt, your request will come from Dubai. Operating and optimizing a global CDN is outside the scope of my expertise, so in general I think in terms of the abstractions the CDN provides.

![](https://thepracticaldev.s3.amazonaws.com/i/qu464gsxgfuspvzknvqj.png)

An issue with this approach is that we need to maintain a high hit rate of the cached content, but we don't know who you are on initial request. We use headers to understand whether you are logged in or not, and this helps us organize the initial page for you and simplify some of our logic; but, of course, it would not be reasonable to cache every unique logged in user. So we make secondary ajax requests in order to serve custom data. When you see your profile picture or information on the page related to _you_, it was loaded after initial page load. Sometimes this is laggy, and we're always trying to work it out, but the typical experience is solid.

![](https://thepracticaldev.s3.amazonaws.com/i/31lpuv2mp2737392qp27.png)

In order to optimize your experience when we can, we store some of your information locally when available. So your profile picture in the nav bar and on the home page, and the tags you follow, and some other info, is stored in your browser so it's rendered virtually instantly regardless of network conditions. We still make the request to the origin server every time to double check and ensure the info is fresh. This approach makes the typical request a lot faster than if we did not take this approach.

As I have [written about in the past](https://dev.to/ben/making-devto-insanely-fast), a big part of eliminating rendering latency is inlining styles and making all JavaScript async. This has been a big part of delivering a great experience. This decision was made a while ago and has been great.

We used to take that approach on each page, but we've since evolved our approach.

## Internal navigation

While it's been a great choice to inline styles for ensuring incoming traffic gets a great experience, it's not optimal to send this data over with every instance of internal navigation. We use a form of XHR-based internal navigation similar to [Turbolinks](https://github.com/turbolinks/turbolinks), but smaller and more customized. This gives the experience of an extremely performant single-page-application, but most of the logic gets to stay on the server, which I think is a really big win in terms of keeping the code clean and maintainable.

So we only inline styles on requests that are not triggered by internal navigation.  To take it a step further, we also stopped sending over the nav bar or the footer. You only get the meat of the page. And yet the backend developer doesn't have to stress about this choice. They only have to know that we can't have different versions of the nav bar on different pages (without invoking special code on the client to do this). This means that internal page request typically measure in about 4-8kb. There are outliers here, like pages with _a lot of comments_, but we'll iron that out as well.

## Progressive Web Apps

Integrating some newer APIs that will improve these aspects even further is next on the docket. Our caching-first architecture is primed to take advantage of these features and I am very excited. Our initial efforts have been to improve performance for everyone, regardless of browser features, and it has set us up well to make good use of the bleeding edge APIs which are growing in browser coverage and becoming more mature overall.

## In conclusion

I tried running some rendering tests comparing [dev.to](/) and [twitter.com](https://twitter.com) and Twitter just never stopped rendering. I waited for seven minutes and the test wouldn't stop. If this post is any indication, I do not have a lot of patience for waiting around. So I'm just going to go ahead and hit publish now. I'll be forced to extrapolate the evidence I do have and assume our site is _infinitely_ faster.

Of course I know that Twitter's product is massively more complex and they serve orders of magnitude more traffic than we ever would. So I'm not trying to boast on any engineering levels, but for any individual user, it doesn't really matter how much overall traffic they serve. The individual will still measure user experience apples to apples. User experience is everything and performance is the biggest factor in user experience in the browser.

Happy coding âœŒï¸