Best way to host a Single Page Application (SPA) in Microsoft Azure

Traditional Azure App Service vs. Azure Functions V2 with Proxies vs. Azure Storage Static Website (Preview)

medialesson
Published in
8 min readJul 4, 2018

--

I frequently need to deploy static websites (e.g. our company website https://www.media-lesson.com) or Single Page Applications and I’m always looking for ways to improve cost and time-to-deploy.

Microsoft just recently announced public preview of static website hosting for Azure Storage adding yet another option to host a single page application (SPA) on Azure.

Just before Microsoft added back support for Proxies in the Azure Functions Runtime 2.0.11776-alpha on May 14th providing a way to host a static website in Azure Storage and forwarding traffic through a Azure Function proxy route.

Both new options add to the traditional way of hosting of (static) website in an Azure App Service. There are even more options of hosting websites in Azure e.g. using Containers, Docker, Kubernetes, Virtual Machines etc. but I will keep the focus on easy and cost efficient Deployments of single page applications.

And since we’re evaluating hosting options it’s also worth comparing manual and automatic deployment using Visual Studio Team Services (VSTS) for the mentioned services.

So let’s answer these questions to help decide which service to use when hosting a Single Page Application:

  1. How much effort is it to manually and automatically deploy an app?
  2. How much configuration is needed?
  3. How does it scale?
  4. How does it perform?
  5. How much does it cost?

Test App

So lets start this experiment by creating a simple SPA based on Angular using the Angular CLI as a test app to deployment and test on the 3 different services: ng new testapp --routing Please note that I’m directly adding the routing feature to app using the --routing parameter. I find this an important aspect to test when looking at hosting options as routing can be a configuration challenge in some environments like App Service because we need to configure the web server to allow route handling by our client app instead of server side.

To fully test routing we also need to have some sample routes. Therefore lets add a home and a about component to our app using the CLI: ng generate component home and ng generate component about. Next we need to have two routes in our app-routing.module.ts to allow navigation between the two components:

Finally some navigation buttons to allow the user to navigate between the two components in our app.component.html:

Let’s build our app locally in preparation for manual deployment using ng build --prod which gives us this little folder of build artifacts:

Of course we also want to dry automatic deployment as part of a continuous deployment process. Therefore let’s push our app to a VSTS repository and setup a build definition with these 3 tasks:

npm install

npm build

Publish build artifact from path dist to app

Azure App Service

Azure App Service is a PaaS (Platform as a Service) offering and the classic way of hosting web content on Azure. Using app service we need to take care of configuration and scaling of our service by hand. To test this let’s create a new app service in the Azure Portal. I choose the S1 tier for this test as it’s the entry level tier for production apps.

Configure Routing

Because of it’s nature in being a PaaS offering, developers to take care of the configuration of the underlying web server (in case of Windows it’s IIS). This means to enable routing inside our Angular app we have to provide a web.config file with instructions for the web server how to handle routing or host the angular app inside a ASP.Net Core 2.1 app with the SpaServices middleware enabled. Here a simple sample of a web.config with SPA routing:

Manual Deployment

For manual deployment let’s just use FTP to upload the content of our dist folder and the web.config to the wwwroot folder of the app service. The FTP credentials can be downloaded in the Azure portal in the overview tab of the app service by clicking on “Get publish profile”.

Continuous Deployment using Visual Studio Team Services

Setting up a release definition in VSTS to deploy the app to app service is pretty straight forward as we just need an empty release definition with one task:

To configure an FTP service endpoint let’s click on “Manage” and add a new generic endpoint with the same credentials used for manual deployment.

Azure Functions

In this variant we’re going to use Azure Storage as an inexpensive store for our static files and an Azure Function App with proxies to serve the SPA to the user. This allows us to scale automatically (when using the consumption plan), combine our SPA with other functions (e.g. API calls) under one domain and manage our app in a serverless fashion.

So we need to create a Function App and a Storage Account (automatically created when creating a Function App). Next we need to create a blob container named “web” in the storage account where we will deploy our files to later.

The routing magic now happens in the way we configure proxies to forward request from our function app to the storage account. Therefore we will need 2 proxies:

The first one to forward requests to the base url to our index.html

and a second one to forward requests for other static assets like javascript files or stylesheets to their location in the storage account.

Manual Deployment

To manually deploy click on “Containers” in the storage account in the Azure portal and select the “web” container that we just created. Now let’s upload the files from the local build.

Continuous Deployment using Visual Studio Team Services

To test this we need to create a second release definition in VSTS with the empty process template and add the AzureBlob File Copy task:

Azure Storage

This is the newest option that just recently went into public preview. The idea is to use storage to host the SPA because a real web server is not needed to serve just static files qualifying this variant for the buzzword “serverless”. So let’s create a new storage account (general purpose v2) and then enable the static website feature:

This is all configuration we need. Storage scales automatically and routing also works out of the box. The primary endpoint is our SPA’s url. Nice!

Manual Deployment

To manually deploy click on “Containers” in the storage account in the Azure portal and select the “$web” container (which is created automatically when enabling static website). Now let’s upload the files from the local build:

And that’s it!

Continuous Deployment using Visual Studio Team Services

To test this we need to create a third release definition in VSTS with the empty process template and add the AzureBlob File Copy task:

Make sure to select version 2.* (preview) otherwise the deploy wail fail because the “$” character in the container name was not allowed previously.

Performance

To get an idea about the performance let’s run some artillery.io load tests on our 3 deployments. Here are my results when firing 1,000, 10,000 and 100,000 requests:

There’s a drastic performance benefit using Azure Storage and avoiding the web server component while Functions and App Service run at a comparable speed. This makes sense as both share the same underlying infrastructure. I’m a bit surprised that the Function app is running even slower that the App Service.

I find this even stranger when comparing the total runtimes to complete the 100,000 requests test. This took around 46 seconds to complete to Azure Storage and 14 minutes and 27 seconds for App Service and even 17 minutes and 2 seconds for the Function app. I would have expected the Function app to gain speed over time as I was expecting it to scale horizontally automatically. Which doesn’t seem to work in this scenario.

So we have a clear winner in this discipline: Storage is really fast!

Costs

Getting the cost right is tricky as all have different billing models. Here’s my example calculation for monthly costs for 100.000 requests/day in the West Europe region (which I’m not 100% sure it’s complete and accurate!):

App Service

1x S1 Instance in West Europe running Windows (1 core, 1.75 GB RAM, 50 GB Storage) = 61.56 €/month

Function App (+ Storage)

Our SPA consists of 5 files * 100,000 requests/day * 31 = 15,500,000 requests per month total. The total size of our app is roughly 0,33 MB which accounts for 0,98 TB of outbound traffic per month total. The minimum execution time is 100ms (which would be enough for our proxy purpose) for we can handle 10 requests/execution second.

1x Function App with 1.550.000 executions with a execution time of 1s each per month (each less than 128 MB of memory) for handling the requests going through the proxies = 0.17 €/month

1x Storage General Purpose V2 Block Blob Storage account with LRS redundancy and hot access tier. Capacity 1 GB (we actually need just 300kb but that’s the smallest size available), 100 Write Operations, 100 List operations, 15,500,000 read operations and 0,98 TB data retrieval = 5.64 €/month

Total: 5.81 €/month.

Storage

For the storage we just take the same calculation than above:

1x Storage General Purpose V2 Block Blob Storage account with LRS redundancy and hot access tier. Capacity 1 GB (we actually need just 300kb but that’s the smallest size available), 100 Write Operations, 100 List operations, 15,500,000 read operations and 0,98 TB data retrieval = 5.64 €/month

Conclusion

  • Hosting a SPA in pure Storage is by far the cheapest and most performant way of running in Azure
  • Hosting a SPA in a Function App with Proxies comes at a minimal extra cost but a massive performance drop. This kind of strange as I should scale horizontally. I will definitely do more research here…
  • Hosting a SPA in an App Service requires extra configuration effort to support routing (getting more complex when combined e.g. with Web APIs)

Hosting an SPA in Storage should be a no-brainer for dev, test and staging situations as it’s fast to setup and in most cases even free for those scenarios. I didn’t find any disadvantages yet so will dive a bit deeper and see if we can also use it in production.

Please feel free to provide feedback.

--

--

medialesson

CEO @ medialesson. Microsoft Regional Director & MVP Windows Development. Father of identical twins. Passionate about great User Interfaces, NYC & Steaks