Hybrid rendering with Java 8 and Handlebars

Single Page Applications (SPA) with all due respect, but they are not the best choice for all type of applications. Especially not when it comes to performance.

A typical SPA consists of one HTML-file with only a link and a script tag. The web server only serves static content and the client talks to the server via a REST/JSON API.

So, a typical GET-request to a SPA becomes:

  1. The browser downloads the HTML-file, and executes the script.
  2. The script creates a HTTP-request to fetch some JSON/data from the server.
  3. The script renders JSON and create DOM elements (usually also by doing other HTTP-request(s) to get template(s)).
  4. Done! Start JS-app, route, render, handle interactions, whatever.

This needs to be done for every full page load. Of course, the point of SPA is to never reload the pages, ever. But when you do, all HTTP-requests, parsing, rendering and binding could cause a FOUC (Flash of Unstyled Content). And all HTTP-requests makes the initial page load slower.

Initial server rendering

The web server can send rendered HTML-markup in the first request so that we can show something instantly for the user while the script files are downloading in the background.

So, a typical GET-request becomes:

  1. The browser downloads the HTML-file, that contains rendered markup and displays it to the user.
  2. Scripts download in the background
  3. Done! Start JS-app, route, render, handle interactions, whatever.

The user will not experience any FOUC and should be able to interact with the application immediately.

One disadvantage with server rendering and clientside rendering is that you usually have two rendering languages. For example:

  • Razor on the server, Lodash templates on the client.
  • PHP on the server, jQuery templates on the client.
  • JSP on the server, Handlebars templates on the client.

etc.

This means that you need to know and maintain different rendering languages.

Hybrid rendering

So, why not use the same templates on both client and server?
This way, we only need to maintain one template language. I found a Java implementation for Handlebars that I used togheter with (JAX-RS) REST-services.

By using Handlebars.java on the server and Handlebars.js on the client, the same templates could be shared between server and client.

Here is how we render a Handlebar-template on the server with Handlebars.java (in production, we should cache the template creation at startup):

private Template createHandlebarsTemplate() throws IOException {  
  TemplateLoader loader = new  ServletContextTemplateLoader(context);
  // Where we have our handlebars templates
  loader.setPrefix("/templates/person/");
  // Prefix
  loader.setSuffix(".html");
  Handlebars handlebars = new Handlebars(loader);
  return handlebars.compile("persons");
}

Here is how we get a person as JSON, get a template, and render the person with the template on the client (in production, we should cache the template (in memory or in localStorage etc) when we start the application):

// Code for demo purpose. Can be written more efficient and compact.
$.ajax({ url: '/persons/1', contentType: 'application/json' })
 .then(displayPerson);

function displayPerson(person) {  
  // Should only be done once
  var promise = $.get('/templates/person/persons.html');

  promise
   .then(function(html) {
     // Should only be done once
     return Handlebars.compile(html);
   })
   .then(function(template) {
     var html = template({
       person: person
     });

     // Use HTML5 History API to update the URL in the browser.
     history.replaceState(null, '', '/persons/' + person.userId);
     $personContainer.html(html);
  });
}

Here is an example of a GET-request. We are using the same method findById, but we return JSON if the client sends header application/json or HTML if the client sends something else.

@GET
@Path("persons/{userId}")
public Response findById(@HeaderParam("Content-Type") String mediaType, @PathParam("userId") String userId) throws IOException {  
    Person person = persons.stream()
                           .filter(p -> p.getUserId().equals(userId))
                           .findFirst()
                           .orElse(null);

    // mediaType = application/json. Return JSON.
    if (isJSON(mediaType)) {
      return responseJSON(person);
    }

    // mediaType = something else, render person and return HTML.
    return responseHTML(createHandlebarsTemplate().apply(person));
}

Now, we can create pages that renders fast with templates that works both on the client and the server.
And we only need to use one REST-API that can send both HTML and JSON. This way we can reuse our API if we need to build e.g an mobile native-app against our application.

Here is a complete demo of the *application: https://handlebars-demo.herokuapp.com
In the demo, when you click on a person. We are fetching the person details as JSON, and render the result on the client. The persons can be sorted by name or age by clicking on the table header links. Note that the URL-changes for every click, and that the state is kept if you are doing i full refresh (CTRL+F5 / CTRL+R).

Demo code: https://github.com/nekman/handlebars-demo

(*If it dosen't respond for a while, that is beacuse the app is sleeping in the heroku cloud, and may need some time to start up).

References: