Azure functions can be very cheap and very easy to manage. Hosting a single page app (SPA) that does very few requests to the host and is basically just a few files to be delivered to the client seems perfect for using the consumption pricing of azure functions. Basically only pay for a request vs a monthly fee.

Azure Resources

We’ll need a few resources in Azure, they are:

  • Azure Function app
  • Azure storage account with a blob container that has public access

These are very straight forward to create in azure and plenty of articles if you haven’t done this before.

Our SPA

I’ll setup a site with the angular cli.

ng new my-app

We want the browser to access any page resources directly via azure storage and so we need to setup a couple of items so that our published output will have absolute URLs for resources. You will need the url Set ‘deployUrl’ either in .angular-cli.json at

{
  "apps": [
    {
      "deployUrl": "https://storage-account.blob.core.windows.net/container"
    }
  ]
}

or use build cli param

ng build --prod --deploy-url "https://storage-account.blob.core.windows.net/container"

To get this into the container in the storage account I use the Azure Storage Explorer or you can use the azure portal browser also. You want to copy everything from the dist folder into the storage container.

Azure Functions setup

At the time of this writing function proxies are in preview and need turning on. You will have to go to ‘Function app settings’ and switch the Proxies feature to ‘On’. We need to create two proxies: one for the home/root path and another for a wildcard catch-all that will serve in path for the site to our SPA host. I thought this could be done with one wildcard path, but it didn’t word for the URL with no path so two it is

  1. Create no path proxy Route template: / HTTP Methods: GET BackendURL: https://storage-account.blob.core.windows.net/container/index.html
  2. Create catch all path proxy Route template: {*path} HTTP Methods: GET BackendURL: https://storage-account.blob.core.windows.net/container/index.html

Now if you browser to https://your-function-app.azurewebsites.net your SPA should be served :) Another point that’s not required, but best so you can take advantage of browser caching is to set the cache-control properties of your appropriate resources.

Trigger to set cache policy on all static resources

To do this, we will use a blob trigger function that updates the CacheControl property of the blob. This will correspond to the cache-control header when the resource is used in the browser.

  1. Create a function using C# Blob Storage Trigger
  2. Paste in the code below into the function
#r "Microsoft.WindowsAzure.Storage"
using Microsoft.WindowsAzure.Storage.Blob;

public static void Run(ICloudBlob myBlob, string name, TraceWriter log)
{
    log.Info($"C# Blob trigger function Processed blob\n Name:{name}");

    if (Path.GetExtension(name) != ".html")
    {
        myBlob.Properties.CacheControl = "public, max-age=3600";
        myBlob.SetProperties();
    }
}

We are adding the header for all files except the html file as that will be the root homepage. It is the only one that isn’t published with a version string by the angular cli and so there’s no way to bust the user’s browser cache to get it updated with future changes.