Deployment konfigurierbarer Single Page Applications

Keine Kommentare

In den letzten Jahren ist die Implementierung von Frontends in Form von Single Page Applications (kurz SPA) immer beliebter geworden. Bei Single Page Applications handelt es sich um Webseiten, die auf den Web-Technologien HTML, CSS und vor allem JavaScript aufsetzen. Im Unterschied zu klassischen Webseiten wird allerdings immer dieselbe HTML-Datei ausgeliefert. Der Inhalt einer Seite wird nachträglich per JavaScript gerendert.

Das Deployment von Single Page Applications geht mit verschiedenen Herausforderungen einher, auf die ich in diesem Blog Post eingehen möchte. Am Ende des Posts stelle ich ein Container-Basis-Image vor, das auf das Deployment von SPAs spezialisiert ist und bei der Bewältigung der genannten Herausforderungen behilflich sein kann.

Statischer Einstiegspunkt

Bei der Navigation zwischen Inhalten einer SPA wird die Browser-URL üblicherweise mithilfe des HTML 5 History API manipuliert. Wie bereits erwähnt, wird beim Aufruf einer SPA immer dieselbe HTML-Datei an den Browser ausgeliefert. Wenn die URL des Browsers per History API manipuliert ist, kann es allerdings vorkommen, dass nach einem Reload der Seite eine Fehlerseite angezeigt wird. Dies geschieht, weil der Browser den zu aktualisierenden Inhalt beim Webserver der Seite mit der aktuellen Browser-URL abfragt und die angefragte Ressource dort nicht vorhanden ist. Um dies zu verhindern, muss der Webserver so konfiguriert werden, dass auch bei unbekannten Ressourcen die gewünschte index.html-Datei ausgeliefert wird. Ausnahmen sollten unbekannte Ressourcen-URLs mit bestimmten Endungen wie .js, .css, .jpg und .png bilden. In diesen Fällen sollte der Server weiterhin eine Fehlerseite und den Status-Code 404 statt der index.html zurückliefern. Dieses Verhalten muss im Webserver, der die SPA ausliefert, entsprechend konfiguriert werden.

Deployment-Konfiguration

Frameworks wie Angular oder React bieten Mechanismen, um die Konfiguration einer Anwendung zur Compile-Zeit anzuwenden. Diese Vorgehensweise hat allerdings den großen Nachteil, dass die Anwendung für jede Deployment-Umgebung neu kompiliert werden muss. Schöner wäre es, den Build einer Anwendung unabhängig vom Deployment durchführen zu können. Webseiten mit dieser Eigenschaft werden auch Immutable Web Apps genannt. Eine Möglichkeit, eine Single Page Application zur Laufzeit zu konfigurieren, ist es, die Konfiguration in eine einfache Skript-Datei auszulagern und diese beim Deployment auszutauschen.

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

Skript-Datei mit der Deployment-Konfiguration der SPA

Es ist wichtig, dass dieses Skript in der index.html vor dem Skript der eigentlichen Anwendung deklariert wird. So kann im Skript der Anwendung davon ausgegangen werden, dass die Konfiguration bereits verfügbar ist.

Ressourcen-Caching

Die Compiler bzw. Bundler von Single Page Applications erzeugen häufig Ressourcen-Dateien mit Hashes im Datei-Namen (z. B. main.3b7d25e6.js oder style.74ed70bc.css). Wenn sich der Inhalt einer solchen Datei ändert, ändert sich daher auch automatisch deren Name. Ein Client muss so eine Ressource nur einmalig laden und kann diese anschließend beliebig lange im lokalen Cache halten, ohne den Webserver noch einmal nach einer aktuellen Version zu fragen. Das Ausliefern dieser Ressourcen sollte daher möglichst mit dem HTTP-Header cache-control: public, max-age=31536000, immutable erfolgen. Ressourcen ohne einen entsprechenden Hash (wie z. B. die index.html der Webseite) müssen dagegen mit dem Header cache-control: no-store oder cache-control: no-cache, max-age=0 ausgeliefert werden. no-store deaktiviert das Caching dieser Ressourcen vollständig, während no-cache, max-age=0 nur die Prüfung erzwingt, ob eine im Cache liegende Ressource noch aktuell ist.

Der Webserver sollte entsprechend so konfiguriert werden, dass der Browser die Ressourcen mit einem Hash im Namen beliebig lange im Cache hält und bei allen anderen Ressourcen das Caching verhindert bzw. eine Revalidierung der im Cache liegenden Ressourcen erzwingt.

Security-Header

Bei der Auslieferung von Webseiten kann der Server verschiedene Header mitsenden, um die Seite gegen verschiedene Angriffe abzuhärten. Dazu zählt beispielsweise der Content-Security-Policy-Header, der die ungewollte Kommunikation mit anderen HTTP-Servern im Internet unterbinden kann. Des Weiteren kann mit dem Referrer-Policy-Header gesteuert werden, ob bei der Navigation zu anderen Webseiten ein Referrer-Header mitgeschickt werden soll. So kann das Leaken sensibler Seiten-Parameter verhindert werden. Ein weiterer Header, der beim Ausliefern von HTTP-Ressourcen gesetzt werden sollte, ist der X-Content-Type-Options-Header. Über diesen kann das MIME-Type-Sniffing von Browsern unterbunden werden.

Basis-Image: Single Page Application Server

Single Page Applications lassen sich sehr gut als Container-Anwendung deployen. Als Basis eignet sich ein Nginx-Image, wie es bereits in einem früheren codecentric-Blogpost für Angular-Anwendungen gezeigt wurde. Die oben genannten Anforderungen an das Deployment von Single Page Applications können allerdings die Nginx-Konfiguration sehr kompliziert machen. Wir haben daher ein Basis-Image gebaut, das diese Anforderungen bereits erfüllt und besonders die Deployment-Konfiguration sehr vereinfacht. Zu finden ist dieses Image im Docker-Hub. Es basiert ebenso auf einem Nginx-Image und wird regelmäßig automatisch aktualisiert.

Das Image bietet die Möglichkeit, die Anwendung zur Startzeit des Containers zu konfigurieren. Diese Konfiguration kann für unterschiedliche HTTP-Hosts individuell sein, so dass es möglich wird, mit demselben Container unterschiedliche Umgebungen zu bedienen. Die Konfiguration wird in Form von YAML vorgenommen. Das folgende Beispiel soll dies verdeutlichen:

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"

Beispiel einer spa_config.yaml

Ein mit dieser Konfiguration gestarteter Container wird zwei verschiedene index.html-Dateien und zwei Konfigurationsskripte generieren, die jeweils in die entsprechende index.html-Datei eingebettet werden. (Eine index.html– und Skript-Datei für den Host special.example.com und einmal beide Dateien für alle anderen Hosts.) Das Image konfiguriert den Nginx-Server so, dass anhand des HTTP-Hosts ausgewählt wird, welche index.html Seite tatsächlich ausgeliefert wird.

Host-spezifische Single Page Application-Skriptdatei
Auslieferung einer Host-spezifischen index.html und spa_config.js

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

Skript-Datei für special.example.com

Die Skript-Datei erzeugt im globalen Kontext der Seite eine spaConfig-Variable, welche die Container-spezifischen Einstellungen der Seite enthält. In der eigentlichen JavaScript-Anwendung kann somit beispielsweise über window.spaConfig.endpoints.globalApi ein Endpunkt abgefragt werden, über den die Seite auf ein HTTP-API zugreifen kann.

Zur Entwicklungszeit einer SPA mit TypeScript ist es notwendig, die Existenz der spaConfig-Variablen im window-Objekt zu deklarieren. Ansonsten wird sich der TypeScript-Compiler darüber beschweren, dass er diese Variable nicht kennt. Dazu sollte eine TypeScript-Declaration-Datei wie die folgende geschrieben werden:

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

SpaConfig.d.ts

Im GitHub-Repository zu diesem Basis-Image gibt es Beispiel-Anwendungen mit Angular und React, welche die Verwendung des Images mit dem jeweiligen Framework verdeutlichen.

Weitere Features

Neben der einfachen Konfiguration der SPA bietet das Image weitere Features, auf die an dieser Stelle kurz eingegangen werden soll. Per Default liefert das Image die Webseite bereits mit einer strengen Content Security Policy (kurz CSP) aus. Auf diese Weise sollen Sicherheitsaspekte wie die Entschärfung von XSS-Attacken bereits früh berücksichtigt werden. Die Endpunkte, die in spaConfig.endpoints zur Konfiguration der SPA aufgelistet werden, werden allerdings direkt freigegeben, so dass die zusätzliche Konfiguration der CSP nicht zu schnell als störend empfunden und im Zweifel ganz deaktiviert wird. Weitere Sicherheitseinstellungen, wie das Verhindern des Referrer-Headers oder das Sniffen von Ressourcen-Content-Typen durch den Browser, sind ebenfalls per Default eingeschaltet, sollten allerdings noch seltener Probleme bereiten.

Der Nginx-Server wird außerdem bereits so konfiguriert, dass er Ressourcen, mit Hashes im Namen, mit einer langlebigen Cache-Policy ausliefert. Auf diese Weise müssen viele Ressourcen nur einmalig vom Server geladen werden. Eine Revalidierung der Aktualität dieser Ressourcen wird verhindert, da diese nicht notwendig ist.

Falls eine Webseite unter einem anderen Basis-Pfad verfügbar sein muss, kann beim Starten des Containers die Konfiguration des <base>-Elements der index.html vorgenommen werden.

Bestimmte Tags des Basis-Images werden regelmäßig aktualisiert, so dass immer eine aktuelle Nginx-Version eingesetzt werden kann. Um welche Tags es sich dabei handelt, kann der Dokumentation des Images entnommen werden. Die Funktionsfähigkeit des Images wird bei dessen Aktualisierung kontinuierlich mit Integrationstests geprüft, um mögliche Breaking Changes bei der Konfiguration des Nginx-Servers erkennen zu können.

Fazit

Das Deployment von Single Page Applications ist nicht trivial und hat in der Vergangenheit in praktisch jedem Projekt zu Fragezeichen geführt. In vielen Fällen waren Best Practices bereits bekannt, allerdings wurde der Aufwand, diese umzusetzen, häufig gescheut oder auf später verschoben. Dieser Blogpost konnte hoffentlich einen guten Überblick über die verschiedenen Aspekte von SPA-Deployments geben und mit dem Basis-Image für Container-Deployments auch einen konkreten Lösungs-Ansatz vorstellen, um diesen Herausforderungen zu begegnen.

 

Finde jetzt als Frontend Developer und Consultant deinen Wen in dein neues Team!

 

 

Philip arbeitet seit 2017 bei der codecentric in Münster. Sein Interesse gilt vor allem dem Umfeld der verteilten Systeme und skalierbaren IT-Architekturen.

Über 1.000 Abonnenten sind up to date!

Die neuesten Tipps, Tricks, Tools und Technologien.
Jede Woche direkt in deine Inbox.

Kostenfrei anmelden und immer auf dem neuesten Stand bleiben!
(Keine Sorge, du kannst dich jederzeit abmelden.)

Kommentieren

Deine E-Mail-Adresse wird nicht veröffentlicht. Erforderliche Felder sind mit * markiert.