Deployment of configurable Single Page Applications

No Comments

In recent years, the implementation of frontends in the form of Single Page Applications (SPA) has become increasingly popular. Single Page Applications are websites that are based on the web technologies HTML, CSS and, above all, JavaScript. In contrast to classic websites, however, the same HTML file is always delivered. The content of a page is rendered subsequently using JavaScript.

The deployment of Single Page Applications poses several challenges which I will discuss in this blog post. At the end of the post, I will introduce a container base image that specializes in deploying SPAs and can help overcome said challenges.

Static entry point

When navigating between content in an SPA, the browser URL is typically manipulated using the HTML 5 History API. As mentioned earlier, when an SPA is called, always the same HTML file is delivered to the browser. However, if the browser’s URL is manipulated via History API, it may happen that an error page is displayed after a reload of the page. This happens because the browser requests the content to be updated from the web server of the page with the current browser URL and the requested resource is not available there. To prevent this, the web server must be configured to deliver the requested index.html file even for unknown resources. Exceptions should be unknown resource URLs with specific extensions, such as .js, .css, .jpg and .png. In these cases, the server should continue to return an error page and the status code 404 instead of the index.html. This behavior must be configured appropriately in the web server that delivers the SPA.

Deployment configuration

Frameworks such as Angular or React provide mechanisms to apply the configuration of an application at compile time. However, this approach has the major disadvantage that the application has to be recompiled for each deployment environment. It would be nicer to be able to perform the build of an application independently of the deployment. Web pages with this feature are also called Immutable Web Apps. One way to configure a Single Page Application at runtime is to dump the configuration to a simple script file and swap it out during deployment.

window.spaConfig = {
  api: 'https://api.example.com'
};

Script file with the deployment configuration of the SPA.

It is important that this script is declared in the index.html before the script of the actual application. This way it can be assumed in the application’s script that the configuration is already available.

Resource caching

The compilers or bundlers of Single Page Applications often create resource files with hashes in the file name (e.g. main.3b7d25e6.js or style.74ed70bc.css). Therefore, when the content of such a file changes, its name automatically changes as well. A client only has to load such a resource once and can then keep it in the local cache for any length of time without having to ask the web server again for a current version. The delivery of these resources should therefore be done with the HTTP header cache-control: public, max-age=31536000, immutable if possible. Resources without a corresponding hash (such as the index.html of the web page), on the other hand, must be delivered with the cache-control: no-store or cache-control: no-cache, max-age=0 header. no-store completely disables caching of these resources, while no-cache, max-age=0 only enforces checking whether a cached resource is still up to date.

The web server should be configured accordingly so that the browser caches resources with a hash in their name for any length of time and prevents caching for all other resources, or forces revalidation of cached resources.

Security header

When delivering web pages, the server may send along various headers to harden the page against various attacks. These include, for example, the Content-Security-Policy header, which can prevent unwanted communication with other HTTP servers on the Internet. Furthermore, the Referrer-Policy header can be used to control whether a Referrer header should be sent along when navigating to other websites. This can prevent sensitive page parameters from being leaked. Another header that should be set when delivering HTTP resources is the X-Content-Type-Options header. This can be used to prevent MIME type sniffing by browsers.

Base image: Single Page Application server

Single Page Applications can be deployed very well as container applications. An Nginx image is suitable as a base, as shown in a previous codecentric blog post for Angular applications. However, the above requirements for deploying Single Page Applications can make Nginx configuration very complicated. We have therefore built a base image that already meets these requirements and especially simplifies the deployment configuration a lot. You can find this image in the Docker Hub. It is also based on an Nginx image and is automatically updated on a regular basis.

The image provides the ability to configure the application at container startup time. This configuration can be an individual one for different HTTP hosts, so it becomes possible to serve different environments with the same container. The configuration is done in the form of YAML. The following example will illustrate this:

default:
  spa_config:
    appTitle: "My Default Application"
    endpoints:
      globalApi: "https://api.example.com"
special_host:
  server_names:
    - "special.example.com"
  spa_config:
    appTitle: "My Special Application"

Example of a spa_config.yaml.

A container started with this configuration will generate two different index.html files and two configuration scripts, each of which will be embedded in the corresponding index.html file. (One index.html and script file for the host special.example.com and once both files for all other hosts). The image configures the Nginx server to select which index.html page is actually delivered based on the HTTP host.

host-specific single page application script file
Delivery of a host-specific index.html and spa_config.js.

var spaConfig = {.
  "appTitle": {"My Special Application",
  "endpoints": {
    "globalApi": "https://api.example.com"
  }
}

Script file for special.example.com.

The script file creates a spaConfig variable in the global context of the page, which contains the container-specific settings of the page. Thus, in the actual JavaScript application, window.spaConfig.endpoints.globalApi can be used, for example, to query an endpoint through which the page can access an HTTP API.

At development time of an SPA using TypeScript, it is necessary to declare the existence of the spaConfig variable in the window object. Otherwise, the TypeScript compiler will complain that it does not know about this variable. To do this, you should write a TypeScript declaration file like the following:

declare global {
  interface Window {
    spaConfig: {
      appTitle: string;
      endpoints: {
        globalApi: string;
      }
    };
  }

SpaConfig.d.ts

In the GitHub repository for this base image, there are sample applications using Angular and React that illustrate how to use the image with each framework.

More features

In addition to the simple configuration of the SPA, the image offers further features that will be briefly discussed here. By default, the image already delivers the website with a strict content security policy (CSP). In this way, security aspects such as the mitigation of XSS attacks are already taken into account at an early stage. However, the endpoints listed in spaConfig.endpoints for configuring the SPA are released directly, so that the additional configuration of the CSP is not perceived as annoying too quickly and, in case of doubt, is deactivated completely. Other security settings, such as preventing the referrer header or the sniffing of resource content types by the browser, are also enabled by default, but should cause even fewer problems.

The Nginx server is also already configured to deliver resources, with hashes in their names, with a long-lived cache policy. This way, many resources only need to be loaded from the server once. Revalidation of the timeliness of these resources is prevented, as it is not necessary.

If a web page needs to be available under a different base path, configuration of the <base> element of the index.html can be done when the container is started.

Certain tags of the base image are updated regularly, so that the current Nginx version can always be used. The tags involved can be found in the documentation of the image. The functionality of the image is continuously checked with integration tests when it is updated, in order to be able to detect possible breaking changes in the configuration of the Nginx server.

Conclusion

Deploying Single Page Applications is not trivial and has raised many questions in virtually every project in the past. In many cases best practices were already known. However, the effort to implement them was often avoided or postponed. Hopefully, this blog post provided a good overview of the different aspects of SPA deployments and it also presented a concrete solution approach with the base image for container deployments to meet these challenges.

Comment

Your email address will not be published. Required fields are marked *