Node.js PWA

I’m creating a node.js PWA and was able to get most of it working (using @ben’s tutorial here for a php pwa spa plus a bunch of googling).

in any case, the dynamic cache seems to work fine, as once i visit a page, i can revisit while offline. however, i can’t seem to get fallback.ejs to load in place of any page if i’ve never been to a page before going offline. i check the local storage and its blank.

i think its likely because of the default folder hierarchy. the service worker is in the public folder, which is set to be the web root folder in project settings (as default). but all the content pages are set to be in the views folder:

  • project_folder/
    • public/
      • serviceworker.js
      • manifest.json
      • asset/
      • css/
    • views/
      • layouts/
        • main.ejs
      • partials/
        • mypartial.ejs
      • index.ejs
      • about.ejs
      • fallback.ejs
    • app
    • lib

so then the topmost level the serviceworker.js can go is the public folder. my question is, what is the best way to fix this? should i change the webroot folder to the main project folder (one level higher than “public”), and if so, what are the downsides? seems like not a good idea…

if not, how do i get the pages from the views folder, etc to cache?

any help would be greatly appreciated!

bumping… can anyone provide some guidance here please? :pray: :pray: :pray:

maybe @Teodor or @ben?

Can you copy and format your service worker contents?

In any case, try caching the route and not the file.

i tried a bunch of different variants for the folder structure for the assets array and for the very last catch function with the request ‘.ejs’ and the caches.match fallback.ejs, but this is what i currently have:

const staticCacheName = 'site-static-pwa-v2.0.1';
const dynamicCacheName = 'site-dynamic-pwa-v2.0.1';
const assets = [
'../',
'../views/layouts/main.ejs',
'../views/index.ejs',
'../views/fallback.ejs',
'https://stackpath.bootstrapcdn.com/bootstrap/4.4.1/css/bootstrap.min.css',
'/css/style.css',
'https://stackpath.bootstrapcdn.com/font-awesome/4.7.0/css/font-awesome.min.css',
'https://fonts.googleapis.com/css?family=Roboto:400,700',
'/dmxAppConnect/dmxAppConnect.js',
'https://code.jquery.com/jquery-3.4.1.slim.min.js',
'/app.js',
'/manifest.json'
]

// cache size limit function
const limitCacheSize = (name, size) => {
caches.open(name).then(cache => {
    cache.keys().then(keys => {
        if (keys.length > size) {
            cache.delete(keys[0]).then(limitCacheSize(name, size));
        }
    });
});
};

// install event
self.addEventListener('install', evt => {
//console.log('service worker installed');
evt.waitUntil(
    caches.open(staticCacheName).then((cache) => {
        console.log('caching shell assets from public folder sw');
        cache.addAll(assets);
    })
);
});

// activate event
self.addEventListener('activate', evt => {
//console.log('service worker activated');
evt.waitUntil(
    caches.keys().then(keys => {
        //console.log(keys);
        return Promise.all(keys
            .filter(key => key !== staticCacheName && key !== dynamicCacheName)
            .map(key => caches.delete(key))
        );
    })
);
});

// fetch event
self.addEventListener('fetch', evt => {
//console.log('fetch event', evt);
evt.respondWith(
    caches.match(evt.request).then(cacheRes => {
        return cacheRes || fetch(evt.request).then(fetchRes => {
            return caches.open(dynamicCacheName).then(cache => {
                cache.put(evt.request.url, fetchRes.clone());
                // check cached items size
                limitCacheSize(dynamicCacheName, 100);
                return fetchRes;
            })
        });
    }).catch(() => {
        if (evt.request.url.indexOf('.ejs') > -1) {
            return caches.match('/fallback.ejs');
        }
    })
);
});// JavaScript Document

In your ‘assets’ try caching the URL route, not the files directly.

2 Likes

omg, yes, thank you! that worked!

weirdly, however, the assets (pictures) on the homepage do not cache on the first load, only if i click to another page and then click back to the homepage:

if i load the hompage initially, then “go offline” then click somewhere else, the fallback page loads. however, if i then click to go back to the homepage, the page load but the pictures do not. if i load the homepage, go to another page, then come back to the homepage, THEN go offline, then go to another page (it loads the fallback), then go back to the homepage, the images will have cached and load up.

Unfortunately I don’t have any active PWA project so I can’t test things. I just knew that caching was about the response given by the URL and not the actual files :slight_smile:

Sorry I can’t be of more help on this.

change it to:

cache.put(evt.request, fetchRes.clone());

There is a problem with content pages since the server returns a different response depending on the Accept header that is set. The cache doesn’t save the different versions and only saves the latest response.

Let me explain more in detail the special situation with NodeJS and PWA caching.

In NodeJS and its templating support, we introduced the so called partial refresh for a view.
So instead of loading the full page only the content view is loaded in a hybrid refreshing approach.

So guarantee that and the full page (for full browser refresh or SEO) as well only the view can be loaded, we have a smart routing solution. It detects based on accept header what content is required and set it out. While the URL stays the same.

So if a browser ask the page url with accept text/html - full page is returned.
While our App Connect when fetching the content view only asks the same URL but with accept html/fragment - only the partial fragment is returned.

However they both live under the same url. So if you are checking the url only under the PWA cache they will overwrite each other.

And this is solved by comparing the whole request - ie including the accept headers - for the cache entries.

So by doing the fix above as Patrick suggestion you are solving your issue.

But if you do want to cache both full page and the partial - you might have a problem with the current PAW service worker code.

This is because each cache storage only contains unique URL’s and you can’t have twice the same url.
But this can be solved by using two different cache storages - one for the full and one for the partials.
it all depends on how you exactly you plan to use the dynamic cache.

Hope it is all a bit clearer now :slight_smile: