Create Personal PWA Site With Hugo and Webpack

Dec 26, 2018 7 Min read

Recently I went through the processing of creating my personal site, after reading a lot of excellent articles (most of them come from Google Developers and Medium) and comparing many famous static site generators (SSG), such as Jekyll, Hugo, Hexo and Gatsby, I decided to choose Hugo as static content compiler and webpack as global resource optimizer. When building the site, I tried my best to follow best practices1, maximize code reuse and turn it into Progressive Web App (PWA)2,3 through Workbox.

Why Hugo?

Hugo is neither the most popular nor the most mature one among other SSGs. After careful consideration, I ultimately chose it because:

  • Open source: Hugo is an open source project with a huge community of developers and users.

  • Highly efficient: The major advantage of Hugo comparing with other SSGs is it’s speed4, speed of compile and speed of live-reload.

  • Extremely powerful: Hugo has excellent template engine, common configuration commands and flexible resource handling mechanisms.

  • Deadly easy: Hugo is just one static binary, it’s straight-forward for users from different operating systems to install, maintain and deploy Hugo projects.

  • Fully customizable: Developers can always create their own themes with little efforts.

Why webpack?

Hugo is powerful enough to build a full featured static site, so why do even bother with Webpack?

  • Multiple modes: webpack has well distinction between development and production models, which effectively balanced the compilation speed and resource size.

  • Flexible configuration: most importantly, webpack provides more flexible approaches to process resources.

(While setting up my site,victor-hugo has completely migrated from Gulp to webpack, their project is more customizable. Highly recommended)

Project structure

webpack-hugo
  ├── .babelrc                 babel configuration file
  ├── .editorconfig            editor configuration file
  ├── .eslintignore            eslint ignore configuration file
  ├── .eslintrc.json           eslint configuration file
  ├── .gitignore               git ignore configuration file
  ├── .prettierrc              prittier configuration file
  ├── netlify.toml             netlify configuration file
  ├── README.md                Documentation
  ├── package.json             npm configuration file
  ├── webpack.common.js        webpack general configuration file
  ├── webpack.dev.js           webpack development mode configuration file
  ├── webpack.prod.js          webpack production mode configuration file
  ├── site                     Hugo standard folder
  │   ├── archetypes/          Hugo prototype folder
  │   ├── assets/              Hugo assets folder
  │   ├── content/             Site content folder
  │   ├── data/                Hugo data folder
  │   ├── i18n/                internationalization folder
  │   ├── layouts/             Hugo templates folder
  │   ├── static/              Hugo static content folder
  │   └── config.toml          Hugo main configuration file
  └── src                      Public resource folder
       ├── img/                Public images folder
       ├── sass/               Public styles folder
       ├── 404.html            Public 404 html
       ├── minor.js            Secondary js file
       ├── robots.txt          robots.txt   
       ├── sw.js               Service Worker configuration file
       └── index.js            Main js file

Develop and Deploy

Prerequisites

Hugo, Node and npm (or yarn) are required.

  • Mac: brew install Hugo

  • Ubt: sudo apt-get install Hugo

  • Win: choco install Hugo -confirm

Development

$ yarn dev
▶ webpack --config webpack.dev.js --mode development --watch

...

                   | EN | ZH
+------------------+----+----+
  Pages            | 49 | 49
  Paginator pages  |  4 |  4
  Non-page files   |  0 |  0
  Static files     |  1 |  1
  Processed images |  0 |  0
  Aliases          | 19 |  19
  Sitemaps         |  2 |  2
  Cleaned          |  0 |  0

Total in 57 ms
Web Server is available at http://localhost:1313/ (bind address 127.0.0.1)

...

Webpack Bundle Analyzer is started at http://127.0.0.1:8888

Hugo comes with live-reload built in, it’s important for online updating content. And webpack provide watch mode to enable hot reloading scripts.

Production

$ yarn build
▶ env HUGO_ENV=production webpack --config webpack.prod.js --mode production

...

Total in 27 ms

Production mode triggered additional resource optimizations comparing with development mode.

Deployment

  • Push: push the entire project to Github repository.

  • Link: create a new Netlify site and link to the Github repository.

Custom domain

By default, any site on Netlify is accessible via its subdomain. There’s a step by step tutorial on how to add custom domains5. As a student, you can apply for the Student Developer Pack6, which provides a free Namecheap domain and a SSL certificate. Enable the SSL certificate is easy according to the official guidance7. You can set your custom domain as follows:

domain

Performance Optimization

Optimization mainly focus on resource and request8, and follows Pure Text | Unicode Character > CSS | SVG > WebP | JPG > JS may improve performance to a certain extent.

Reduce third-party dependencies

The major benefit of reducing third-party dependencies is decreasing the number of HTTP requests, therefore shortening the page load time, eventually reducing the delay time for user interaction.

  • css: Tiny website could use critical css instead of Bootstrap, CSS is treated as a render blocking resource, which means that constructing the CSSOM will block main render process.

  • js: Simple DOM selection can employ vanilla js rather than jquery, JavaScript is a kind of expensive resource as it must not only be downloaded, but parsed, compiled and executed as well.9

  • icon: Few icons could depend on unicode characters10 and svg sprite instead of Font Awesome.

  • font: Defaulting to the native system font11 can increase performance because of code and request reductions.

    body {
    font-family:
    -apple-system,        /* OS X (10.11+), iOS (9+) */
    BlinkMacSystemFont,   /* OS X, Chrome */
    'Segoe UI',           /* Windows */
    Roboto,               /* Android 4.0+ */
    'Ubuntu',             /* Linux, Ubuntu */
    'Helvetica Neue',     /* OS X (10.9) */
    sans-serif;
    }
    

Minimize resource size

Apply resource compression to minimize transmission size.

  • Image:Images often account for most of the transmission bytes on a web page12,13, consider css or svg effects instead of image where possible. Black & White images sometimes achieve better results. Convert image types into webp14 or jpg. Compress image through TinyPNG, Compressor.io etc.

  • Text:The smaller text size, the faster transmission. It’s the developer’s responsibility to eliminate irrelevant characters and comments, remove non-critical js scripts, “streamline” HTML code, decrease the depth of DOM tree and enable the webpack optimization strategies at production mode.

    $ http https://www.valleyease.me/
    
    HTTP/1.1 200 OK
    Connection: keep-alive
    Content-Encoding: gzip
    Content-Length: 162
    Content-Type: text/html; charset=UTF-8
    

Migrate to HTTP/2

HTTP/215 enables full request and response multiplexing, hence it’s reasonable to split some code into smaller chunks.

  • Optimize request:Adjust the order of resources executions, deliver main css as quickly as possible, load less important resources asynchronously and remove unused js from the critical path.16,17

  • Split resources:Only load resources needed for the current page, consider lazy-loading for non-critical scripts and using inline strategy for critical scripts.

Use service workers

For security reasons, service workers can only be registered on pages served over HTTPS. They already include features like intercept requests, push notifications and background sync etc. The most exciting feature is that they allow you to support offline experiences. What this means is that when a user navigates to your site, there’s a good chance requested resources already in the browser cache, and so they experience much more reduced costs in terms of booting scripts up and getting interactive.

Make some compromise

  • In-site Search: Algolia together with Autocomplete are powerful for content searching. But they contribute to almost 70% of total js size. Hence after serious consideration, I decide to use client-side search depend on article titles which contain critical key words.

  • Font Style:Using the native system font can boost performance,it is well used by Github, Medium, Ghost, WordPress and Bootstrap, The disadvantage is lacking of custom style.

  • Image Type:With WebP, developers can create smaller images that make the web faster. However, not every browser has WebP support, for example, Apple’s Safari browser, along with their iOS Safari browser, both don’t support WebP. So if you read this article on Safari, you may not see the images.

Performance Testing

Chrome DevTools is awesome for network, performance and service workers testing and debugging. The normal http request is as follows, which revels the optimizations around execution order, resource compression and code-splitting.

performance

Service worker has the ability to intercept and handle network requests, and give users response at low delay time by using cached resources. Most importantly, it can provide offline experiences within a certain duration.

service worker

I use Lighthouse to test my site and optimize my site according to the reports, which mainly focus on performance, PWA, accessibility, best practices and SEO. After step by step repairing, the result is good enough.

page test

audits

Summary

This is the first article of my site, mainly focus on performance optimization. There may exists unreasonable code because I create it to fulfill my own needs. The code is on Github, please fell free to use them.