Many companies have services that help build and manage applications, which can save a great deal of development and maintenance. If we’re a smaller operation, we may want to take advantage of some of these services. Three important services—site analytics, client logging, and CDNs—are particularly important for SPA development.
9.2.1 Site analytics
An important tool in the web developer’s toolbelt is the ability to acquire analytics about the site they’re working on. With traditional websites, developers have come to depend on tools like Google Analytics and New Relic to provide detailed analysis of how people are using the sites and to find any bottlenecks in application or business performance (how effectively the site is generating sales). A slightly different approach using the same tools will make them every bit as effective on an SPA.
Google Analytics provides a simple way to get statistics about how popular our SPA and its various states are, as well as how traffic is coming to our site. We can use Google Analytics in a traditional website by pasting a snippet of JavaScript code onto every HTML page on the site and making a few small modifications to categorize pages. We could use this approach with our SPA, but then we’d only get analytics on the initial page load. There are two paths we can use to enable our SPA to take full advantage of Google Analytics:
1 Use Google Events to track hashtag changes
2 Use Node.js to record server-side We’ll begin by looking at Google Events.
GOOGLE EVENTS
Google has long recognized the need to record and classify events on pages—SPA development may be fairly new, but Ajax has been around a long time (in web years, a really long time... since 1999!). Tracking events is easy, though it’s more manual work then tracking page views. In a traditional website, the snippet of JavaScript code makes a call to _trackPageView on the _gaq object. It allows us to pass in custom vari- ables to set information about the page the snippet is on. That call sends the informa- tion to Google by requesting an image and passing along parameters on the end of the request. Those parameters are used by Google’s servers to process information about that page view. Using Google Events makes a different call on the _gaq object: it calls _trackEvent and takes some parameters. _trackEvent then loads an image with some parameters on the end of it that Google uses to process the information about that event.
The steps to set up and use event tracking are fairly straightforward:
1 Set up tracking for our site on the Google Analytics site.
2 Call the _trackEvent method.
3 View the reports.
The _trackEvent method takes two required parameters and three optional ones:
_trackEvent(category, action, opt_label, opt_value, opt_noninteraction)
The parameter details are:
■ category is required and is used to name the group of events this belongs to. It will show up in our reporting to categorize events.
■ action is required and defines the specific action we’re tracking with each event.
■ opt_label is an optional parameter used to add additional data about the event.
■ opt_value is an optional parameter used to provide numerical data about the event.
■ opt_noninteraction is an optional parameter used to tell Google not to use this event in bounce rate calculations.
For example, if in our SPA we want to track when a user opens a chat window, we might make the following _trackEvent call:
_trackEvent( 'chat', 'open', 'home page' );
This call would then show up in reports letting us know that a chat event occurred, the user opened the chat window, and the user did this on the home page. Another call might be:
_trackEvent( 'chat', 'message', 'game' );
319 The cloud and third-party services
This would record that a chat event occurred, the user sent a message, and did it on the game page. Like the traditional website approach, it’s up to the developer to decide how to organize and track different events. As a shortcut, instead of coding each event into the client-side models, we can insert the _trackEvent calls into the cli- ent-side router (the code that watches the hashtag for changes) and then parse those changes into categories, actions, and labels and call the _trackEvent method using those changes as parameters.
SERVER-SIDE GOOGLE ANALYTICS
Tracking on the server side is useful if we want to get information about what data is being requested from the server, but it can’t be used to track client interactions that don’t make requests to the server side, which there’s quite a bit of in SPAs. It may seem less useful because it can’t track client-side actions, but it’s useful to be able to track requests that are making it past the client cache. It can help us track down server requests that are running too slow and other behaviors. Though this is still able to provide helpful insights, if we have to choose one, we go with the client.
Since JavaScript is used on the server, it seems likely that we could modify the Google Analytics code to be used from the server. It’s not only possible, but like many things that seem like a good idea, it has probably already been implemented by the community. A quick search turns up node-googleanalytics and nodealytics as community- developed projects.
9.2.2 Logging client-side errors
In a traditional website, when there’s an error on the server, it’s written to a log file. In an SPA, when a client hits a similar error, there’s nothing in place to record it. We’ll have to either manually write code to track errors ourselves or look to a third-party ser- vice for help. Handling it ourselves gives us the flexibility to do whatever we want to with the error, but using a third-party service gives us the opportunity to spend our time and resources on something else. Besides, they’ve likely implemented far more than we’d have time to. It’s also not all or nothing—we can use a third-party service and then if there are errors we want tracked or escalated in a way that the service doesn’t provide, we can implement the desired capability ourselves.
THIRD-PARTY CLIENT LOGGING
There are several third-party services that collect and aggregate errors and metrics data generated by our application:
■ Airbrake specializes in Ruby on Rails applications, but has experimental JavaScript support.
■ Bugsense specializes in mobile application solutions. Their product works with JavaScript SPAs and native mobile applications. If we have a mobile-focused application, they may be a good choice.
■ Errorception is dedicated to logging JavaScript errors and is therefore a good choice for an SPA client. They’re not as established as Airbrake or Bugsense but
we like their moxy. Errorception keeps a developer blog (http://blog.errorcep- tion.com), where we can gain insight on JavaScript error logging.
■ New Relic is fast becoming an industry standard for web application perfor- mance monitoring. Its performance monitoring includes error logging and performance metrics for each step of the request/response cycle, from how long the query took in the database to how long the browser took to render the CSS styles. The service provides an impressive amount of insight into perfor- mance on both the client and the server.
At the time of writing, we tend to prefer New Relic or Errorception. Whereas New Relic provides more data, we’ve found Errorception superior when dealing with JavaScript errors, as well as easy to set up.
LOGGING CLIENT-SIDE ERRORS MANUALLY
When it comes down to it, all these services use one of these two methods to send JavaScript errors:
1 Catching errors with the window.onerror event handler.
2 Surrounding code with a try/catch block and sending back what it catches.
The window.onerror event is the basis of most of the third-party applications. onerror fires for runtime errors, but not for compilation errors. onerror is somewhat contro- versial because of uneven browser support and potential security holes, but it’s a major weapon in our arsenal for logging client-side JavaScript errors.
<script>
var obj;
obj.push( 'string' );
windor.onerror = function ( error ) { // do something with the error }
</script>
The try/catch method requires wrapping a try/catch block around the main call in our SPA. This will catch any synchronous errors generated by our application; unfortu- nately it’ll also prevent them from bubbling up to window.onerror or being displayed in the error console. It won’t catch any errors in asynchronous calls like those made in event handlers or in setTimeout or setInterval functions. That means having to wrap all of the code in our asynchronous function with a try/catch block.
<script>
setTimeout( function () { try {
var obj;
obj.push( 'string' );
} catch ( error ) {
// do something with error
Results in an error because there’s no push method on undefined.
The error is accessible inside this block; the attributes on the error object vary by browser.
321 The cloud and third-party services
} }), 1);
</script>
Having to do that for all of our asynchronous calls would get tedious, and prevent reporting of the errors to the console. Wrapping code in a try/catch block also pre- vents the code in that block from being compiled in advance, causing it to run slower.
A good compromise approach for an SPA is to wrap our init call in a try/catch block, log the error to the console inside the catch, and send it off via Ajax, then use window.onerror to catch all of our asynchronous errors and send them off via Ajax.
No need to log the asynchronous errors to the console manually because they’ll still appear there on their own.
<script>
$(function () { try {
spa.initModule( $('#spa') );
} catch ( error ) {
// log the error to the console
// then send it to a third party logging service }
});
window.onerror = function ( error ) { // do something with asynchronous errors };
</script>
Now that we understand which errors are happening on the client, we can focus on how to deliver content to site visitors more quickly.
9.2.3 Content delivery networks
A content delivery network (CDN) is a network set up to deliver static files as quickly as pos- sible. It could be as simple as a single Apache server sitting next to our application server, or a worldwide infrastructure with dozens of data centers. In any case, it makes sense to have a separate server set up to deliver our static files, so as to not burden our application server with that task. Node.js is particularly ill-suited to delivering large static content files (images, CSS, JavaScript), because this usage can’t take advantage of the asynchronous nature of Node.js. Apache, with its pre-fork, is much better suited.
Because we’re well-versed in Apache, we could throw together our own “one-server CDN” until we get ready to scale the site; otherwise there are many third-party CDNs we can use. Three big ones are Amazon, Akamai, and Edgecast. Amazon has the Cloudfront product, and Akamai and Edgecast resell through other companies like Rackspace, Dis- tribution Cloud, and others. In fact, there are so many CDN companies out there that there’s a website dedicated to selecting the right provider: www.cdnplanet.com.
Another benefit of using a globally distributed CDN is that our content is served from the closest server, making the time it takes to serve up those files much shorter.
When we consider the performance benefits, using a CDN is often an easy choice.