-
-
Notifications
You must be signed in to change notification settings - Fork 230
Closed
Description
I'm not 100% sure if this is user error, or a bug, but I have scoured many similar issues such as #665, the documentation, and the code here to work this out 😅 Hopefully someone can help!
Context
- I am building a PWA that is hosted under
https://mapper.fmtm.hotosm.org
. - Users either access via the main domain, or directly into the project route: e.g.
https://mapper.fmtm.hotosm.org/project/1
. - We are trying to aggressively cache everything on the website, so it can be loaded entirely offline (for field mapping).
Problem
- Loading directly from the site root works fine,
https://mapper.fmtm.hotosm.org
, and manifest and sw.js are found. - However, if loading from
https://mapper.fmtm.hotosm.org/project/1
, I get errors:
manifest:
Manifest fetch from https://mapper.dev.fmtm.hotosm.org/project/manifest.webmanifest failed, code 404
sw.js
SW registration error TypeError: Failed to register a ServiceWorker for scope ('https://mapper.dev.fmtm.hotosm.org/') with script ('https://mapper.dev.fmtm.hotosm.org/project/sw.js'): A bad HTTP response code (404) was received when fetching the script.
Both exist on the server under https://mapper.dev.fmtm.hotosm.org/sw.js
and https://mapper.dev.fmtm.hotosm.org/manifest.webmanifest
Note we also have some issues related to caching, but one thing at a time!
Files For Debug
vite.config.ts
:
const pwaOptions: Partial<VitePWAOptions> = {
// This ensures that caches are invalidated when the app is updated
registerType: 'autoUpdate',
injectRegister: 'script', # originally I was using 'auto', but changed to try and tweak manually
strategies: 'generateSW',
// Important to ensure the PWA runs on the **entire** website
// For example, it's possible to have `scope: '/project'` for a PWA
// on only part of the website
scope: '/',
// Allow testing the PWA during local development
devOptions: {
enabled: true,
// // Don't fallback on document based (e.g. `/some-page`) requests
// We need this to include /project/ID as well as home page for direct load when offline
navigateFallbackAllowlist: [/^\/$/, /^\/project\/.+$/],
// Enable this to disable runtime caching for easier debugging
// disableRuntimeConfig: true,
},
// // Cache all the imports, including favicon
workbox: {
// Don't fallback on document based (e.g. `/some-page`) requests
// Even though this says `null` by default, I had to set this specifically to `null` to make it work
// We can do this because we are explicitly handling all navigation routes via runtimeCaching
navigateFallback: null,
// Cache all imports
// globPatterns: ["**/*"],
globPatterns: ['**/*.{js,css,html,wasm,ico,svg,png,jpg,jpeg,gif,webmanifest}'],
// This is where the magic happens: routes to cache key to cache to
runtimeCaching: [
// Handle SPA navigations (e.g., /, /project/123)
{
urlPattern: ({ request }: RouteMatchCallbackOptions) => request.mode === 'navigate',
// Try to get fresh version; fallback when offline
handler: 'NetworkFirst',
// // NOTE the below doesn't work yet, so we use NetworkFirst above
// // This will load the cached version instantly, then update the cache in the
// // background. On next refresh, the updated app will be loaded.
// // This is a good tradeoff, as slightly outdated content is only shown temporarily.
// handler: 'StaleWhileRevalidate',
options: {
cacheName: 'field-tm-html-pages',
networkTimeoutSeconds: 5, // Setting only valid for NetworkFirst, else errors
expiration: {
maxEntries: 50,
maxAgeSeconds: 60 * 60 * 24 * 7, // 7 days
},
},
},
// Static assets (e.g., JS, CSS, icons, fonts)
{
urlPattern: ({ url }: RouteMatchCallbackOptions) => url.origin === self.location.origin,
// Serve fast from cache; change infrequently
handler: 'CacheFirst',
options: {
cacheName: 'field-tm-static-assets',
expiration: {
maxEntries: 300,
maxAgeSeconds: 60 * 60 * 24 * 14, // 14 days
},
},
},
],
// We need to cache files up to 15MB to allow for PGLite to be cached
maximumFileSizeToCacheInBytes: 15 * 1024 * 1024,
},
// Cache all the static assets in the static folder
includeAssets: ['**/*', 'icons/*.svg'],
manifest: {
id: 'com.hotosm.field-tm',
name: 'Field-TM',
short_name: 'Field-TM',
description: 'Coordinated field mapping for Open Mapping campaigns.',
categories: ['mapping', 'humanitarian', 'hotosm', 'field', 'odk'],
scope: '/',
start_url: '/',
orientation: 'portrait',
dir: 'auto',
display: 'standalone',
launch_handler: 'auto',
theme_color: '#d63f3f',
background_color: '#d63f3f',
icons: [
{
src: 'pwa-64x64.png',
sizes: '64x64',
type: 'image/png',
purpose: 'any',
},
{
src: 'pwa-192x192.png',
sizes: '192x192',
type: 'image/png',
},
{
src: 'pwa-512x512.png',
sizes: '512x512',
type: 'image/png',
},
{
src: 'maskable-icon-512x512.png',
sizes: '512x512',
type: 'image/png',
purpose: 'maskable',
},
],
screenshots: [
{
src: 'screenshot-mapper.jpeg',
sizes: '1280x720',
type: 'image/jpeg',
form_factor: 'wide',
label: 'Mapper App',
},
],
related_applications: [
{
platform: 'web',
url: 'https://fmtm.hotosm.org',
},
],
},
};
svelte.config.ts
import adapter from '@sveltejs/adapter-static';
import { vitePreprocess } from '@sveltejs/vite-plugin-svelte';
/** @type {import('@sveltejs/kit').Config} */
const config = {
base: '/',
// Consult https://kit.svelte.dev/docs/integrations#preprocessors
// for more information about preprocessors
preprocess: vitePreprocess(),
kit: {
// adapter-auto only supports some environments, see https://kit.svelte.dev/docs/adapter-auto for a list.
// If your environment is not supported, or you settled on a specific environment, switch out the adapter.
// See https://kit.svelte.dev/docs/adapters for more information about adapters.
adapter: adapter({
// default options are shown. On some platforms
// these options are set automatically — see below
pages: 'build',
assets: 'build',
// This fallback is required to compile as SPA
// as we don't have any server side rendering here
fallback: 'index.html',
precompress: false,
strict: true,
}),
alias: {
$lib: 'src/lib',
$components: 'src/components',
$store: 'src/store',
$routes: 'src/routes',
$constants: 'src/constants',
$styles: 'src/styles',
$assets: 'src/assets',
$translations: 'src/translations',
$static: 'static',
$migrations: '../migrations',
},
},
};
export default config;
I tested with a +layout.svelte
file like this:
import { pwaInfo } from 'virtual:pwa-info';
const webManifestLink = $derived(pwaInfo ? pwaInfo.webManifest.linkTag : '');
<svelte:head>
{@html webManifestLink}
</svelte:head>
and also without the above, but adding the manifest and sw.js manually:
<svelte:head>
<link rel="manifest" href="/manifest.webmanifest">
<script>
if ('serviceWorker' in navigator) {
navigator.serviceWorker.register('/sw.js', { scope: '/' });
}
</script>
</svelte:head>
The second approach did fix the loading of the manifest, but the sw.js file was still loaded from the wrong place!
Thanks so much for your time & I appreciate the support 🙏
Metadata
Metadata
Assignees
Labels
No labels