Static Website Hosting with Function App Backend on Azure

Leon Fausten
Holisticon Consultants
6 min readMay 12, 2021

This article is part of a series of articles about a journey to a completely serverless application on azure. In the previous part we talked about the serverless backend realized with azure functions.
This part is about the frontend, hosted as static website. The source code can be found on github.

In the next part we will add serverside updates to synchronize the state between all clients.

Screenshot of the Todo App Frontend with example tasks
Example Todo App

Next.Js Frontend

Our frontend was originally implemented by Marvin Luchs. It is written in Typescript with Next.JS.

The application uses the Static Generation approach of Next.js which means that for each page of the app a corresponding HTML page is generated at build time. Requesting the route /tasks/completed delivers the file /tasks/completed/index.html. This is important for us, as we will see later.
The original source code can be found here. I made a few modifications to the app. All updated sources are on github as well.

We build the static frontend with npm install && npm run build . In the out folder we now find all compiled sources to deploy. In our example case we trigger the deployment manually with Visual Studio Code and the Azure Storage Extension. But first we have to enable the static website feature.

Static Website Hosting

We use the Static Website feature inside our already existing Storage Account from the Function App. It is also possible to use a different one. The frontend hosted as static website doesn’t require a web server to render the pages, instead just the files are delivered to the clients. With this kind of hosting we require very low computing power, this results again in lower costs for storage and read access.

Screenshot Static Website Settings
Azure Portal: Enable Static Website Feature for the Storage Account

To use it, we have to enable the feature in the settings section of our Storage Account. After we enable it, a $web blob container is automatically created. The compiled frontend has to be deployed into this container.

We define an index document as entry point and also an error document, as you can see in the screenshot. These are delivered on /or if a file could not be found. The path is relative to the $web container.
Here we can also see the endpoint to access the static website.

After a successful deployment, we can reach the website with the given endpoint. But we are not able to add new tasks or use any other feature. The api requests aren’t working.
The page tries to reach the api via the static web page endpoint and not via the function app endpoints.

Now we have two possible ways to solve this problem:

  1. Configure our frontend to use a different origin for the api.
  2. Make our frontend accessible via the function app endpoint.

In the first solution we have to manage the CORS (Cross-Origin Resource Sharing) settings. With the second we have the additional possibility to manipulate the requests and responses. We will use this feature later to optimize our app. So we decide to go with the second one.

Proxies

To make the app fully functional we use Azure Function Proxies. Now we need the proxies.json file from the Azure Function App, we created in the part before.

The desired state at the end is, that our application is reachable via the function app endpoint. Currently we can only see the default “It works” page there.
The correct proxy for a request is selected by the most specific match method. So we can create proxies with overlapping route definitions, but the most specific one will be used.

Below we can see the proxy definitions, we will explain them in detail in the following sections.

proxies.json

Routing Proxy

First we want to be able to access the static webpage also from the function app endpoint. We create a new proxy inside the configuration with a match condition for the base endpoint url and everything after it.

This proxy will proxy every GET and HEAD request after / to the corresponding folder or file of our static storage. If the path doesn’t exists, the above defined error document is delivered.

The variable restOfPath is used to match everything after / . The * defines it as wildcard variable. That means, it will contain the rest of the path as string. Without it, it would only match until the next /.

When we now visit our function app endpoint, we can see that the page is loaded successfully and also the above error page is delivered for unknown paths. But because of the proxy, all our requests to /api/... are proxied to the static web page as well. They never reach our functions.

To resolve this we have to create another proxy for our api.

Local Api Proxy

To fix our api routes we add another proxy for every route starting with /api.

proxy definition for local api routes

Here we can use localhost instead the public endpoint of our function app. This prevents a roundtrip to the proxies again.

Now our app is fully functional.

Response Manipulation

The static files of our frontend doesn’t change very often, so we should cache them. This reduces the read requests and increases the performance of our clients.
A proxy can also manipulate the requests and responses. With a separate proxy for our files, we can add a caching header only to them.

We need the backendUri for the new proxy again, so first let’s save it in an environment variable to reuse it and for better maintainability. We do this in the application settings tab of the function app configuration.

Application Environment Variable Setting

Now we can use the variable instead the hardcoded endpoint.

"backendUri": "https://%STATIC_WEBSITE_URL%/"

To enable browser caching, we add aCache-Control header to the response. The body stays untouched.

proxy definition with additional cache headers

Here we override the response and add a cache header. But be careful with this. This header allows the browser to cache the response for more than six days and we will not be able to force the clients browsers to reload the files. In our case we want that only files on the _next route are cached.

Conclusion

In this article we deployed our frontend as static web page inside an azure storage accounts. To make it fully functional we added a few proxies. With the proxies we also manipulated the responses to add custom cache headers.

Alternatively to this approach azure offers a preview version of a Static Web App service. In this service you can host you web app and use functions as well.

In the next part we are going to add real-time updates with the help of SignalR to our application.

If you don’t want to miss any part of this journey or a lot of other interesting stories, feel free to subscribe to Leon Fausten or Holisticon.

Sources and Further Information

--

--