Files
gists/gohugo-pwa.md
T
2026-10-07 07:45:40 -05:00

5.2 KiB

GOHUGO PWA SETUP

layouts/partials/custom_head.html

<!-- A partial to be overwritten by the user.
  Simply place a custom_head.html into
  your local /layouts/partials-directory -->

<div id="sw-update" hidden>
  <span>A new version is available.</span>
  <button id="sw-update-button" type="button">
    Reload
  </button>
</div>

<style>
  #sw-update {
    position: fixed;
    right: 1rem;
    bottom: 1rem;
    z-index: 1000;
    padding: 0.75rem 1rem;
    color: white;
    background: #222;
    border-radius: 0.4rem;
    box-shadow: 0 0.25rem 1rem rgb(0 0 0 / 25%);
  }

  #sw-update button {
    margin-left: 0.75rem;
  }
</style>

<script>
  if ("serviceWorker" in navigator) {
    let refreshing = false;

    navigator.serviceWorker.addEventListener("controllerchange", () => {
      if (refreshing) {
        return;
      }

      refreshing = true;
      window.location.reload();
    });

    navigator.serviceWorker.register("/sw.js").then((registration) => {
      function showUpdatePrompt(worker) {
        const prompt = document.querySelector("#sw-update");
        const button = document.querySelector("#sw-update-button");

        prompt.hidden = false;

        button.addEventListener("click", () => {
          worker.postMessage({
            type: "SKIP_WAITING"
          });
        }, { once: true });
      }

      // Handle an update that was already waiting.
      if (registration.waiting) {
        showUpdatePrompt(registration.waiting);
      }

      registration.addEventListener("updatefound", () => {
        const worker = registration.installing;

        if (!worker) {
          return;
        }

        worker.addEventListener("statechange", () => {
          if (
            worker.state === "installed" &&
            navigator.serviceWorker.controller
          ) {
            showUpdatePrompt(worker);
          }
        });
      });

      // Ask the browser to check for a newer sw.js.
      registration.update();
    });
  }
</script>

layouts/partials/custom_body.html

<script src="/main.js"></script>

static/main.js

if ('serviceWorker' in navigator) {
  window.addEventListener('load', () => {
    navigator.serviceWorker.register('/sw.js')
      .then(req => {
        console.log('Registered!', reg);
      }).catch(err => {
        console.log('Registration failed: ', err);
      });
  });
}

static/manifest.webmanifest

{
  "name": "Pat K",
  "short_name": "PK",
  "start_url": "/",
  "scope": "/",
  "background_color": "#000000",
  "theme_color": "#2e1e3c",
  "display": "standalone",
  "id": "5801e2e4fab6956ac349d7c724329b869cbffb19e87e545cd5bf6ffb2cc4a3e7",
  "icons": [
    {
      "src": "icons/192.png",
      "type": "image/png",
      "sizes": "192x192"
    },
    {
      "src": "icons/512.png",
      "type": "image/png",
      "sizes": "512x512"
    }
  ] 
}

static/icons

ffmpeg -i example.png -vf "scale=192:192" icon-192.png
ffmpeg -i example.png -vf "scale=512:512" icon-512.png

hugo.toml

[outputs]
  home = ["HTML", "SW"]

[outputFormats.SW]
  mediaType = "text/javascript"
  baseName = "sw"
  isPlainText = true
  notAlternative = true

themes/hugo-bearblog/layouts/index.sw.js

const CACHE_NAME = "patk-{{ now.Unix }}";

const PRECACHE = [
  "/index.html",
  "/manifest.webmanifest",
  "/main.js",
  "/robots.txt",
  "/icons/192.png",
  "/icons/512.png",
  "/404.html",
  "/sitemap.xml",
  {{- range hugo.Sites }}
    {{- range .Pages }}
  {{ printf "%q" .RelPermalink }},
    {{- end }}
  {{- end }}
];

self.addEventListener("install", (event) => {
  event.waitUntil(
    caches.open(CACHE_NAME).then((cache) => {
      return cache.addAll(PRECACHE);
    })
  );

//  self.skipWaiting(); // remove if want to changes to occur without pressing reload button
});

self.addEventListener("activate", (event) => {
  event.waitUntil(
    caches.keys().then((keys) => {
      return Promise.all(
        keys
          .filter((key) => key !== CACHE_NAME)
          .map((key) => caches.delete(key))
      );
    })
  );

  self.clients.claim();
});

self.addEventListener("fetch", (event) => {
  event.respondWith(
    caches.match(event.request).then((cachedResponse) => {
      return cachedResponse || fetch(event.request);
//      return fetch(event.request) || cachedResponse; // switch if you want to fetch resources first and serve cached if offline; current serve cache then request online fetch if error or not available
    })
  );
});

self.addEventListener("message", (event) => {
  if (event.data?.type === "SKIP_WAITING") {
    self.skipWaiting();
  }
});

Makefile

  • Make file requires tabs not space indentation
.PHONY: help cl gen serve pub dev

help:
	@echo "Usage: make <option>"
	@echo "gen ..... generate site"
	@echo "serve ... generate site and http serve 8080"
	@echo "dev ..... hugo serve"
	@echo "pub ..... generate and publish to griffin nginx-blog/config/www"

cl:
	@clear

gen:
	hugo --cleanDestinationDir

serve: gen
	cd /opt/websites/patk/public && \
	python3 -m http.server -b 0.0.0.0 8080 && \
	cd .. 

dev: cl
	hugo serve

pub: cl gen
	rsync -azvP --delete public/ griffin-wsl:/opt/docker/volumes/nginx-blog/config/www