Hugo PWA Module

PWAs (Progressive web apps) are web apps developed using a number of specific technologies and standard patterns to allow them to take advantage of both web and native app features. The PWA module includes preliminary support for Hugo sites, see the features below.

Modulegithub.com/hugomods/pwa
StatsGitHub Stars Used By Used By Used By Used By

Features

  • Offline available: offline page and offline image.
  • Installable: can be added to home screen.
  • Precaching: allow precaching pages, CSS, JS, fonts and so on.
  • Cache Strategies1: supports cache first, network first and stale while revalidate.

You may still be a bit confused about the features, the following video will show what the module can bring to your sites. You can also give it a shot on our website or the demo site.

Installation

This section is for theme developers to integrate this module with their themes.

There is a demo site and it’s source code to help you get started.

1. Import the Module

hugo.yaml

1module:
2  imports:
3  - path: github.com/hugomods/pwa

hugo.toml

1[module]
2  [[module.imports]]
3    path = 'github.com/hugomods/pwa'

hugo.json

1{
2   "module": {
3      "imports": [
4         {
5            "path": "github.com/hugomods/pwa"
6         }
7      ]
8   }
9}

2. Import the Meta Partial

The meta partial generate the manifest meta tag to tell browser the location of web app manifest.

1<head>
2  {{ partialCached "pwa/assets/meta" . }}
3</head>

3. Precache CSS and JS

1{{ $css := resources.Get "main.css" }}
2<link href="{{ $css.RelPermalink }}" rel="stylesheet" />
3{{/* THIS OPERATION CANNOT BE BE CACHED BY partialCached. */}}
4{{ partial "pwa/functions/precache" (dict "URL" $css.RelPermalink "Page" .) }}
1{{ $js := resources.Get "main.js" }}
2<script src="{{ $js.RelPermalink }}"></script>
3{{/* THIS OPERATION CANNOT BE BE CACHED BY partialCached. */}}
4{{ partial "pwa/functions/precache" (dict "URL" $js.RelPermalink "Page" .) }}

As the comments said, pwa/functions/precache cannot be cached, since Hugo renders pages concurrently.

    sequenceDiagram
	    participant P1 as Page1
	    participant H as Hugo
	    participant P2 as Page2
	    P1->>H: Precache CSS.
	    H-->>P1: Done!
	    P2->>H: Precache CSS.
	    H-->>P2: Cache found, ignored!
	    P2->>H: Precache JS.
	    H-->>P2: Done!
	    P1->>H: Precache JS.
	    H-)P1: Cache found, ignored!

The example above shows the case of caching the pwa/functions/precache operations. So far, the Page1 precaches the CSS, while the Page2 precaches the JS, in either case, the service worker not aware of all the resources that need to be precached.

4. Import the Service Worker Partial

Finally, import the pwa/assets/sw partial to generate service worker script.

1<body>
2...
3{{ partialCached "pwa/assets/sw" . }}
4</body>

It’s recommended to use partialCached to cache the partial for getting better build performance.

5. Custom the Offline Page

This module provides a built-in offline page with inline style, you can change it by creating the layouts/_default/index.offline.html file.

Usage

This section is for theme users to set up the PWA.

Site Configuration

Append the Offline and WebAppManifest format into the outputs.home, to generate the offline page and web app manifest.

hugo.yaml

1outputs:
2  home:
3  - HTML
4  - RSS
5  - Offline
6  - WebAppManifest

hugo.toml

1[outputs]
2  home = ['HTML', 'RSS', 'Offline', 'WebAppManifest']

hugo.json

 1{
 2   "outputs": {
 3      "home": [
 4         "HTML",
 5         "RSS",
 6         "Offline",
 7         "WebAppManifest"
 8      ]
 9   }
10}

Site Parameters

ParameterTypeDefaultRequiredDescription
debugbooleanfalse-Whether to enable the debug mode.
icon_pathstringimages/pwa/icon.png-The icon image relative to the assets folder, which used to generate icons in multiple sizes.
icon_sizesarray[48, 64, 128, 144, 256, 512]-The target sizes of icons.
offline_imagestringimages/pwa/offline.png-The offline image relative to the assets folder, which will be shown when request an image offline.
precachesarray[]-Custom precache files.
precaches.urlstring--The URL of precache file.
cachesobject--Cache settings.
caches.fontobject--Font cache settings.
caches.font.originsarray[]-Trusted origins2.
caches.font.strategystringcache-first-Font cache strategy1.
caches.font.max_agestring2592000-Font cache max age in second.
caches.imageobject--Image cache settings.
caches.image.originsarray[]-Trusted origins2.
caches.image.strategystringcache-first-Image cache strategy1.
caches.image.max_agestring2592000-Image cache max age in second.
caches.scriptobject--Script cache settings.
caches.script.originsarray[]-Trusted origins2.
caches.script.strategystringcache-first-Script cache strategy1.
caches.script.max_agestring2592000-Script cache max age in second.
caches.styleobject--Style cache settings.
caches.style.originsarray[]-Trusted origins2.
caches.style.strategystringcache-first-Style cache strategy1.
caches.style.max_agestring2592000-Style cache max age in second.
manifestobject--Manifest settings, such as theme_color, background_color.

This module doesn’t provide the built-in icon and offline images, you’ll need to save your icon and offline images to corresponding path.

hugo.yaml

 1params:
 2  pwa:
 3    caches:
 4      font:
 5        max_age: 2592000
 6        origins: []
 7        strategy: cache-first
 8      image:
 9        max_age: 2592000
10        origins: []
11        strategy: cache-first
12      script:
13        max_age: 2592000
14        origins: []
15        strategy: cache-first
16      style:
17        max_age: 2592000
18        origins: []
19        strategy: cache-first
20    debug: false
21    icon_path: images/pwa/icon.png
22    icon_sizes:
23    - 48
24    - 64
25    - 128
26    - 144
27    - 256
28    - 512
29    manifest:
30      background_color: '#ff4088'
31      theme_color: '#ff4088'
32    offline_image: images/pwa/offline.png
33    precaches:
34    - url: /
35    - url: foo.png

hugo.toml

 1[params]
 2  [params.pwa]
 3    debug = false
 4    icon_path = 'images/pwa/icon.png'
 5    icon_sizes = [48, 64, 128, 144, 256, 512]
 6    offline_image = 'images/pwa/offline.png'
 7    [params.pwa.caches]
 8      [params.pwa.caches.font]
 9        max_age = 2592000
10        origins = []
11        strategy = 'cache-first'
12      [params.pwa.caches.image]
13        max_age = 2592000
14        origins = []
15        strategy = 'cache-first'
16      [params.pwa.caches.script]
17        max_age = 2592000
18        origins = []
19        strategy = 'cache-first'
20      [params.pwa.caches.style]
21        max_age = 2592000
22        origins = []
23        strategy = 'cache-first'
24    [params.pwa.manifest]
25      background_color = '#ff4088'
26      theme_color = '#ff4088'
27    [[params.pwa.precaches]]
28      url = '/'
29    [[params.pwa.precaches]]
30      url = 'foo.png'

hugo.json

 1{
 2   "params": {
 3      "pwa": {
 4         "caches": {
 5            "font": {
 6               "max_age": 2592000,
 7               "origins": [],
 8               "strategy": "cache-first"
 9            },
10            "image": {
11               "max_age": 2592000,
12               "origins": [],
13               "strategy": "cache-first"
14            },
15            "script": {
16               "max_age": 2592000,
17               "origins": [],
18               "strategy": "cache-first"
19            },
20            "style": {
21               "max_age": 2592000,
22               "origins": [],
23               "strategy": "cache-first"
24            }
25         },
26         "debug": false,
27         "icon_path": "images/pwa/icon.png",
28         "icon_sizes": [
29            48,
30            64,
31            128,
32            144,
33            256,
34            512
35         ],
36         "manifest": {
37            "background_color": "#ff4088",
38            "theme_color": "#ff4088"
39         },
40         "offline_image": "images/pwa/offline.png",
41         "precaches": [
42            {
43               "url": "/"
44            },
45            {
46               "url": "foo.png"
47            }
48         ]
49      }
50   }
51}

Best Practice

The default parameters is best for production, but will be bad for development, because you may see a cached, out-of-date page and have to clear the cache or do a force refresh.

But don’t worry, we can fix it by changing the default cache strategies1 for development environment.

config/development/hugo.yaml

 1params:
 2  pwa:
 3    caches:
 4      font:
 5        strategy: network-first
 6      image:
 7        strategy: network-first
 8      script:
 9        strategy: network-first
10      style:
11        strategy: network-first

config/development/hugo.toml

 1[params]
 2  [params.pwa]
 3    [params.pwa.caches]
 4      [params.pwa.caches.font]
 5        strategy = 'network-first'
 6      [params.pwa.caches.image]
 7        strategy = 'network-first'
 8      [params.pwa.caches.script]
 9        strategy = 'network-first'
10      [params.pwa.caches.style]
11        strategy = 'network-first'

config/development/hugo.json

 1{
 2   "params": {
 3      "pwa": {
 4         "caches": {
 5            "font": {
 6               "strategy": "network-first"
 7            },
 8            "image": {
 9               "strategy": "network-first"
10            },
11            "script": {
12               "strategy": "network-first"
13            },
14            "style": {
15               "strategy": "network-first"
16            }
17         }
18      }
19   }
20}

  1. Available strategies: cache-first, network-first and stale-while-revalidate↩︎ ↩︎ ↩︎ ↩︎ ↩︎ ↩︎

  2. Only trusted third-party origin resources will be cached, such as https://example.com, https://example.org/↩︎ ↩︎ ↩︎ ↩︎