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.
1window.spaConfig = { 2 api: 'https://api.example.com' 3};
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:
1default: 2 spa_config: 3 appTitle: "My Default Application" 4 endpoints: 5 globalApi: "https://api.example.com" 6special_host: 7 server_names: 8 - "special.example.com" 9 spa_config: 10 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.
Auslieferung einer Host-spezifischen index.html
und spa_config.js
1var spaConfig = { 2 "appTitle": "My Special Application", 3 "endpoints": { 4 "globalApi": "https://api.example.com" 5 } 6}
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:
1declare global { 2 interface Window { 3 spaConfig: { 4 appTitle: string; 5 endpoints: { 6 globalApi: string; 7 } 8 }; 9 }
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 -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.
Weitere Beiträge
von Philip Sanetra
Dein Job bei codecentric?
Jobs
Agile Developer und Consultant (w/d/m)
Alle Standorte
Weitere Artikel in diesem Themenbereich
Entdecke spannende weiterführende Themen und lass dich von der codecentric Welt inspirieren.
Gemeinsam bessere Projekte umsetzen.
Wir helfen deinem Unternehmen.
Du stehst vor einer großen IT-Herausforderung? Wir sorgen für eine maßgeschneiderte Unterstützung. Informiere dich jetzt.
Hilf uns, noch besser zu werden.
Wir sind immer auf der Suche nach neuen Talenten. Auch für dich ist die passende Stelle dabei.
Blog-Autor*in
Philip Sanetra
IT Consultant & Software Engineer
Du hast noch Fragen zu diesem Thema? Dann sprich mich einfach an.
Du hast noch Fragen zu diesem Thema? Dann sprich mich einfach an.