Tom Genoni / Design Technologist
March 10, 2020

Wiring genoni.dev

The obligatory post on the rebuild.

As it has been in the past, rebuilding my website is an opportunity to try out approaches I’d learned in the past few years. Instead of using an existing static-site generator I decided to build one from scratch, both to better understand and control each piece of the build phase and optimize the client-side output for speed.

Build process

1. Node & Nunjucks.

  1. Markdown with front-matter as source files for all content.
  2. A custom Node script to read in, convert, and combine the Markdown data.
  3. Nunjucks for templating.

I originally built the site with Handlebars.js but found I was needing to do more conditionals than it comfortably supports.

2. Bash script. One of the benefits of using npm is the ease with which you can store aliases to commands in a package.json file. But this is cumbersome if you have more than a few scripts to manage. Moving the commands into a bash file, as described in An alternative to npm scripts, makes it far easier to manage and organize.

Instead of a package.json file with a list of scripts…

"scripts": {
  "css:scss": "node-sass -q src/asset/scss/root.scss tmp/root.css && node-sass -q src/asset/scss/roll.scss tmp/roll.css",
  "css:postcss": "postcss tmp/root.css --no-map --use css-mqpacker cssnano -o dist/asset/css/root.css && postcss tmp/roll.css --no-map --use cssnano -o dist/asset/css/roll.css",
    ...
}

… we put them in functions inside scripts.sh. This means we can structure the file however we like, add comments and console output, and more easily make changes.

function css:scss() {
  # Build CSS
  echo 'css...'
  node-sass -q src/asset/scss/root.scss tmp/root.css && \
  node-sass -q src/asset/scss/roll.scss tmp/roll.css && \
  echo '✔ scss'
}

function css:postcss() {
  # Run `mqpacker` to sort by breakpoint, cssnano to minify
  postcss tmp/root.css --no-map --use css-mqpacker cssnano -o dist/asset/css/root.css && \
  postcss tmp/roll.css --no-map --use cssnano -o dist/asset/css/roll.css && \
  echo '✔ postcss'
}

The package.json scripts section is now greatly simplified, replacing previously lengthy commands with references to bash rollup functions than include other functions.

"scripts": {
  ...
  "css:dev": "bash scripts.sh css:dev",
  "css:prod": "bash scripts.sh css:prod",
  ...
}

3. CSS manipulations. A few postcss packages gets the CSS as small as possible.

  1. css-mqpacker to sort by breakpoints.
  2. cssnano for a bunch of optimizations.

4. Local build.

  1. chokidar with concurrently watches files and directories to trigger rebuilds.
  2. browser-sync serves and auto-refreshes the site for local development.
  3. prismjs for code example syntax highlighting

5. Production build. On pushes to master the site is rebuilt and served by Netlify. Here we run a few other optimizations.

  1. html-minify on the HTML and CSS files.
  2. node-minify for the JS files.
  3. purgecss to remove unused CSS.
  4. sitemap-generator to build my very simple sitemap.

Running purgecss is a little risky as it relies on a scan of the output HTML to determine what classes to remove. This means classes introduced by JavaScript can get discarded unless whitelisted in a config. But when managed correctly it can significantly reduce the CSS output.

Client side

Now we’re ready to send the HTML to the user.

1. Responsive images. Adding images to sites is like opening a nesting doll, one issue leads to another and another…

2. preload critical fonts. If I were trying to keep my site as fast as possible I’d use system fonts. But I’m a little vain and wanted to test out best practices for loading custom fonts. Before the page renders I’m using preload on two font weights. This does create a slight delay but I avoid layout jumps from unstyled type.

3. Cache fonts. cache-control One of Netlify’s great features is the ability to quickly rollback if a deploy goes wrong. But for the custom fonts that I have no intention of changing I’ve set cache-control = "public, max-age=31536000". This means subsequent page loads feel much faster.

4. preconnect and dns-prefetch. These provide ways of asking the browser to do remote DNS lookups of resources not served by your domain. On this site it’s being used for the image service.

5.async on script tags. Provides parallel fetching of JavaScript files that don’t need to be downloaded in a specific order.

6. instant.page. When a user hovers over an anchor this script assumes it will be clicked and preloads the HTML for that page. It’s a small amount of data and provides a slightly faster user experience, mainly on desktop.

7. Functional CSS. I wrote about my experience introducing this CSS approach to the engineering team at Thumbtack and am using it to power much of this site. Used with purgecss described above the main CSS file this site delivers to the client is 3.4kb.

Other tools

I wrote a few one-off scripts to help with image production.