Architecting the new WBEZ.org with Nuxt, Serverless and AWS

Oliver Holmberg
WBEZ Codes
Published in
7 min readMay 27, 2020

--

There’s a lot going on under the hood of the new WBEZ.org

Building a website that supports a digital news brand AND a radio station has a unique set of technical requirements:

  • Speed and performance to meet the needs of discerning users
  • Unlimited scalability to keep the information flowing in the unpredictable ebbs and flows (or the never-ending flow that is COVID-19) of the news cycle
  • Persistent audio streaming while browsing between pages
  • AMP capability to keep our stories visible in Google’s proprietary news delivery framework

Additionally, this website needed to make use of a pre-existing API served from our custom built CMS in order to consume all of our content. The development of that product, however, is not covered here.

Here are the technologies we used to meet these often contradictory requirements and deliver a best in class news website.

Selecting a Software Stack

In selecting a stack for a public radio web application, one requirement jumps out ahead of the rest pretty quickly: Persistent audio playback. The need for a user to listen to streaming or on-demand audio programming uninterrupted whilst browsing the website meanth we were going to be building a single-page application.

Choosing a JavaScript SPA framework for this project was actually the least interesting problem we solved in selecting our overall stack. I had worked in both Angular and React for years, having enjoyed both frameworks for different reasons. Having read some awesome things about the Vue framework, I took a few tutorial courses and loved the modern CLI tools and overall developer experience.

Vue is a JavaScript framework for building single-page web applications

Having provisionally selected a SPA framework, it was time to measure the selection against our technology requirements:

✅ Persistent Audio: Yup, that’s what got us here in the first place.

✅ Scalability: You betcha. Single-page applications can be cheaply and reliably deployed on a myriad of highly available and scalable cloud environments. (more on that later)

🛑 Speed: Single-page applications move all of the processing work that would normally happen on a server to the browser. While they can be quite fast on some devices, modern SPAs can leave users of older mobile devices in the slow lane. That just won’t do.

🛑 AMP Capability: AMP requires that the AMP version of your pages be delivered to the client fully rendered and ready to be cached by their CDN. Vue on its own can’t do this. Dang.

Well, heck. We got too excited and blew half of our technical requirements. Now what?

Through a little research we discovered there are a couple ways to get around some of the shortcomings of SPA framework applications. Two solutions show up frequently:

  1. Static site generators allow you to develop your application — including using a SPA framework — and compile the resulting pages to HTML that can be statically hosted. This provides insanely fast response times, and fully rendered pages that AMP requires and search engines love. However, with tens of thousands of story pages, and new ones being published every day, this was not a practical option for wbez.org.
  2. Universal rendering: “Universal”, as it pertains to SPA frameworks, refers to the application’s ability to render pages both in SSR (server-side rendering) and CSR (client-side rendering) modes. This means that initial page loads to the site will be fully rendered on the server, while all subsequent navigation on the app will be rendered in the browser without the page reloading. Now this sounds promising!

A number of frameworks exist to add this “Universal” rendering capability to SPA frameworks. The popular React JS framework can be used to develop Universal apps by adding a framework called Next.js. For would-be Vue developers such as ourselves, the Nuxt framework was available to meet our Universal rendering requirements.

Nuxt is a JavaScript framework that extends Vue, adding universal rendering and other features.

Nuxt, in addition to solving our rendering requirements, adds a few nice developer experience features. It requires zero configuration of the local development environment. That got us up and coding very quickly.

Selecting the Infrastructure

Undertaking a project of this size with only two software engineers on staff meant one thing for me: We don’t have time for a lot of DevOps configuration, server maintenance, and the like. Our time needed to be spent almost entirely on feature development in order to hit our six-month deadline.

Unfortunately, by adopting server-side rendering via Nuxt, we added a new dependency to the project: servers.

I hate servers.

Having managed AWS accounts and resources for ten years, I have both fallen victim to, and contributed to bloated AWS accounts filled with unmaintained, insecure, and mysterious EC2 instances. These have a tendency to drive up costs opaquely and create constant liabilities for myself and my colleagues. While EC2 instances and other cloud servers have had a transformative effect on the DevOps world, in our case a fleet of load-balanced instances would need to be carefully provisioned, configured, monitored, and maintained to meet our speed and reliability requirements. We just didn’t have the bandwidth for that.

For the past few years, I had been tinkering with AWS Lambda, and other serverless technologies to solve small technology problems with microservices in an effort to reduce the need for more EC2 servers. Naturally, I proceeded to suss out whether or not it would be feasible to run our Nuxt app on Lambda via API Gateway. I stumbled upon this excellent reading on serverless rendering with Nuxt and Lamdas, where a similar approach had been taken.

The author had managed to combine Nuxt and AWS serverless technologies to create what we affectionately started calling a Server(less) Side Rendered Single Page Application. Even better, we learned how to manage and deploy the entire stack and its required infrastructure via the Serverless framework. Serverless makes the management of the various AWS resources that power the app trivial.

Serverless is a framework that makes deploying apps to serverless cloud infrastructure trivial

By this point, we had selected a framework, an infrastructure and deployment plan, and started coding up the building blocks of our application. However, there were a few remaining obstacles:

  1. API Gateway (APIGW)acts as the, well, gateway to the application code running on Lambda. While APIGW works great for APIs, it wasn’t really designed to serve web applications. One shortcoming is that only https is available at APIGW endpoints. That said, a user navigating to somewebsite.com from their browser would find that the site could not be reached.
  2. Lambda functions on their own are pretty fast, with our app responding between 200 and 300 ms. While that’s fine and well, we wanted the majority of our website requests to respond much faster. The way to accomplish this is caching

Lucky for us, CloudFront, AWS’s magical CDN solution, solved these last-mile roadblocks. By serving the whole app behind a CloudFront distribution we were able to:

  • Accept both http and https requests, redirecting all to https.
  • Deliver the majority of first initial requests cached by Cloudfront, resulting in sub-10ms response times. (Crazy, stupid fast.)

Another fun fact about CloudFront and CDN caching in general: Depending on your cache settings and traffic behavior, your cache hit-to-miss ratio will vary. That is, the percentage of requests that are delivered from the (must faster) cached edge, instead of the origin, typically increases the more people are using your application. With that said, it is safe to say that in this arrangement, your app actually gets faster the more people are using it. That’s what I call scalable, not to mention it’s a super fun thing to tell your bosses.

Here’s a look at the finished infrastructure:

A high level look at the wbez.org serverless infrastructure

So there you have it. With the above technologies, we built a web application that achieved the following:

  1. Meets a complex combination of technical requirements
  2. Provides an overall pleasant developer experience to our engineers
  3. Delivers crazy, stupid fast response times
  4. Is extremely scalable
  5. Requires almost no DevOps maintenance
  6. Costs little to operate

A little more about that last point: WBEZ.org can now serve over 1 million unique users a month at a cost of less than $100 / month. Note: this number only includes the cost of resources required to run the application itself, not the underlying API, or storage and bandwidth of image and audio assets.

WBEZ Chicago is Chicago’s NPR news station, one of the largest and most respected public media stations in the country. Oliver Holmberg is the Lead Software Engineer.

--

--