Benjamin Howarth

Code Gecko

Introducing RomanSPA

In 2013, whilst splitting my time between London & Chicago, John Papa released a couple of starter kits for building single-page applications, which he nicknamed the "Hot Towel SPA" starter kits. He did a public Q&A on the ASP.NET forums, when someone asked him a very interesting question about the general SPA design pattern (which is generally an MVVM-style pattern - AngularJS is not MVC, it's MVVM/MVx, & I'll happily debate that at length with anyone). The question was:

"Could someone think of a scenario for the web project when one will not want to have a Single Page Application?"

John's answer:

"One example would be a web site. Lots of static data for presentation in regions or areas. This could be done with client side templating, but then you lose a lot of SEO too, and web sites are often were SEO is important. (In contrast, a SPA for a banking app is not a place you'd want SEO). So there are 2 examples: web sites and any place you need SEO badly. There are ways to do SEO in SPA, but its not a natural fit."

At which point, I interjected:

"so what would you recommend if you want to create a web site with SEO and SPA data-rich features (like Twitter)? [...] i.e. I want to re-do my website, mob[ile]-friendly UI, with some SPA features for OSS projects. Do I go MVC3 w[ith] JS extensions or SPA?"

John's response was:

"I dont have a great answer for you on SEO . Great opportunity awaits there."

At which point, I called shotgun :-)

My simple understanding of SPAs at the time was, in essence, this:

  • Client-side JS has routes, controllers/viewmodels, and views;
  • Server-side MVC code (C#, PHP, whatever) has routes, controllers, and views;
  • So you have MVC on the server, and MVx on the client. Why not share the M, the V, and the routes?

The first part was simple - forcing the JS frameworks to load views through the ASP.NET pipeline. This was fairly straightforward, as both AngularJS and DurandalJS provide interception points to modify the request to the view before it's sent to the server. At this point, I hijacked the request and added an extra header. On the server, this header was used to determine whether the view request was "cold" (i.e. first page load - therefore, required layout, CSS, JS, assets, etc.), or "warm" (loaded via AJAX through the SPA framework in place - thus could be loaded as a partial with no layout specified).

The next part was to export the routes from server-side MVC to client-side JS. This wasn't straightforward, and the implementation still isn't perfect, but it does work.

The main caveat is that the JS framework has to make an AJAX call to the server to retrieve the routes for the website before the app hits "runtime" mode. This means that you can't use an AJAX request, because AJAX = app runs before routes are loaded = empty app. So, you have to force SJAX - synchronous thread-blocking call to the server. Ugly as sin, but necessary for the context.

Once the routes are populated, last up is specifying the JS controller for the JS view. In the sample project I've got on Github, I've created a base viewmodel in KnockoutJS and a base controller in AngularJS, which can be extended to support custom functionality such as offline storage.

So, the final request pipeline looks like this:

  • Request to server
  • Server responds with full page load
  • JS SPA framework boots up
  • JS SPA framework loads routes from server (1 SJAX request)
  • JS SPA framework identifies current route
  • JS SPA framework requests current URL twice, each with separate headers - one for view, one for JS model (2 AJAX requests)
  • Server receives requests, ASP.NET action filters intercept & deliver JSON model or HTML view depending on header present
  • JS receives responses from server, binds view & model and renders to browser.

Why would you do this? One initial request + JS files + at least 2, maybe 3 AJAX requests (routes, model and view), just to get that "shiny" app feel?

Our first job as web developers is to deliver content. Whilst Google has recently started to index content loaded with Javascript, our first concern should be to deliver content as quickly as possible, and to make sure that the content we deliver is accessible to the widest possible audience. RomanSPA allows us to create websites that do what they're supposed to - deliver content - whilst giving us a toolset to provide an enhanced experience for those visitors that can support it.

Before I began developing RomanSPA, I sat down and had drinks with my great friend Sebastian Lambla, and discussed the possibility of progressive SPAs. He opined that SPAs undermined the fundamental function of the web - to deliver content to users - because an SPA resulted in x-hundred KB of Javascript libraries before you actually got to the content, which is the primary reason you visited the site. RomanSPA guarantees that your site will still deliver content to visitors and search engines, even if your Javascript fails - which means it still meets minimum accessibility requirements.

The entire project is available on GitHub to download & road-test. To get a working sample site up and running, simply F5-run the RomanSPA. Demo.AngularJS project in Visual Studio. I also spoke about RomanSPA last month at the Umbraco UK Festival in London, and the slides are available here. I will also release packages onto Nuget soon!