How this website was built

27 October 2016 | 3 minute read

Static FTW!

Not so long ago, I stumbled upon this beautifully designed and amazingly fast website from MojoTech. A quick look at their source code showed that they used a pretty nifty approach for speeding everything up. First and foremost it was a static website, or at least statically served I thought (proxy?). And second of all, nearly all the needed HTML was served as well using JSON.

So I started looking for sources how the HTML was rendered, and I found a great video from Sam Saccone explaining the technology behind it all. Curiously enough, he showed a rather straightforward framework called Roots, made by Carrot Creative. Here it is:

This convinced me to use a similar approach and start building this website using Roots as well after creating a design. Now, some things did not go as planned. Roots already has several extremely useful extensions. I wanted to go for a completely static approach. No database involved. And the Dynamic Content extension made that possible.

Wait! Not so fast!

Dynamic Content compiles static templates and serves the rest of the website with local variables, which in turn you can use to dynamically render your website. Sweet! However, whenever you would like to use these variables within the dynamic templates themselves, the remaining templates aren't yet compiled. The result is that the local variables lack data upon compilation.

The solution to this problem is rather simple. The dynamic templates use Front Matter in order to deliver data to Roots. I wrote a small CoffeeScript class that recursively extracts and returns this data in JSON format. Roots can be configured as such that local variables are inserted before compiling. Here's the result:

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
fs          = require 'fs'
fm          = require 'front-matter'
_           = require 'underscore'
path        = require 'path'

# Create our Content Repository class
class ContentRepository
    getBlogPosts: ->
        # Get the blogposts
        posts = @getDirectoryEntries 'views/blog'
        # Sort by descending date
        posts = _.sortBy(posts, 'date').reverse()

    getProjects: ->
        # Get the projects
        projects = @getDirectoryEntries 'views/work'
        # Sort by ascending index
        projects = _.sortBy(projects, 'index')

    getDirectoryEntries: (directory, files = []) =>
        # Read all files
        entries = fs.readdirSync directory

        # Process each entry individually
        _.each entries, (entry, index) =>
            # Get the full path
            filePath = path.join(directory, entry)

            # What kind of entry is it?
            fileStat = fs.statSync(filePath)

            # Process an individual file
            if fileStat.isFile() && path.extname(entry) == '.jade'
                # Read it
                file = fs.readFileSync(filePath, 'utf8')

                # Create the URL
                p = path.relative('views', path.join(filePath.substr(0, filePath.lastIndexOf('.'))))

                # And add the front-matter data to the files array, with URL
                fileData = fm file
                fileData.attributes._url = "/#{p.replace(path.sep, '/')}"
                files.push fileData.attributes

            # Process a directory
            else if fileStat.isDirectory()
               # Traverse the newly found directory
                files = @getDirectoryEntries filePath, files

            return files

    module.exports = ContentRepository

What about the images?

Nowadays it is no longer a luxury to have retina-proof responsive images on your website. The complexity is that, with or without art direction, you may need more than 2 versions of the same image. Now, imagine that you have added dozens (if not hundreds) of manually resized images to your website and you would like to alter the dimensions or layout of your website. What now?

Again, using the same approach above a solution came to mind. I wrote a CoffeeScript image resizer class and used it within custom Jade Mixins for rendering the final image tags. The only thing I had to do next was to say where the image was, where it needed to go, and what extra data needed to be added. That's it. The rest is magic. Here's the class:

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
_       = require 'underscore'
fs      = require 'fs'
path    = require 'path'
Jimp    = require 'jimp'
mkdirp  = require 'mkdirp'

class ImageResizer
    createImage: (opts) ->
        # Complete the config with defaults where necessary.
        options = _.defaults(opts, {
            crop: true,
            quality: 80
        })

        # Create the new filename
        filedest = path.parse(options.source).name + '-' + options.suffix + '.jpg'

        # Define the public destination
        dest = '/images/' + options.dest + '/' + filedest

        # Check if the final image exists, if not, create it
        fs.stat 'public' + dest, (err, stat) ->
            if err && err.code == 'ENOENT'
                # Make sure that the directory exists
                mkdirp 'public/images/' + options.dest, (err) ->
                    if (err)
                        throw err

                    # Resize the image
                    Jimp.read options.source, (err, img) ->
                        if (err)
                            throw err

                        # Resize the image
                        if (options.crop)
                            img.cover(options.w, options.h)
                                .quality(options.quality)
                                .write('public' + dest)
                        else
                            img.resize(options.w, Jimp.AUTO)
                                .quality(options.quality)
                                .write('public' + dest)

        return dest

module.exports = ImageResizer

The styling

Ahh, the styling... theming has become increasingly more complex, especially considering the increasingly more dynamic and modular approach to building websites. Luckily, a few very well-thought-out techniques have been published over the past few years. The ones I use on this website are ITCSS and BEM. Despite having more files and longer syntax, the actual theming is now much more versatile and maintainable. The compilation of the styling is done using the almighty tool PostCSS.

Next to the CSS I also use Browserify for writing modular JavaScript. It's incredibly handy for compiling your code together with required CommonJS modules into a final codebase, ready to be used by your browser. I combined it with the power of CoffeeScript, as demonstrated in the video above. Many thanks to Sam Saccone for the idea.

Final thoughts

Roots may be an excellent framework, but honestly, I'm considering to rewrite it for the next big thing from Carrot Creative. Jeff Escalante, the main creator of Roots, is now busy creating something truly remarkable. It is called Spike, and it's like no other static website generator you have seen before. At least I know I haven't. This is what got my attention, and it may just be the new home for the codebase of this website in the future.

So stay tuned!