Enable FastBoot in your Ember app

11 November 2016

What is FastBoot and why should you use it?

FastBoot is the awesome add-on that adds server-side rendering to your Ember app. This will make users of your application see the content of your page before any of the javascript is downloaded. In other words, the "time to first tweet" of your app is greatly reduced, which is a big win where and when people have slow or unstable network connections.

Another advantage is that search engine crawlers will have an easier job indexing your site, which brings SEO benefits.

Furthermore, your site will be readable with Javascript disabled which is convenient for screen readers.

I recently went through the process of enabling the demo version of the Rock and Roll application to run in Fastboot. Below, I'm going to tell you about the challenges I encountered and how I overcame them in the hope that my journey will prove valuable when you do the same for your app.

Installing the add-on

FastBoot is a regular Ember add-on, so installing it is piece of cake:

1$ ember install ember-cli-fastboot

I could then run

1$ ember fastboot

from the project's directory and had the node server serving my application at port 3000. It's important to note that you should refresh your browser tab each time you make changes to your code as FastBoot doesn't (yet) auto-refresh the way ember server does.

I then disabled JavaScript in my browser and then directed my browser to http://localhost:3000.

Disabling JavaScript in Chrome is most easily done by expanding the context menu of Developer Tools and then clicking on Settings:

Disable JavaScript

Mirage is disabled in FastBoot mode

My first obstacle turned out to be Mirage.

Mirage is a great tool for mocking server responses and even prototyping your Ember app. I used it in development, too, and it turned out that since it turns itself off when your app is running in FastBoot mode, the requests hitherto handled by Mirage now went out and were thus unhandled.

The fix here was to disable Mirage in development (and, in my case, production, too) and to make the requests against an actual API.

You also have to add the hosts that will serve your Ember app in FastBoot mode to a list called hostWhitelist in your app's configuration.

In my case, it contains the host I wanted to deploy it to and any localhost port:

1// config/environment.js
2module.exports = function(environment) {
3  var ENV = {
4    (...)
5    fastboot: {
6      hostWhitelist: ['demo.rockandrollwithemberjs.com', /^localhost:\d+$/]
7    }
8  };

Serving assets

When I restarted the ember fastboot and looked at the server-rendered version of my app, I saw that the dynamic data was now correctly rendered on the page. However, it did not have any styling.

A quick glance at the documentation made me realize I needed to pass the serve-assets option to the command so that it serves the css (and other asset) files:

1$ ember fastboot --serve-assets

document is not defined

So now the main page, with the list of bands rendered fine but when I selected one of the bands to have their songs displayed, I got the following error:

1Error while processing route: bands.band.songs document is not defined

Since Fastboot runs your Ember app in a node environment, not in the browser, document is not present. In my case, I accessed document (through jQuery) to set the document title, which does not work in FastBoot mode.

The user guide suggested to use ember-cli-document-title, a FastBoot compatible way to set document titles. So my next step was to install that add-on:

1$ ember install ember-cli-document-title

Armed with this great add-on, I only had to define a title (method) in the corresponding route:

 1// app/routes/bands/band/songs.js
 2import Ember from 'ember';
 3
 4export default Ember.Route.extend({
 5  (...)
 6  title() {
 7    let bandName = this.modelFor('bands.band').get('name');
 8    return `${bandName} songs - Rock and Roll`;
 9  },
10});

Missing dynamic content

The next thing that did not work was that the songs for a specific band did not load in FastBoot mode, the list of songs was empty each time.

Adolfo Builes and Jonathan Jackson helped me out by pointing out that songs are loaded asynchronously. The request to fetch the songs was only fired when the template rendered each song belonging to the band. FastBoot does not know when the page is fully rendered and thus relies on the beforeModel, model and afterModel route hooks having finished their work. When that happened, the songs were not yet fetched and rendered on the screen yet:

Songs missing

The way to fix this was to block rendering in the afterModel hook, by returning a promise that fetched the songs:

 1// app/routes/bands/band/songs.js
 2import Ember from 'ember';
 3
 4export default Ember.Route.extend({
 5  fastboot: Ember.inject.service(),
 6
 7  afterModel() {
 8    if (this.get('fastboot.isFastBoot')) {
 9      let band = this.modelFor('bands.band');
10      return band.get('songs');
11    }
12  },
13  (...)
14});

As you can see, I only pre-fetch the songs in FastBoot mode. In the browser, I let rendering start earlier, with a "pop-in" effect (which can be remedied in several ways in the browser, too).

The songs now appeared in the FastBoot "view" of the app, too:

Songs present

You can read more about this in the "Use Model Hooks to Defer Rendering" section of the guide.

Fastboot-enabled hosting

It's fine to have FastBoot working in development but nobody actually needs it to work in that environment. It has to work when deployed to a server.

The guide has a whole page on deployment, listing several deployment options, from which I chose Heroku as it seemed the easiest option. And it really was.

All I had to do was to set the buildpack URL from my project:

1$ heroku buildpacks:set https://codon-buildpacks.s3.amazonaws.com/buildpacks/heroku/emberjs.tgz -a rarwe-demo

I then added a static.json file to the root of my project, to disable forcing https requests, as the domain is not (yet) SSL-supported:

 1// static.json
 2{
 3  "root": "dist/",
 4  "https_only": false,
 5  "clean_urls": true,
 6  "routes": {
 7    "/**": "index.html"
 8  },
 9  "headers": {
10    "/**": {
11      "Cache-Control": "private, no-store, no-cache, must-revalidate, proxy-revalidate",
12      "Pragma": "no-cache",
13      "Expires": "Sat, 05 Nov 1955 00:00:00 PST",
14      "Strict-Transport-Security": "max-age=31536000; includeSubDomains;",
15      "X-Download-Options": "noopen",
16      "X-Content-Type-Options": "nosniff",
17      "X-Frame-Options": "SAMEORIGIN",
18      "X-XSS-Protection": "1; mode=block"
19    },
20    "/assets/**": { "Cache-Control": "public, max-age=512000" },
21    "/robots.txt": { "Cache-Control": "public, max-age=512000" },
22    "/crossdomain.xml": { "Cache-Control": "public, max-age=512000" }
23  }
24}

This step is really only needed to change the default https_only setting. If you have SSL set up for your domain, you don't need the static.json file.

The next time I pushed to the remote set up by Heroku, it just worked, and my app was now FastBoot enabled. Hooray!

Acknowledgements and further resources

I would like to thank Adolfo and Jonathan for their help in pointing me at Ember Weekend, an Ember app that runs in FastBoot and whose source code is publicly available, and also for overcoming the above mentioned "missing dynamic content" problem.

My app does not use many of Fastboot's features. If you're looking to see a more complex use case, check out the Ember Weekend source code.

If you want to learn more about FastBoot's architecture and rationale, I recommend checking out Tom Dale's great presentation he gave at Global Ember Meetup.

Finally, the Rock and Roll demo app that this post describes is available on Github at balinterdi/rarwe-demo and deployed to http://demo.rockandrollwithemberjs.com.

Share on Twitter