Architecting TravelFighter’s Frontend

Franco Morinigo
TravelFighter
Published in
7 min readSep 22, 2017

Disclaimer 1: I really consider myself pretty junior at frontend, indeed I’m a backend guy so I specially wanted to architect the frontend part, to learn more about new stacks and (maybe) understand better the frontend community.
So if you have any idea, suggestion, correction, etc, please let me know about it!

Infrastructure

I decided to implement each part of the site’s flow in a separate frontend application because of the following reasons:

  • To try different frameworks or refactor the implementation of the chosen one in every new app.
  • To keep at the minimum the codebase of each app.
  • Resiliency and fast deploys.

Also, if we used separate apps instead of an SPA we wouldn’t have to sacrifice the user experience (at least, not a lot), so it looked like a good approach at that moment.

Then, I divided the site in the following frontend apps:

  • Home
  • Flights Results
  • Checkout Redirection

And every frontend app looked like this:

We use AWS for all our infrastructure and will share more details about it in future posts!

Frontend Framework

At the time of starting the frontend development I didn’t have experience with any recent frontend framework or library, the last time I had coded a frontend app was simply jQuery and closures.
I started to read the differences between the two major frameworks at the time: Angular 2 and React + Redux. The most common conclusion of experienced people that I read was “You could use any of them and you’ll probably achieve the same goals, but Angular 2 is much more opinionated”.
With that in mind I chose Angular 2 at the beginning, after all it was one of the main frameworks and it had a lot of choices made, a.k.a. less “tough” decisions to make with my poor experience in frontend.

Then, I saw a couple of tutorials and started to code but… not long after that, I found multiple problems:

  • The framework changed almost every week.
  • The footprint was huge.
  • The hot reloading in the browser was far from smooth.
  • I couldn’t find some basic components in the open source community (e.g. a date range picker).
  • The high coupling in my component’s logic with the implementation of the framework. e.g. specific code for AOT and JIT compilers.
  • Etc, etc, etc.

At that time, I gave a try to React and it was really glorious, indeed I could make an entire post with very good reasons to choose React as your frontend framework.

Now, all the TF’s frontend apps are basically React + Redux + Webpack and that’s it. No more gulp or browsersync.

After trying both, Angular 2 and React, I will resume the main difference I’ve found with this:

On a scale of difficulty from 1 to 10, where 1 is to buy a coffee in Starbucks and 10 is to organize Donald Trump’s murder: coding a React app is a 3 and coding an Angular 2 app is a 6.

Disclaimer 2: I chose the frontend stack at around mid 2016. I know that Angular has made some improvements since then, but even today I would choose React again!

Speed

We decided to focus our site in the increasing mobile traffic, but we are in Argentina and the speed of mobile networks is not the best. The coverage of the LTE network is very limited and the 3g network performance is very poor.
Then, in order to give the user a better experience we needed to make a really fast site and here are some of the things we did:

HTTP2

Both ALB (AWS load balancer) and Cloudfront (AWS CDN) works with HTTP2, so it was a straightforward to implement this. I’m not going to make a full review of HTTP2 in this post but by simply using HTTP2 we have the following features:

  • A high number of concurrent connections for each domain (this is very important for static content).
  • Compressed headers (this is very important for cookies).

Compress Everything

You could do this with Nginx very easily. Also, you could make very tricky improvements using the pagespeed module. I did a very basic docker image of pagespeed to give it a try but, after all, I didn’t use any filter because it didn’t give us any performance improve and I saw it like a dangerous coupling.

Squeeze Webpack Features

Our app code was changing every day but not the libraries we used. I divided the app code of the 3rd party dependencies in two different bundles. In this way, we took advantage of the browser cache because the only new asset with every deploy was de app.js bundle.

Other features of webpack I used were:

  • Version every asset with a checksum.
  • Filter unnecessary resources like all the momentJs locales we didn’t use.
  • Minify the js code of each bundle.
  • Use css modules keeping crystal clear sass code for each component.

To test many of these features, I used the BundleAnalyzerPlugin which gave me an idea of the final footprint of each js bundle and its composition.

CDN

I decided to serve all the static content through Cloudfront. With the edge location servers, we reduced 75% of the average latency. We only have infrastructure in us-east-1 region so it’s very important for us to have CDN locations close to the requests’ origins all over the world.
We version all our assets and keep them cached in the browser with only one exception: the html files. However we keep them in the CDN cache using the http header cache-control:s-maxage=2592000 that indicates that the asset could be stored in shared caches like a proxy or a CDN.
In this way, we keep a low response time even in the first resource ever served, the home page html. One thing that it’s needed to achieve this is to invalidate the cache of the html files served for a specific app when we make a new deploy of that app.

We use Jenkins pipeline for the CI of each app.

One constraint of Cloudfront is that, at the time of writing this post, it doesn’t support HTTP2 server pushes =(.

Eliminating roundtrips with preconnect

Another thing I’ve optimized is the time to download assets from different domains like the gtm domain or our static.travelfighter.com domain, which has generic resources like logos, backgrounds, favicons, etc.

If you don’t do this, you should! Use a tag in the HEAD of your html like:

<link rel=”preconnect” href=”https://static.travelfighter.com"/>

The browser:

  • Resolves the DNS name.
  • Performs the TCP handshake.
  • Negotiates the TLS tunnel

And that could save you a couple hundreds of milliseconds when you download resources from a new domain.

Monitoring Performance

This is perhaps the most important thing to do when you want to improve your site’s performance and make good decisions. The first tool I used was the webpagetest site and, after a while, I automated the performance monitoring using the sitespeed.io tools, scheduling very simple jobs like this:

You can use the servers of webpagetest all over the world (or in your own servers) to run performance tests of your site, simulating custom connection bandwidths and latencies. Then, you could store the metrics in Graphite and visualize them and make custom alerts in Grafana.

After a lot of tunings, we reached a sweet performance of 1.6 seconds to have our home visually complete with cable connectivity from Buenos Aires and all over the world, spending only a couple of dollars every month. =)

Monitoring Errors in the Browser and Server Side

For both server (nodeJs) and client sides (browsers) we use free tools.
In the server side we use the good old NewRelic APM combined with NewRelic Servers metrics to obtain a lot of insights for the applications, docker containers and hosts.
In the client side we use Sentry that has a lot of integrations with different languages and frameworks. We use the redux-raven-middleware to see every action like a breadcrumb and the entire redux state whenever an error occurs.

For alerts, we use a Slack channel to receive the notifications from both NewRelic and Sentry.

Architecting the frontend was definitely one of the most entertaining things I’ve done in TravelFighter. And, I repeat, I’m a beginner at this, so if you have any idea, suggestion or correction, please let me know about it!

--

--