SolidStart PWA Support

Posting this here since this should be search indexable, right? Becasue there's almost nothing useful relating to this topic >~<... The main issue is that the manifest and service worker aren’t auto injected since SolidStart's way of bundling is different. Getting your web app recognized as a PWA - Install vite-plugin-pwa as a dev dependency and follow this guide. - Also install @solidjs/meta since it's the recommended way of adding <link>s. - Add import "vite-plugin-pwa/info"; import "vite-plugin-pwa/solid"; to src/global.d.ts (so typescript will be able to detect some Vite virtual modules we’ll import later). - Add import { useRegisterSW } from "virtual:pwa-register/solid"; import { pwaInfo } from "virtual:pwa-info"; import { MetaProvider, Link } from "@solidjs/meta"; to your App.tsx file, and edit the main component like this:
export default function App() {
useRegisterSW({ immediate: true });

return (
<MetaProvider>
{/* check for and add a link for the webmanifest */}
{pwaInfo?.webManifest?.href ? (<Link rel="manifest" href={pwaInfo.webManifest.href}/>):(“”)}

{/* Other stuff in the `App` Component */}
</MetaProvider>
);
}
export default function App() {
useRegisterSW({ immediate: true });

return (
<MetaProvider>
{/* check for and add a link for the webmanifest */}
{pwaInfo?.webManifest?.href ? (<Link rel="manifest" href={pwaInfo.webManifest.href}/>):(“”)}

{/* Other stuff in the `App` Component */}
</MetaProvider>
);
}
- And that should do it, although, note that this doesn’t give offline functionality (that will take a few more tweaks). You may still get terminal warnings but it works well enough for now.
4 Replies
Overdrive8911
Overdrive8911OP2mo ago
Offline Functionality This took me a tad longer but the main issue is that, as of SolidStart v1.2.0, bundled client-side related code is outputted in a nested _build folder, including the service worker file. So the worker gets a much more limited scope. To deal with this: - Assuming you want to support both CSR and SSR, you'll want an environment variable e.g. const isSSRBuild = process.env.SSR === "true" - Your VitePWA entry in app.config.ts should at least, have these fields:
VitePWA({
// So the service worker can access the root of the build folder instead of `/_build/`. This requires a header to actually have the required effect though
scope: "/",

workbox: {
// Explicitly define the path for workbox to search for cacheable assets so that `public` assets and html files are visible. Set this to whatever your build folder is
globDirectory: "dist",

// Regular stuff you want to precache
globPatterns: ["**/*.{js,css,html,ico,png,webp,svg,woff2,woff}"],

// Skip server and worker stuff
globIgnores: ["**/_server/**", "**/_worker.js/**"],

// Since the service worker gets tossed into a nested `/_build/`, we have to explicitly prepend all precache asset urls (because every network request the service worker makes will have `/_build/` prepended thanks to it's location)
modifyURLPrefix: {
"": "../",
},

// For cases where you want to serve a default page when navigating to an uncached page. Not really useful for SSR.
…(isSSRBuild
? { navigateFallback: null }
: { navigateFallback: "../index.html" }),

runtimeCaching: [
// So ssr'd pages get cached upon navigation
{
urlPattern: ({ request }) => request.destination === "document",
handler: "NetworkFirst",
options: {
cacheName: "html-pages",
cacheableResponse: {
statuses: [0, 200],
},
},
},
],

// For some reason, `runtimeCaching` is ignored unless these 2 are set
skipWaiting: true,
clientsClaim: true,

cleanupOutdatedCaches: true,
},
});
VitePWA({
// So the service worker can access the root of the build folder instead of `/_build/`. This requires a header to actually have the required effect though
scope: "/",

workbox: {
// Explicitly define the path for workbox to search for cacheable assets so that `public` assets and html files are visible. Set this to whatever your build folder is
globDirectory: "dist",

// Regular stuff you want to precache
globPatterns: ["**/*.{js,css,html,ico,png,webp,svg,woff2,woff}"],

// Skip server and worker stuff
globIgnores: ["**/_server/**", "**/_worker.js/**"],

// Since the service worker gets tossed into a nested `/_build/`, we have to explicitly prepend all precache asset urls (because every network request the service worker makes will have `/_build/` prepended thanks to it's location)
modifyURLPrefix: {
"": "../",
},

// For cases where you want to serve a default page when navigating to an uncached page. Not really useful for SSR.
…(isSSRBuild
? { navigateFallback: null }
: { navigateFallback: "../index.html" }),

runtimeCaching: [
// So ssr'd pages get cached upon navigation
{
urlPattern: ({ request }) => request.destination === "document",
handler: "NetworkFirst",
options: {
cacheName: "html-pages",
cacheableResponse: {
statuses: [0, 200],
},
},
},
],

// For some reason, `runtimeCaching` is ignored unless these 2 are set
skipWaiting: true,
clientsClaim: true,

cleanupOutdatedCaches: true,
},
});
- Your nitro config also needs to specify the right headers:
server: {
routeRules: {
"**/*.html": {
headers: {
"cache-control": "public, max-age=0, must-revalidate",
},
},

// Give the service worker the required header so it can increase it's `scope`
"/_build/sw.js": {
headers: {
"cache-control": "public, max-age=0, must-revalidate",
"service-worker-allowed": "/",
},
},
},
};
server: {
routeRules: {
"**/*.html": {
headers: {
"cache-control": "public, max-age=0, must-revalidate",
},
},

// Give the service worker the required header so it can increase it's `scope`
"/_build/sw.js": {
headers: {
"cache-control": "public, max-age=0, must-revalidate",
"service-worker-allowed": "/",
},
},
},
};
Also, you'll need to run the build script twice Hope this helps someone else ^w^
Madaxen86
Madaxen862mo ago
@Atila for the docs?
Atila
Atila2mo ago
that’s really cool! Thanks for the research @Overdrive8911 , and for the ping @Madaxen86 i think it’s worth to have at least as a guide, but perhaps even as a PWA primitive for :start: i’ll have a deeper look once i’m on the keyboard and probably create an issue on the repo
Overdrive8911
Overdrive8911OP2mo ago
Glad you found it useful. But please note that while CSR pages work perfectly offline or not, SSR + offline caching has some slight "wonkiness" with route reloading that I couldn't easily reproduce. Although I only the aforementioned issue sometimes when changing from csr to ssr without clearing the cache And one last thing I forgot, you need to run the build command twice In the first build, Workbox will try to find assets to precache in the output folder, but the required folder won't be available and fully populated till the end of the first build process. Which is why you'd want to run / defer the plugin after the build in some way. The easiest bandaid solution I foundwas simply running the build script twice.

Did you find this page helpful?