Building a Blog with Ember (Part 3) Pre-Render Pages to Static HTML

In the previous part of the series, we made our code blocks look nice by adding syntax highlighting using a library called PrismJS. So far, we have a workflow of adding and displaying posts to our blog. There is a drawback we to our current architecture, though.

The Problem with Single-Page Applications

Single-Page Applications (SPA) traditionally work by delivering a minimal HTML document to the user and using JavaScript to load the actual content of the page into the HTML, usually into a specific container element.

This approach has the significant drawback that the entire page relies on JavaScript to show any content to the user. If for some reason the JavaScript is slow to load, like on mobile connections, or it takes a long time to parse the code, like on low-end devices, nothing will be visible to the user during that time. Worst case is that the JavaScript doesn't load at all due to an error and no content will ever be visible. In that case, the site is basically unusable.

The fact that the JavaScript is responsible for loading the content also has implications on how the page is being crawled and indexed by Google. At Google I/O 2018, Tom Greenaway and John Mueller talked about the process of indexing pages that heavily rely on JavaScript.

According to Greenaway and Mueller, while the Google crawler does handle JavaScript on your page without a problem, the parsing of JavaScript requires more processing power than parsing static HTML. Google, therefore, defers rendering your page with JavaScript rendered content until they have these resources available.

What this means is that while a Single-Page application can be indexed by Google just like a static page, it may take up to several days longer to have your content visible on Google. Depending on your use case, this might be fine, but if SEO is critical to the success of your site and/or business, this can be a real dealbreaker for you.

Delivering HTML on the initial load

We can solve the issues mentioned above by making sure that when a user visits our blog, the initial HTML always contains the content we want to display. When the JavaScript is subsequently loaded, it then "hooks" into the DOM generated by the initial HTML and handles rendering — based on user input or other events — on the client side from this point on.

While sending content with the initial HTML which is delivered to the user is achieved using Server Side Rendering, the process of hooking the Single-Page application into the DOM is commonly know as Rehydration.

So, with the theory out of the way, let's implement the introduced concepts into our blog, shall we?

Lucky for us there is — you guessed it — an addon by the Ember community that helps us achieve what we just described in a fairly pain-free fashion. The addon is called Prember. Prember requires another addon called Fastboot. Fastboot is the defacto standard when it comes to rendering Ember applications on the server side. Install the addons and start the development server:

ember install ember-cli-fastboot && ember install prember && ember serve

If you pull up the developer tools in the browser of your choice, visit a page on our blog and inspect the HTML that got sent to your browser, you can notice the first difference right away. Before, the markup consisted of only the basic HTML structure defined in src/ui/index.html.

Now, after installing the addons, the response will already include all the relevant HTML elements that were generated on the client-side before. This is the functionality that Fastboot comes with out of the box. Magic, right?

One could think that we might already be done at this point, but the actual goal was to deliver static pages to the user. What we have accomplished so far is having our pages rendered on the server-side when a user first visits a page. But the pages are still generated dynamically at runtime. Since our posts don't change during runtime, we can enhance our workflow further by generating the HTML we need during the build time of our application. This is the process of pre-rendering I was talking about earlier. And that's where Prember comes into play.

Generating HTML at Build Time

To get a better grasp of what Prember is doing for us, let's create a production-ready build of our current Ember application. Run:

ember build --environment=production

In the dist directory you now have a file structure that is similar to the following:

dist
├── _empty.html
├── assets
│   ├── auto-import-fastboot-d41d8cd98f00b204e9800998ecf8427e.js
│   ├── lost-in-technology-205aa841acb171e4b001c0bd3982333a.css
│   ├── lost-in-technology-f6c5143f52d6724ac8cbc5fd6a3af7ee.js
│   ├── lost-in-technology-fastboot-aa0e320401d89cd9395632f294066ec8.js
│   ├── vendor-3c8a3b08c4c522a82d4448f99a86a8a5.js
│   └── vendor-d9a0884ff4ddbca30bed8e85633fed23.css
├── index.html
├── package.json
└── robots.txt

Next, configure Prember and tell the addon which routes we want to pre-render. Let's start simply by configuring the route of a single post. Add it to your application's config:

// ember-cli-build.js

let app = new EmberApp(defaults, {
   // Add options here
   'ember-prism': {
     components: ['markup', 'markup-templating', 'handlebars', 'diff'],
   },
+  prember: {
+    urls: ['/blog/fancy-post'],
+  },
})

Build the application again:

ember build --environment=production

And look at our dist directory now:

dist
├── _empty.html
├── assets
│   ├── auto-import-fastboot-d41d8cd98f00b204e9800998ecf8427e.js
│   ├── lost-in-technology-205aa841acb171e4b001c0bd3982333a.css
│   ├── lost-in-technology-f6c5143f52d6724ac8cbc5fd6a3af7ee.js
│   ├── lost-in-technology-fastboot-aa0e320401d89cd9395632f294066ec8.js
│   ├── vendor-67cd919cb13661e9ecc6b237ac82b8aa.js
│   └── vendor-d9a0884ff4ddbca30bed8e85633fed23.css
├── blog
│   └── fancy-post
│       └── index.html
├── index.html
├── package.json
└── robots.txt

The important difference here is the new file directory called blog with a file called index.html. The path to this file matches the one we just specified in our application's config. If you look at the contents of the file, you'll notice that it contains the HTML of the related post, generated by Fastboot.

Now, if you visit the related post, what happens behind the scenes is that Ember looks for an index.html at the corresponding path in our dist directory and if it finds one, it will deliver it instead of generating the HTML at runtime the way it did before.

Awesome!

Dynamically rendering Posts at Build Time

Thus far, we added the post we want to pre-render explicitly to the configuration of our application. Of course, this doesn't scale very well as more posts are added over time. We need to tell Prember that it should generate static pages for every post in our markdown directory.

If you remember what I said in the first part of this series, we use the slug of a post as the identifier in our route to know which post we want to display. Now that it's required to know which route to pre-render, the most simple way is to use the slug as the name of your respective file.

Assuming that the names of the files match the slug, we can implement pre-rendering all our posts by adding the following code to our configuration file:

// ember-cli-build.js

'use strict'

const EmberApp = require('ember-cli/lib/broccoli/ember-app')
+const fs = require('fs')
+
+function getPremberUrls() {
+  let urls = ['/blog']
+
+  const files = fs.readdirSync('markdown')
+  const filesWithoutExtension = files.map(file => file.replace('.md', ''))
+
+  filesWithoutExtension.forEach(file => urls.push(`/blog/${file}`))
+  return urls
+}

module.exports = function(defaults) {
  let app = new EmberApp(defaults, {
    // Add options here
    'ember-prism': {
      components: ['markup', 'markup-templating', 'handlebars', 'diff'],
    },
    prember: {
-      urls: ['/blog/fancy-post'],
+      urls: getPremberUrls(),
    },
  })

  return app.toTree()
}

We import the fs module from Node which allows us to access our file system. Use the module to read all the files that are located in our markdown directory. Since we only need the file name (matching the slug) to specify the route, not the extension, we map over the array of file names and remove the extension. Next, we use these names to configure our Prember-Routes.

And that's it! Run another production build for your application and you will see all of your blog posts pre-rendered to static HTML files which Ember will serve.

In the next part of the series, I want to have a look at how we can integrate service workers into our application to further improve performance and therefore enhance a user's experience.

Thank you for reading! If you have feedback of any kind or just want to have a chat, feel free to reach out to me. You can find my contact information below.