5 Single Page Application (SPA) Best Practices
Customers have high expectations for client application experiences. Much has been written in the debate between native vs HTML5 applications in recent years. One thing that is certain native mobile applications have raised the expectations bar. Other factors that also contribute to increased expectations include ubiquitous high-speed broadband at home, the office and now on our mobile devices with LTE availability. Users want applications that load and respond quickly. Once loaded they expect snappy responses, smooth transitions and engaging interactions.
Many believe HTML5 does not posses the ability to meet these requirements. Using traditional web site architecture it could never achieve these levels. HTML5 adds many features, and is still growing, developers can leverage to build modern rich web apps that rival and in some cases exceed native app capabilities.
One of the hottest web architecture trends today is Single Page Apps or SPA. This architecture eliminates the traditional request response model the classic web was built upon. Instead it requires only a single page be retrieved from the server. Subsequent views (analogous to traditional pages) are brought in and out of view as needed. The SPA architecture relies heavily upon AJAX requests to pass JSON objects to and from the server to the client to merge with views that present data.
The benefits of a SPA include much less network activity, faster responses to user interactions, smaller server loads. It also makes it possible to use fancy view transitions, which give the appearance of a native application.
Around 6 or 7 years ago the Model View Controller (MVC) architecture went more mainstream and with it came the concept of routing. Not that routing in and of itself has anything to do with MVC, but about the same time search engine friendly URLs gained more traction. Combing to two trends the concept of server-side routing developed. To me, as ASP.NET developer, this meant I could define routes to trigger controllers that would ultimately generate the desired markup and send it on its way to the client.
A SPA is client-heavy and this responsibility must be move to the browser. There are many frameworks available around the web to help with this task and I have built one myself. There are a couple of features a SPA route manager must have:
- Be Simple to Implement
- Handle Route Parameters
|1 2 3 4 5 6 7 8|| |
I want to call your attention to the last data- attribute, data-path. Its value defines the route to display this view and give you the article you wanted. First is what I call the main hook, 'article'. That is followed by a parameter, ':slug'. Notice how each part is split by a \. This is a delimiter my engine uses to parse the route to see if the requested route actually exists. If it does not my engine routes you to a 404 view, otherwise you proceed and display the view and initiate the callback method defined in the data-callback attribute.
Once the route engine identifies the route it parses it for any parameters. In this example the slug is a parameter that acts as the key to query the database for the article's content. This part is ultimately passed to my application's loadArticleView method, which retrieves the JSON either from the server or localStorage and ultimately merges it with the article template. We will learn more about those techniques later.
I did sort of skip a step in the process. When my site if first loaded my SPA architecture does several things. It looks for any new views, parses the data- attributes to create localStorage entries for each one and builds the routing table. This table is used as different views are requested by the routing engine. It's just a piece to the process that makes the Single Page Application architecture work.
Traditionally web developers have relied on the server to generate any dynamic markup a page request needs. This generally does not apply for a single page application, except for the initial page request. Instead a SPA relies more on creating views in the client as they are needed.
A user's session with a web app has to start somewhere, usually the home page. Depending on your application you may need to retrieve data and send it to the browser from the server, but generally this is not done in a SPA. Instead you would return a page's layout sans content. This scares many developers, but they need to realize the content will be added shortly on the client.
Google has defined guidelines to help webmasters still have their sites spidered and properly indexed, https://support.google.com/webmasters/answer/174992?hl=en.
If search engines are not a concern then return the initial page without content. The initial page's payload also contains the markup for the application's remaining views. This includes markup layouts for each view and potential script templates used to build a pages content on demand.
For smaller applications it is fine to include the entire application's markup. For larger applications like those found in the enterprise you may consider loading additional views and templates asynchronously once the initial page has been loaded. Now the application's markup has been loaded it can reference each view's markup.
Be sure to hide non-visible views. This can be done with CSS rules. It is a good idea to apply a common CSS rule to each view's main element and an additional rule to indicate the current view. These view indicate if the view is displayed or hidden. Of course you could define other layout specific rules at your discretion, but as far as the SPA architecture is concerned a view is either hidden or visible.
|1 2 3 4 5 6|| |
|1 2|| |
Swapping the classes is not good enough. You will also want to make sure the new view is placed after the existing displayed view in the DOM. The following simple method to perform a view swap, using the above CSS class name technique as well as making sure the new view is placed after the existing view.
|1 2 3 4 5 6 7 8|| |
Data Retrieval And Storage
|1 2 3 4 5 6 7 8 9|| |
The dataType is set to 'json' which tells the server end point why data format you are expecting. If you are using ASP.NET Web API this will automatically be handled for you. If you are using some other server technology you may need to check with its documentation on how to return JSON formatted data.
If you are calling an endpoint on a 3rd party domain then you will probably need to set the dataType to 'jsonp'. This is a common hack to make cross-domain GET calls. As modern browsers become common you can start relying on CORS, which is generally a more secure way to make cross-domain calls. Joel Cochoran details how to work with Web API and CORS, http://dotnet.dzone.com/articles/cors-ajax-and-post-data-web.
Template libraries make is possible to merge a JSON object with a template to produce markup combined with content. The following is a simple Handlebars template, it merges an array of movie object to produce a list of movies.
|1 2 3 4 5 6 7 8|| |
The merged markup can now be added inside it's parent element using the parent's innerHTML property. You could also use any of the jQuery DOM manipulation functions like appendTo, before and after. The important point is you have a viable way to create the necessary DOM based on the actual data and render it to the user in the correct place.
Using WebStorage To Persist Data
Modern browsers all support web storage such as localStorage and sessionStorage. Internet Explorer, Firefox and Chrome support IndexDB, a light document database in the browser. Safari supports the deprecated WebSQL standard. localStorage and sessionStorage typically offer 5MB of client-side storage in a fast hash table mechanism. IndexDB offers up to 50MB of storage with more advanced querying capabilities. A study recently performed by the Financial Times developer team discovered Internet Explorer 10 gives you up to 500MB of IndexDB and 10MB of localStorage, ample for any modern application to sprawl as will, http://labs.ft.com/2012/06/text-re-encoding-for-optimising-storage-capacity-in-the-browser/.
Using these storage technologies as a persistent storage for AJAX calls reduces the amount of server-round trips a web application makes over its lifetime. Studies have shown around 95% of the data retrieved via AJAX or to build a page has already been retrieved by the client since the data was last updated. This means there are a lot of wasted trips back and forth to the server. This makes the client application slower and adds unnecessary burden on the server infrastructure.
An easy way to persist data to localStorage using jQuery AJAX is the localStorage AjaxPrefilter written by Paul Irish, http://bit.ly/117p7E9. The preFilter checks to see if the requested data is already locally stored before making the call to the server. If it is stored it returns the cached data and cancels the server call. If the data does not exist or is stale it will call the server and cache the response for future requests.
Caching data is a great way to speed up an application, but you can also cache markup in localStorage. This is a technique originally made popular by Bing and Google, http://bit.ly/117puyn. This technique involves persisting the markup and templates needed to build application views in web storage. The view markup and templates are retrieved as needed to build each view on demand. This reduces the size of the application's DOM, which means you will avoid common performance issues created for larger SPA applications with many views and templates. It also reduces the amount of memory needed to run your application in the browser, which on mobile devices is very crucial.
It is a good idea to combine the markup caching with Application cache or offline mode, but don't rely on it as your only caching source. The reason Google and Bing started using this technique was to get around some of the issues with Application cache, http://bit.ly/117q27A. While application cache is also useful to make SPA applications perform better, it can be intrusive to the end user. By persisting markup in storage you can get around some of these limitations. This is also a good technique to use with native applications because it gets around the need to go through a native app store approval process to push updates to clients.
Deep Linking and History Management
A Single Page application can work just fine on a single URL, but this is not a good idea practice even for the simplest web applications. Since hyper linking was first there has been a hash or url fragment available. Typically it is designated with the #. Back in the Mosaic days the url fragment was used to designate what part of the page to display. Typically an anchor tag would be in the markup with a name property, like this:
I used this technique with several research papers I published when I was in graduation school. I would place a table of contents in the top portion of my paper with links to the corresponding section. This saved the user from scrolling down the page to find the section they cared about. You can still see this technique in use with the W3C standards, for example http://dev.w3.org/html5/webstorage/#the-localstorage-attribute.
The History management API, http://www.whatwg.org/specs/web-apps/current-work/multipage/history.html#the-history-interface, is also supported by modern browsers. However I have not really found this useful for Single Page Apps. When using the web storage concepts described earlier it just adds unnecessary complexity to the application architecture. I have found it better to write a custom history management layer, if needed for my SPA applications, but the techniques I have already described meet the majority of cases I have found so far. You can still use the history members to navigate backwards and forwards, but the state management pieces add no value. If you do not want to use the localStorage technique then the history state management methods can be very helpful.
You do want to take advantage of url fragments to trigger view changes and provide deep linking to views. When the hash fragment changes the window triggers the 'hashchange' event. Binding to this event gives you the hook you need to trigger the appropriate methods to swap the view and retrieve and merge data to produce the complete target view. It also provides a unique url the user can bookmark and share with other users.
Single page applications are an exciting new web application technique. When properly built web developers can build native like client application experiences for users. In many cases this eliminates the need to build platform specific client applications resulting in a fragmented, expensive and hard to maintain code base. Single Page architecture is new and therefor does not have many great technical references available. But many, like myself have begun pioneering this technique in many production applications and are rapidly building a solid set of best practices based on our real-world experiences. This article highlighted several SPA best practices that should help you get started on the the path to rich client application success.