Keep pages for reading offline
You'll make an app installable, keep every page a visitor opened for when the network goes and tell an open page when a new build is ready. It takes one configuration section, one page and one island.
Before you start
Install from crates.io. You need no Node and no package manager.
cargo install snapfire_compiler
cargo install snapfire_fsr_cli
fsr --version
Every command and screenshot on this page was captured with fsr 0.x.
Start the app#
The island at the end reads the store through React, so the app needs React.
Give it a few pages to keep. The trails live in a plain module:
;;
;
;
;
;
;
;
Add the [pwa] section#
[]
= "Trails"
= "Trails"
= "#2f5d3a"
= "#f4f1ea"
= "/offline"The host now answers /manifest.webmanifest from those fields. start_url defaults to / and display to standalone. Each PNG, WebP or SVG in app/icons/ whose name carries its size becomes an icon, so put two there:
app/icons/android-chrome-192x192.png
app/icons/android-chrome-512x512.png
Every page now links the manifest and carries the theme colour. Outside development the head also names the worker:
boot() in app/src/main.ts registers it. You write no worker code.
Write the offline page#
offline names a route. The worker fetches it at install and shows it for any page it never kept:
;
Show the connection and the update#
The client keeps two store keys current. online follows the browser's online and offline events. updateReady turns true once a newer build's worker has taken over the page. An island reads them like any other key:
;
;
;
full makes following the link a full document load, so that load runs the new build. Place the island in the layout so every page has it:
;
;
;
Run the production shape#
fsr dev links the manifest and registers no worker, so a rebuild is never hidden behind a cache:
To see the worker, build and serve with RELEASE_ENV=production. That reads config/production.toml, which turns server.dev off and asks for a session key:
Check that the worker is there now:
The worker at /sw.js is written at boot from what the host serves:
| Request | Answered |
|---|---|
| A module or stylesheet of the build | From the build's own cache, filled at install with every hashed name |
The client, the vendor modules, the manifest and offline | Fetched at install and kept |
| Any other GET on this origin | From the network. A response that is not private or no-store is kept. When the network fails the kept copy answers. A page never kept shows offline |
/_sf/, /__fsr/, a POST | Never touched |
A visitor holding a session gets private documents, so nothing of theirs is kept.
Read it offline#
Open / and wait a moment for the worker to install. Then click through to Long Ridge. Stop the server, switch the browser offline in its developer tools and load each page in full:
/trail/ridgereads as it did, with "Offline: you are reading saved pages." in the header./trail/marsh, which you never opened, shows "No signal"./shows "No signal" too. You opened it before the worker was running, so the worker never saw it. A page counts once the worker is in control, which is from the second page on.
The header line follows the browser's own online and offline events. A stopped server alone doesn't change it.
Ship a new build#
Start the server again and change something the browser loads, such as the stylesheet:
}Build again, restart fsr serve and load a page. The browser finds a new /sw.js, which installs the new build's cache beside the old one. The new worker takes over, updateReady turns true and the header says "A new version is ready. Open it". Follow the link and the banner is gone.
A client navigation does not check for a new worker. Clicking between trails after the deploy shows no banner until a page is loaded in full.
A change that only reaches the server, such as a loader's data, leaves every module's hash the same. The worker is the same file, so no update is announced.
Next up: 400. Test a loader.