Technology

3 Approaches to Single Page Applications on Sitecore 8.x

News

Over the years we have witnessed increasing demand for websites that deliver experiences akin to native mobile applications. Coupled with the complexities of implementing websites on powerful marketing platforms such as Sitecore, where WYSIWYG interfaces are commonplace, this presents a challenge for developers. 

Many modern websites use a single-page application (SPA) approach to provide a more fluent user experience, similar to a desktop or mobile application.

Unlike a traditional web application when for each request the web server renders a new HTML page; in a SPA, after the first page load all the further interactions with the web server happen on the client side through AJAX calls returning data for the partial page update, without a full reload. Data returning by AJAX calls is usually in JSON format, but simple HTML markup is also acceptable for partial renderings.

A diagram below illustrates the SPA approach in general:

Single page application

The SPA approach gives us two obvious benefits:

  1. More fluid and responsive user experience without page reloads;
  2. Separation between the data presentation (HTML markup) and application logic (AJAX requests plus JSON responses) layers, which makes for smoother design and development workflow.

In this article we will examine three approaches of a SPA implementation on Sitecore. The aim of our exploration is to provide an application-like user experience to the end user, while still maintaining support for the following features:

  • Sitecore Experience Editor (where we can control personalisation and testing)
  • Sitecore Experience Profile
  • SEO friendliness

For the purpose of this article, we will be focusing on the back-end tactics without the detailed explanation of the front-end implementation.

1. Sitecore as a Content Database

Let's start from a simple option of using Sitecore as content storage only, without any presentation details such as layouts and renderings. The actual layout in this case will be be implemented by our front-end code.

We can choose any fancy new JavaScipt MVC/MVVM framework to create a web application - Angular, Ember, Backbone, you name it. Our application will call a REST web service in order to retrieve a Sitecore item in JSON format, for this we can either use the Sitecore Item Web API (included in Sitecore 7.1+) or create our own web API to pull data from Sitecore and then transform them, or use both.

This is a classic SPA approach where the resultant HTML does not contain the actual page content, but instead references to JavaScript & CSS. This is what prevents the page from being indexable by search engines.

The application life-cycle for such an implementation would be as follows:

  1. User opens a web application page
  2. Browser loads & initiates AngularJS web application
  3. Web application loads Angular layout templates
  4. Web application calls Sitecore Item Web API to retrieve the Sitecore items data
  5. Web application renders the received JSON data in browser using the layout

This approach might be suitable if you already have a Sitecore powered website and you now want to create a new SPA that will reuse some of your existing Sitecore content.

For example, you have an existing Sitecore website and you would like to deploy a new campaign microsite. This new microsite looks like it will make use of a fair amount of your existing content from the main website. In this scenario, it makes perfect sense to reuse the existing Sitecore items on your main website, and retrieve their content via the API.

This approach has the following advantages and disadvantages:

Pros

  1. Ease of implementation
    Custom REST service implementation to pull data from Sitecore and return in JSON format is a simple task for a .NET developer. Even for the sake of simplicity the default Sitecore Item Web API can be used instead of writing a bespoke server-side code.
  2. Can use any front end framework
    If your UI developers are experienced with a particular framework then they will thank you. Frameworks boost development productivity and give access to the huge library of the open source plugins & controls available on Github.
  3. Separation of concerns
    REST service acts as a business logic layer and JavaScript application as a presentation layer. This reduces overall solution complexity and improves the code maintainability.
  4. An additional opportunity
    Development of a custom REST service makes it easier to then create mobile applications based on the same methods of content distribution.

Cons

  1. Experience Editor is not supported.
    Sitecore items do not have any presentation details, so we cannot view and edit them in Experience Editor. All content can be managed in Content Editor only.
  2. Personalisation and A/B Testing are not supported.
    Sitecore implements them by altering item presentation details on the fly. We do not use the item presentation here, so this functionality is not supported.
  3. Experience Analytics will require custom tracking code
    User actions will not be tracked by Sitecore automatically. Tracking needs to be implemented by custom handlers, see an example here.
  4. Not SEO friendly.
    We end up with HTML content being rendered on the client side, so the content cannot be indexed by search engines.
2. Server-side Rendering

In this approach, we'll give Sitecore more responsibility. Along with acting as a data storage, Sitecore will also control the presentation layer for pages, so the server will return back the full page HTML. To pull the page HTML and update its content in a browser accordingly, without the entire page reload, we will add a script that enables URL routing in the browser - this could be a custom module or some library, for example; PJAX.

So, the application life-cycle will be as follows:

  1. User navigates to a page in a browser
  2. Sitecore returns the full page HTML based on the item presentation details defined in Sitecore
  3. Browser displays the page to the user and initiates URL router in the browser
  4. When the user clicks on a link to navigate to another page, the URL routing script loads the page HTML from Sitecore in the background via an AJAX request
  5. The URL router updates a part of the page with received HTML

On the server side we can identify AJAX requests and serve them by a lighter layout version to reduce the HTML size for transferring.

To enable editing in Experience Editor we will disable the browser side routing.

This approach has the following advantages and disadvantages:

Pros

  1. Ease of implementation
    This approach is easy to start with because it does not require any complex front-end logic implementation. You can use this approach to speed up and enhance the existing website to support SPA behaviour.
  2. All Sitecore features support: Experience Editor, Experience Analytics, A/B testing & Personalisation without any custom code.
    All page requests go through the standard Sitecore page rendering pipeline, therefore all Sitecore features work out-of-the-box.
  3. SEO friendly
    Server returns the full page HTML for all pages, so nothing prevents search engines from proper indexation.

Cons

  1. Front-end related logic spread across the JavaScript and C# code
    This approach is more preferable for content sites that don't have too many complex user interactions. Development of more complex web application style interactions can prove more difficult and time consuming.
  2. Limited choice of JavaScript frameworks
    With Sitecore driving the page presentation you are limited to only the libraries without a presentation component, such as Backbone (which is still good!)
  3. Bigger server response size
    HTML returning by the server is usually bigger than the same data in JSON format.
3. Mustache Templates with JSON Page Model

From our experience the two approaches described above are still not ideal. We wanted to adopt a more elegant approach that would be SEO friendly, support all Sitecore features and still provide a clear separation between front-end and backend code responsibilities.

We found the best solution to be the use of Mustache templates for HTML renderings on both server and browser side.  

The application life-cycle slightly differs in comparison with the second option and is as follows:

  1. User navigates to a page in a browser
  2. Sitecore returns the full page HTML based on the item presentation details defined in Sitecore - Mustache template is used inside MVC rendering
  3. Browser displays the page to the user and initiates the URL router Javascript in browser
  4. When user clicks on some link to navigate to another page the URL router captures this event and loads the required JSON page model from Sitecore in the background by an AJAX request
  5. URL router renders JSON page model by Mustache template
Single page application mustache templates

Below we will review the renderings in detail, starting from the server side:

We use the Controller renderings for all renderings. The controller code itself is almost trivial - it only uses Glass Mapper to map the Content Item fields to a strongly typed model and then returns the result:

CaseStudyController.cs

public class CaseStudyController : BaseController { 	public CaseStudyController(IEditorFactory editorFactory) : base(editorFactory) 	{ 	}  	public ActionResult Intro() 	{ 		var model = GetContextItem<casestudymodel>(); 		return Nustache("/assets/templates/components/CaseStudyIntro.html", model); 	}  	public ActionResult Footer() 	{ 		var model = GetContextItem<casestudymodel>(); 		return Nustache("/assets/templates/components/CaseStudiesFooter.html", model); 	} }

The controller action returns a NustacheActionResult object - our custom ActionResult that renders a specific Mustache template using Nustache library:

NustcheActionResult.cs

public class NustacheActionResult<t> : ActionResult { 	protected string Template; 	protected T Model;  	public NustacheActionResult(string template, T model) 	{ 		Template = template; 		Model = model; 	}  	public override void ExecuteResult(ControllerContext context) 	{ 		HttpContextBase httpContext = context.HttpContext; 		var html = new NustacheEngine(p => httpContext.Server.MapPath(p)).ProcessTemplate(Template, Model); 		httpContext.Response.Write(html); 	} } 

Here is a BaseController code inherited by all other controllers:

BaseController.cs

public class BaseController : GlassController { 	protected IEditorFactory EditorFactory;   	public BaseController(IEditorFactory editorFactory) 	{ 		EditorFactory = editorFactory; 	}   	public virtual T GetDataSourceItem<t>() where T : class 	{ 		var strategy = RenderingDataStrategyManager.GetStrategy(RenderingContext.Current.Rendering); 		var model = strategy.GetRenderingDataSource().CastTo<t>(); 		if (model == null) 		{ 			return null; 		} 		return Context.PageMode.IsPageEditorEditing ? EditorFactory.Create(model).GetEditableModel() : model; 	}   	public virtual T GetContextItem<t>() where T : class 	{ 		var model = base.GetContextItem<t>(); 		return Context.PageMode.IsPageEditorEditing ? EditorFactory.Create(model).GetEditableModel() : model; 	}   	protected override void HandleUnknownAction(string actionName) 	{ 		Redirect("/404/"); 	}   	protected virtual ActionResult Json(IContentItem model) 	{ 		var jsonModel = JsonConvert.SerializeObject(model);   		return Content(jsonModel, "application/json"); 	}   	protected virtual ActionResult Nustache<t>(string template, T model) 	{ 		if (model == null) 		{ 			return Content("<p>Please set data source for this component.</p>"); 		} 		return new NustacheActionResult<t>(template, model); 	} }

Now it is time to look at the browser side and see what happens there. For example, a user clicked a link going to a case study page with URL '/case-study/1234', our custom URL router captures that click and makes an AJAX request to that URL, instead of navigating to it directly.

For this need we have extended "mvc.requestBegin" pipeline in Sitecore with an additional processor to catch all AJAX requests:

AbortSitecoreForAjaxRequests.cs

public class AbortSitecoreForAjaxRequests : RequestBeginProcessor { 	private List<string> excludePaths = new List<string> { "/sitecore", "/api" }; 	public override void Process(RequestBeginArgs args) 	{ 		var context = args.RequestContext.HttpContext; 		if (!context.Request.IsAjaxRequest() || Context.Item == null || !Context.PageMode.IsNormal || Context.Site.Name != "website" || excludePaths.Any( x => context.Request.Url.AbsolutePath.StartsWith(x))) 		{ 			return; 		}   		context.Response.ContentType = "application/json"; 		context.Response.Write(JsonConvert.SerializeObject(new JsonPageModel(Context.Item, args.PageContext))); 		context.Response.End(); 	} } 

This takes a Sitecore item and then maps its data and presentation details (layout and renderings) to a JSON page model, as per the following example:

Data mapping model

{ 	"data": 	{ 		"title":"Delete Website.", 		"url":"/work/delete", 		"brandName":"Delete", 		"brandSubtitle":"Website & Communication Strategy", 		"subtitle":"Project subtitle goes here.", 		"year":"2015", 		"imgurl":"/~/media/images/case-studies/delete/woohoo.jpg", 		"mainNavigation": 		[ 			{"linkText":"Work","cssClass":"","linkUrl":"/work"}, 			{"linkText":"Agency","cssClass":"","linkUrl":"/about"}, 			{"linkText":"Services","cssClass":"","linkUrl":"/services"}, 			{"linkText":"Careers","cssClass":"","linkUrl":"/careers"} 		], 		"modules": 		{ 			"banner9c0645197a114a15912bfd8ed84d7fff": 			{ 				"text":"Lorem ipsum", 				"id":"9c064519-7a11-4a15-912b-fd8ed84d7fff", 				"title":"Hero" , 				"img": "/~/media/images/case-studies/delete/banner.jpg" 			}, 			"text718d7b1756a04b7ab14c7f39b7af45b7": 			{ 				"text":"<section><h2>Summary</h2><p>Lorem ipsum dolor sit amet, consectetur adipisicing elit.</p><section>", 				"id":"718d7b17-56a0-4b7a-b14c-7f39b7af45b7", 				"name":"Text" 			} 		} 	} } , "layout": 	"<div class="\case-study\"> 		<div class="\case-study--container\"> 			{{#modules}} 				{{#banner9c0645197a114a15912bfd8ed84d7fff}} 					{{>banner}} 				{{/banner9c0645197a114a15912bfd8ed84d7fff}} 				{{>caseStudyIntro}} 				{{#text718d7b1756a04b7ab14c7f39b7af45b7}} 					{{>text}} 				{{/text718d7b1756a04b7ab14c7f39b7af45b7}} 			{{/modules}} 		</div> 	</div> 	{{>caseStudiesFooter}}" } 

Therefore:

  • data - contains the content item fields and common data for multiple pages such as navigation
  • data.modules - contains data for each rendering that has the data source
  • layout - Mustache template for the page, each rendering is added to corresponding placeholder in the template as {{>renderingName}}

For more details about how we populate the structure above with the actual data please see JsonPageModel.cs and JsonRenderingModel.cs classes attached.

JsonPageModel runs the Personalize pipeline for each rendering to enable personalisation and A/B testing.

In order to support Experience Editor mode we have a different set of Glass Mapper models where fields are wrapped into Editor controls. So, when you open a page for editing you get the same WYSIWYG editors as you do in the case of @Sitecore.Field() helper.

This approach has the following advantages and disadvantages:

Pros

  1. SEO friendly
    HTML rendering on the server side gives full control over content structure and as a result allows pages be indexed by search engine bots.
  2. Support of personalisation, A/B testing and Experience Editor.
    From the content management side everything works as your would expect from a Sitecore user perspective.
  3. Better page load time.
    Using the JSON model reduces the transfer data size between Sitecore and the browser. This naturally leads to a smoother and faster browser experience, though the initial page load will be a little slower as it will first need to load all of the Mustache templates.

Cons

  1. Complex server side logic
    This method requires more advanced knowledge of both back-end and front-end programming (full stack development knowledge).

We have employed the second and third approaches on our own projects. Although we found that the third option delivers the best results, it's important to note the need for full stack development knowledge. You need to have a very good understanding of Sitecore and be confident working in both backend (C#) and front-end (JavaScript) code. The second option is still a decent fallback method.

Kontakt aufnehmen
Anfragen

Delete Limited.

Registered in England.

03933385

Registered Address.

3370 Century Way, Thorpe Park, Leeds, LS15 8ZB

VAT Registration.

GB 927 1409 27