diff --git a/web/public/sw.js b/web/public/sw.js
index d3967441..33154628 100644
--- a/web/public/sw.js
+++ b/web/public/sw.js
@@ -2,6 +2,7 @@
 import { cleanupOutdatedCaches, createHandlerBoundToURL, precacheAndRoute } from "workbox-precaching";
 import { NavigationRoute, registerRoute } from "workbox-routing";
 import { NetworkFirst } from "workbox-strategies";
+import { clientsClaim } from "workbox-core";
 
 import { dbAsync } from "../src/app/db";
 
@@ -224,6 +225,8 @@ precacheAndRoute(
   self.__WB_MANIFEST
 );
 
+// Claim all open windows
+clientsClaim();
 // Delete any cached old dist files from previous service worker versions
 cleanupOutdatedCaches();
 
diff --git a/web/src/index.jsx b/web/src/index.jsx
index d60c05a4..040f94b5 100644
--- a/web/src/index.jsx
+++ b/web/src/index.jsx
@@ -1,6 +1,34 @@
 import * as React from "react";
 import { createRoot } from "react-dom/client";
+// eslint-disable-next-line import/no-unresolved
+import { registerSW } from "virtual:pwa-register";
 import App from "./components/App";
 
+// fetch new sw every hour, i.e. update app every hour while running
+const intervalMS = 60 * 60 * 1000;
+
+// https://vite-pwa-org.netlify.app/guide/periodic-sw-updates.html
+registerSW({
+  onRegisteredSW(swUrl, registration) {
+    if (!registration) {
+      return;
+    }
+
+    setInterval(async () => {
+      if (registration.installing || navigator?.onLine === false) return;
+
+      const resp = await fetch(swUrl, {
+        cache: "no-store",
+        headers: {
+          cache: "no-store",
+          "cache-control": "no-cache",
+        },
+      });
+
+      if (resp?.status === 200) await registration.update();
+    }, intervalMS);
+  },
+});
+
 const root = createRoot(document.querySelector("#root"));
 root.render(<App />);
diff --git a/web/vite.config.js b/web/vite.config.js
index 86e17646..4df6ef9d 100644
--- a/web/vite.config.js
+++ b/web/vite.config.js
@@ -16,7 +16,7 @@ export default defineConfig(({ mode }) => ({
     react(),
     VitePWA({
       registerType: "autoUpdate",
-      injectRegister: "inline",
+      injectRegister: null,
       strategies: "injectManifest",
       devOptions: {
         enabled: true,