# Single Page Application

Een Single Page Application (SPA), ook wel een Single Page Interface (SPI) genoemd, is een webapplicatie of website die uit een enkele webpagina bestaat waarvan de inhoud dynamisch aangepast wordt. Voor de gebruiker lijkt het alsof hij meerdere webpagina's ziet, terwijl het eigenlijk om dezelfde pagina gaat met telkens een andere inhoud. Het doel is een vlotte gebruikerservaring te bieden, die te vergelijken is met een computerprogramma dat op de computer van de gebruiker zelf draait. Er zijn diverse technieken beschikbaar om een Single Page Application te bouwen, waarin HTML-scripting een essentiële rol speelt. De technieken kunnen daarnaast onder meer gebruikmaken van HTML-formulieren, HTML-frames en XML.
In een SPA wordt alle benodigde code - HTML, JavaScript en CSS - opgehaald met een enkele laadactie van de pagina, of de noodzakelijke middelen worden dynamisch geladen en aan de pagina toegevoegd zodra ze nodig zijn, meestal als reactie op acties van de gebruiker. De pagina wordt op geen enkel moment in het proces opnieuw geladen en draagt ook geen besturing over naar een andere pagina, hoewel moderne webtechnologieën (zoals die zijn opgenomen in HTML5), de perceptie en de navigeerbaarheid van afzonderlijke logische pagina's in de applicatie kunnen bieden. Interactie met een SPA gaat vaak gepaard met dynamische communicatie met de webserver die achter de schermen fungeert.
De term Single Page Application werd bedacht door Steve Yen in 2005, hoewel het concept in elk geval reeds in 2003 besproken is. Stuart 'stunix' Morris schreef met hetzelfde doel en functie zijn SPA-website op adres www.slashdotslash.com in 2002, hetzelfde jaar waarin Lucas Birdeau, Kevin Hakman, Michael Peachey en Clifford Yeh een SPA-implementatie beschreven in de Amerikaanse octrooiaanvraag 8.136.109
Moderne browsers die HTML5 kunnen interpreteren, bieden ontwikkelaars de mogelijkheid om de gebruikersinterface en applicatielogica van webservers te verschuiven naar de client. Complete open source bibliotheken ondersteunen het bouwen van SPA's zonder dat de ontwikkelaars te diep in JavaScript hoeven te duiken of hoeven te worstelen met technische problemen.

## Kenmerken van SPA's

Een vakkundig gemaakte Single Page Application beschikt over de volgende kenmerken:
* Inhoud opdelen in kleine eenheden (Engels: chunking): de webpagina wordt opgebouwd door het laden van stukjes HTML-fragmenten en JSON-data, in plaats van het bij elke request ontvangen van de volledige HTML vanuit een webserver (Backbone.js, Pjax, jQuery, Upshot.js)
* Controllers: JavaScript-code, die complexe manipulaties van Document Object Model (DOM) en data, applicatielogica en AJAX-requests afhandelt, wordt vervangen door controllers die views en modellen scheiden door gebruik te maken van Model-View-Controller (MVC) of Model-View-ViewModel (MVVM) patronen (Backbone.js, Knockout.js, JavascriptMVC, Angular)
* Gebruik maken van templates: de codering van manipulatie van gebruikersinterface (Engels user interface, afgekort tot UI) en DOM wordt vervangen door declaratieve koppeling van data aan HTML-templates (Knockout.js, Mustache, jQuery-templates, Underscore.js, Angular)
* Routering: selectie van views en navigatie (zonder herladen van de pagina) die de paginatoestand, elementen en data bewaren (History.js, Crossroads.js, Backbone.js, Pjax, Angular)
* Realtime communicatie: communicatie in twee richtingen tussen de client-applicatie en de web-server vervangt eenzijdige requests vanuit de browser (HTML5 WebSockets, Socket.io, SignalR)
* Lokale opslag: mogelijkheden voor het opslaan van gegevens in een browser ten behoeve van een beter prestatievermogen (Engels: performance) en offline toegang vervangen cookies en intensieve data-laadacties vanaf de webserver (HTML5 localStorage).

## Technische benaderingen

Er zijn verschillende technieken beschikbaar die de browser in staat stellen om een enkele pagina vast te houden, zelfs wanneer de applicatie communicatie met de server nodig heeft.

### JavaScript-frameworks

JavaScript-frameworks voor webbrowsers, zoals AngularJS, YUI App Framework en React hebben het SPA-concept toegepast.
* Angular is een volledig client-side framework. De template-mogelijkheden van Angular zijn gebaseerd op bidirectionele data-koppeling. Data-koppeling is een automatische methode voor het bijwerken van de view wanneer het model wordt gewijzigd, evenals het bijwerken van het model wanneer de view wijzigt. Het HTML-template wordt gecompileerd in de browser. De compilatiestap creëert pure HTML, waarna de browser de actuele view opnieuw weergeeft. De stap wordt herhaald voor volgende pageviews. In de traditionele server-side HTML-programmering werken controller- en modelcomponenten binnen een proces op de server om nieuwe HTML-views te produceren. In het Angular-framework blijven de controller en het model gehandhaafd binnen de clientbrowser. Zodoende kunnen nieuwe pagina's worden gegenereerd zonder interactie met een server.
* Het YUI App Framework is een combinatiepakket van de componenten App, Model, Model List, Router en View; met elkaar gecombineerd vormen ze een op MVC lijkend framework voor het schrijven van Single Page Applications met JavaScript. Deze componenten zijn afzonderlijk of samen te gebruiken voor het bouwen van allerlei applicaties, van eenvoudige niet-interactieve webpagina's tot omvangrijke applicaties met URL-gebaseerde routering, data-koppeling en volledige client-server synchronisatie.

### AJAX

Een prominente techniek die momenteel wordt gebruikt, is Ajax. AJAX maakt overwegend gebruik van het XMLHTTPRequest-object van JavaScript. Andere AJAX-technieken gebruiken de HTML-elementen iframe of script. Populaire bibliotheken zoals jQuery, dat het gedrag van AJAX standaardiseert in browsers van verschillende fabrikanten, hebben de AJAX-technologie verder gepopulariseerd.

### WebSocket

WebSocket is een protocol voor bidirectionele communicatie over een enkele TCP-connectie. Dit protocol maakt onderdeel uit van de HTML5-specificatie en wordt ook gebruikt voor Single Page Applications.

### Browserplug-ins

Asynchrone aanroepen naar de server kunnen ook worden gerealiseerd met behulp van plug-ins voor browsers, zoals Microsoft Silverlight, Adobe Flash of Java-applets.

### Data-transport (XML, JSON en AJAX)

Requests aan de server resulteren meestal in ruwe data (bijvoorbeeld XML of JSON), of nieuwe HTML die wordt geretourneerd. In het geval dat de server HTML teruggeeft, werkt JavaScript aan de client-side een deel van het DOM bij. Wanneer ruwe data worden teruggestuurd, wordt aan de client-side vaak een JavaScript XML / XSLT proces gebruikt (en in het geval van JSON een template) om de ruwe data te vertalen naar HTML, die vervolgens wordt gebruikt om een deelgebied van het DOM bij te werken.

### Serverarchitectuur
#### Dunne-serverarchitectuur

Een SPA verplaatst logica van de server naar de client. Dit heeft tot gevolg dat de rol van de webserver evolueert naar een pure data-API of webservice. Deze verschuiving in de architectuur wordt wel eens 'dunne-serverarchitectuur' (Engels: Thin Server Architecture) genoemd om te benadrukken dat complexiteit wordt verplaatst van de server naar de client, met het argument dat dit de totale complexiteit van het systeem vermindert.

#### Dikke-serverarchitectuur met bijgehouden toestand

De server houdt de benodigde toestand (Engels: state) van de pagina van de client bij. Wanneer een request bij de server aankomt (meestal als gevolg van een gebruikersactie), verzendt de server de juiste HTML en/of JavaScript met de concrete wijzigingen om de client in de nieuwe gewenste toestand te brengen (gewoonlijk het toevoegen/verwijderen/wijzigen van een deel van het Data Object Model van de client). Op hetzelfde moment wordt de toestand op de server bijgewerkt. De meeste logica wordt op de server uitgevoerd en HTML wordt meestal ook opgebouwd op de server. In sommige opzichten simuleert de server een webbrowser, door het ontvangen van gebeurtenissen (Engels: events) en het uitvoeren van wijzigingen in de servertoestand die automatisch worden doorgegeven aan de client. Deze aanpak vereist meer geheugen en processorvermogen van de server, maar het voordeel is een vereenvoudigd ontwikkelingsmodel, doordat a) de applicatie meestal volledig gecodeerd is in de server, en b) de data en de toestand van de gebruikersinterface worden gedeeld in dezelfde geheugenruimte waardoor geen client/server-communicatiebruggen nodig zijn.

#### Dikke-serverarchitectuur zonder bijgehouden toestand

Dit is een variant van de benadering waarbij de toestand op de server wordt bijgehouden. In dit geval stuurt de clientpagina gegevens over zijn huidige toestand naar de server, meestal door middel van AJAX-requests. Met deze informatie kan de server de clienttoestand van het gedeelte van de pagina dat moet worden gewijzigd reconstrueren en de noodzakelijke gegevens of code genereren. Dit gebeurt bijvoorbeeld met behulp van JSON of JavaScript, die naar de client wordt teruggestuurd om deze in een nieuwe toestand te brengen, meestal door het wijzigen van de DOM-boomstructuur van de pagina, naar aanleiding van een gebruikersactie die de aanleiding was voor het request.
Bij deze aanpak moeten meer gegevens naar de server worden gestuurd en het is mogelijk dat per request meer processorvermogen nodig is om de clienttoestand op de server gedeeltelijk of volledig te reconstrueren. Tegelijkertijd is deze benadering beter schaalbaar omdat op de server geen paginagegevens per client bewaard behoeven te worden, waardoor AJAX-requests naar meerdere servernodes kunnen worden verzonden zonder dat sessiedata tussen servers behoeven te worden uitgewisseld.

## Lokaal uitvoeren

Sommige SPA's kunnen worden uitgevoerd vanaf een lokaal bestand met behulp van het URI-schema voor bestanden, zoals gedefinieerd in RFC1630 en RFC1738. Dit geeft gebruikers de mogelijkheid een SPA te downloaden van een server en uit te voeren vanaf een lokaal opslagapparaat, zonder afhankelijk te zijn van een verbinding met een server. Als zo'n SPA data wil opslaan en bewerken, moet hij gebruikmaken van browser-gebaseerde webopslag (Engels: web storage). Deze applicaties profiteren van de extra mogelijkheden die HTML5 biedt.

## Uitdagingen met het SPA-model

Omdat het SPA-model een ontwikkeling is die afwijkt van het toestandsloze model van het opnieuw opbouwen van webpagina's waarvoor browsers oorspronkelijk bedoeld waren, is een aantal nieuwe uitdagingen ontstaan. Voor elk van deze problemen bestaat een effectieve oplossing door gebruik te maken van:
* Client-side JavaScript-bibliotheken waarmee diverse problemen kunnen worden aangepakt
* Server-side webframeworks die gespecialiseerd zijn in het SPA-model
* De doorontwikkeling van browsers en de HTML5-specificatie gericht op het SPA-model.

### Zoekmachineoptimalisatie

Vanwege het ontbreken van JavaScript-uitvoering op de spiders (ook webcrawlers genoemd) van populaire webzoekmachines, heeft Zoekmachineoptimalisatie (Engels: search engine optimization of SEO) in het verleden problemen opgeleverd voor publiekelijk toegankelijke websites die het SPA-model wilden toepassen
Google doorzoekt momenteel URL's met deel-URL's, hashfragmenten genoemd, die beginnen met #!. Het deel van de URL dat begint met '#' wordt de fragmentidentifier genoemd. Dit maakt het gebruik van hashfragmenten binnen de enkele URL van een SPA mogelijk. De SPA-website moet specifiek gedrag implementeren om het ophalen van relevante metadata door de spider van de zoekmachine mogelijk te maken. Voor zoekmachines die geen ondersteuning bieden aan dit URL-hashschema, blijven de URL's met een fragmentidentifier onzichtbaar.
Als alternatief kunnen applicaties de eerste laadactie van de pagina opbouwen op de server en de daaropvolgende paginawijzigingen op de client. Dit kan weleens lastig zijn, omdat het dan soms nodig is om de weergavecode op de server en op de client in een andere taal te schrijven. De hoeveelheid code die kan worden gedeeld, kan mogelijk worden verhoogd door het gebruiken van templates die geen logica bevatten, het compileren van de ene taal naar de andere (Engels: cross compiling), of het toepassen van dezelfde taal op de server en de client.
Omdat SEO-compatibiliteit in SPA's niet triviaal is, is het vermeldenswaard dat de SPA's doorgaans niet worden gebruikt in een omgeving waarin zoekmachine-indexering een vereiste, of wenselijk is. Toepassingsgebieden omvatten applicaties die privégegevens ophalen die verborgen zijn achter een authenticatiesysteem. In de gevallen waarin deze applicaties consumentenproducten zijn, wordt vaak een klassiek paginamodel gebruikt voor de homepage en de verkooppagina, die voldoende metadata verschaffen om als een 'hit' in de zoekmachine te verschijnen. Blogs, ondersteuningsforums en andere pagina's met een traditionele opbouw bevinden zich vaak naast een SPA en kunnen zoekmachines voorzien van relevante trefwoorden.
Een andere benadering die wordt gebruikt door op servers gerichte frameworks, zoals het op Java gebaseerde ItsNat is alle hypertext op te bouwen op de server met dezelfde taal en template-techniek. Bij deze aanpak kent de server exact de toestand van het DOM op de client; elke grote of kleine paginawijziging wordt gegenereerd op de server en door AJAX getransporteerd, via de exacte JavaScript-code die nodig is om de clientpagina door het uitvoeren van DOM-methoden in de nieuwe toestand te brengen. Ontwikkelaars kunnen beslissen welke paginatoestanden ten behoeve van SEO voor spiders vindbaar moeten zijn en kunnen de vereiste toestand tijdens het laden genereren met pure HTML in plaats van JavaScript. In het geval van het ItsNat-framework gebeurt dit automatisch doordat ItsNat de DOM-boomstructuur op de server bewaart als een Java W3C-DOM-boom. Het opbouwen van deze DOM-boom op de server genereert pure HTML en JavaScript DOM-acties voor AJAX-requests. Deze dualiteit is erg belangrijk voor SEO, omdat ontwikkelaars de gewenste DOM-toestand op de server kunnen bouwen met dezelfde Java-code en templates die op pure HTML gebaseerd is. Op het moment dat de pagina wordt geladen, wordt door ItsNat conventionele HTML-code gegenereerd waardoor de DOM-toestand SEO-compatibel wordt. Vanaf versie 1.3 voorziet ItsNat in een nieuwe toestandsloze (Engels: stateless) modus; het client-DOM wordt niet op de server bewaard, want in de toestandsloze modus wordt het client-DOM gedeeltelijk of volledig op de server gereconstrueerd bij het verwerken van elk AJAX-request, gebaseerd op vereiste gegevens die door de client worden verzonden met informatie over de huidige DOM-toestand. De toestandsloze modus kan ook SEO-compatibel zijn omdat SEO-compatibiliteit gebeurt tijdens het laden van de eerste pagina, niet beïnvloed door toestandsrijke of toestandsloze modi.
Er zijn enkele oplossingen die ervoor zorgen dat het lijkt alsof de website doorzoekbaar is. In beide gevallen is het noodzakelijk om aparte HTML-pagina's te creëren die de inhoud van de SPA weerspiegelen. De server kan een op HTML gebaseerde versie van de website opleveren aan spiders. Ook is het mogelijk om een headless webbrowser zoals Phantom JS te gebruiken (dat wil zeggen een webbrowser zonder grafische gebruikersinterface), om de JavaScript-applicatie te laten draaien en de hieruit resulterende HTML als uitvoer op te leveren.
Beide oplossingen vereisen nogal wat inspanning en kunnen uiteindelijk problemen opleveren bij het onderhoud van grote complexe websites. Er zijn ook potentiële valkuilen voor SEO. Als de HTML die door de server wordt gegenereerd te veel verschilt van de SPA-inhoud, dan zal de website een 'strafsanctie' krijgen. Bovendien kan de verwerking van PhantomJS voor het opleveren van de HTML de reactiesnelheid van de pagina's vertragen, een eigenschap waarvoor zoekmachines - Google in het bijzonder - de classificatie van een website verlagen.

### Partitionering van client/server-code

Een manier om de hoeveelheid code die tussen servers en clients kan worden gedeeld te verhogen, is het gebruik van een template-taal met minder logica, zoals Mustache of Handlebars. Dergelijke templates kunnen worden opgebouwd vanuit verschillende host-talen, zoals Ruby op de server en JavaScript op de client. Uitsluitend het delen van templates vereist echter meestal duplicatie van de bedrijfslogica voor het selecteren van de juiste templates en het vullen van de templates met data. Het opbouwen vanuit templates kan negatieve gevolgen hebben voor de reactiesnelheid wanneer slechts een klein deel van de pagina wordt bijgewerkt, zoals het invullen van een waarde van een tekst in een groter template. Het vervangen van een compleet template kan ook een door de gebruiker gemaakte selectie of de cursorpositie beïnvloeden, wat niet zou gebeuren als uitsluitend de gewijzigde waarde zou worden bijgewerkt. Om deze problemen te vermijden, kunnen applicaties data-koppeling van de gebruikersinterface toepassen of een verfijnde manipulatie van het DOM uitvoeren, om alleen de betreffende gedeelten van de pagina te actualiseren in plaats van het opnieuw opbouwen van volledige templates.

### Browsergeschiedenis

Aangezien een SPA per definitie een enkele pagina bevat, doorbreekt dit model het ontwerp van de browser voor wat betreft de geschiedenisnavigatie met de knoppen Volgende/Vorige. Dit vermindert de bruikbaarheid wanneer een gebruiker op de Terug-knop drukt in de verwachting dat de vorige schermtoestand binnen de SPA verschijnt, maar in plaats daarvan de enkele pagina verdwijnt en de vorige pagina in de geschiedenis van de browser wordt gepresenteerd.
De traditionele oplossing voor SPA's is geweest om de hash-Fragmentidentifier van de browser-URL te wijzigen conform de huidige toestand van het scherm. Dit kan gebeuren met behulp van JavaScript en zorgt ervoor dat geschiedenisevenementen met URL's worden opgebouwd binnen de browser. Zolang de SPA in staat is om dezelfde schermtoestand opnieuw op te bouwen op basis van informatie in de URL-hash, wordt het verwachte gedrag van de Terug-knop behouden.
Om dit probleem verder aan te pakken, heeft de HTML5-specificatie de methoden pushState en replaceState geïntroduceerd om programmatisch toegang te verstrekken tot de URL- en browsergeschiedenis.

### Analysehulpmiddelen

Analysehulpmiddelen zoals Google Analytics zijn sterk afhankelijk van geheel nieuwe pagina's die in de browser worden geladen, geïnitieerd door een URL-wijziging. SPA's werken niet op deze manier.
Na het laden van de eerste pagina worden alle volgende wijzigingen van de pagina en de inhoud afgehandeld door de applicatie. De browser initieert daardoor nooit een nieuwe laadactie van de pagina, er wordt niets aan de browsergeschiedenis toegevoegd en het analysepakket heeft geen idee wat er op de website gebeurt.

#### Pagina-laadacties toevoegen aan een SPA

Het is mogelijk om pagina-laadacties aan een SPA toe te voegen met behulp van de geschiedenis-API van HTML5; dit zal helpen bij het integreren van de analysegegevens. De moeilijkheid zit in het beheer hiervan en het verzekeren dat alles nauwkeurig wordt bijgehouden; dit houdt ook in het controleren op ontbrekende gegevens en dubbele vermeldingen. Het goede nieuws is dat er geen behoefte is om alles van de grond af op te bouwen. Voor AngularJS zijn bijvoorbeeld verschillende open source analysehulpmiddelen online beschikbaar, gericht op de meeste grote aanbieders van analysehulpmiddelen. Ontwikkelaars moeten deze in de applicatie integreren en ervoor zorgen dat alles goed werkt, maar er is geen noodzaak om alles zelf te bouwen.

### Snelheid van de eerste laadactie ( = TTFB / Time till first byte )

Single Page Applications hebben in het algemeen een tragere eerste pagina-laadactie dan server-gebaseerde applicaties. Dit komt doordat de eerste laadactie het framework en de applicatiecode moet ophalen, voordat de vereiste view als HTML in de browser wordt opgebouwd. Een server-gebaseerde applicatie hoeft uitsluitend de benodigde HTML naar de browser te verzenden, dit reduceert de wachttijd en downloadtijd.

#### Versnelling van het laden van de pagina

Er zijn enkele manieren om het initiële laden van een SPA te versnellen, zoals een specifieke opbouw van caching, of het laden van modules uitstellen tot wanneer ze nodig zijn (dit wordt ook wel dynamisch laden genoemd, of in Engels: lazy loading). Het is echter niet mogelijk om het downloaden van het framework te vermijden, plus in elk geval een gedeelte van de applicatiecode. Bovendien zal waarschijnlijk een API benaderd worden om data op te halen, voordat het mogelijk is om iets in de browser weer te geven.

## Pagina-levenscyclus

Een SPA wordt bij de eerste pagina-laadactie volledig geladen en daarna worden pagina's vervangen of aangevuld met nieuwe paginafragmenten die op aanvraag vanaf de server worden geladen. Om overmatig downloaden van ongebruikte functies te vermijden, zal een SPA vaak geleidelijk meer functies downloaden als ze noodzakelijk worden, ofwel kleine fragmenten van de pagina, of volledige schermcomponenten.
Op deze manier bestaat er een analogie tussen toestanden in een SPA en pagina's in een traditionele website. Omdat toestandsnavigatie op dezelfde pagina analoog is aan paginanavigatie, kan in theorie een pagina-gebaseerde website worden omgezet naar een enkele pagina, waarbij op dezelfde pagina alleen de gewijzigde gedeelten worden getoond, vergelijkbaar met de verschillen tussen opeenvolgende pagina's in een niet-SPA.
De SPA-aanpak op het web is vergelijkbaar met de Single Document Interface (SDI) presentatietechniek die populair is in desktopapplicaties.

## Literatuur

(en) Flanagan, David (april 2011). JavaScript: The Definitive Guide. O'Reilly Media, blz. 1096,. ISBN 978-0-596-80552-4.