Next-pwa: no matching service worker detected. you may need to reload the page or check that the scope of the service worker for the current page encloses the scope and start url from the manifest

Created on 18 Jun 2020  路  29Comments  路  Source: shadowwalker/next-pwa

Hello,
I've been trying to setup next-pwa for the past two days but with no luck at all.
Here's my next config:

require('dotenv').config();
const path = require('path');
const Dotenv = require('dotenv-webpack');
const withPWA = require('next-pwa');
const withPlugins = require('next-compose-plugins');

const nextConfig = {
    webpack: (config, { isServer }) => {
        // Fixes npm packages that depend on `fs` module
        if (!isServer) {
            config.node = {
                fs: 'empty',
            };
        }
        // Read the .env file
        config.plugins = [
            ...config.plugins,
            new Dotenv({
                path: path.join(__dirname, process.env.ENV_FILE_PATH || '.env'),
                systemvars: true,
            }),
        ];
        return config;
    },
};

module.exports = withPlugins(
    [
        [
            withPWA,
            {
                pwa: {
                    dest: 'public',
                    sw: 'service-worker.js',
                    maximumFileSizeToCacheInBytes: 3000000,
                },
            },
        ],
    ],
    nextConfig
);

Next.js v9.2.1

server.js
even though it's not required since v9 as mentioned in the docs, but I tried anyways

                if (
                    pathname === '/service-worker.js' ||
                    pathname.startsWith('/workbox-')
                ) {
                    const filePath = join(__dirname, '.next', pathname);
                    app.serveStatic(req, res, filePath);
                }

/public/manifest.json

{
  "name": "hello",
  "short_name": "hello",
  "theme_color": "#00a699",
  "background_color": "#ffffff",
  "display": "standalone",
  "orientation": "portrait",
  "scope": "/",
  "start_url": "/",
  "icons": [
    {
      "src": "images/icons/icon-72x72.png",
      "sizes": "72x72",
      "type": "image/png"
    },
    {
      "src": "images/icons/icon-96x96.png",
      "sizes": "96x96",
      "type": "image/png"
    },
    {
      "src": "images/icons/icon-128x128.png",
      "sizes": "128x128",
      "type": "image/png"
    },
    {
      "src": "images/icons/icon-144x144.png",
      "sizes": "144x144",
      "type": "image/png"
    },
    {
      "src": "images/icons/icon-152x152.png",
      "sizes": "152x152",
      "type": "image/png"
    },
    {
      "src": "images/icons/icon-192x192.png",
      "sizes": "192x192",
      "type": "image/png"
    },
    {
      "src": "images/icons/icon-384x384.png",
      "sizes": "384x384",
      "type": "image/png"
    },
    {
      "src": "images/icons/icon-512x512.png",
      "sizes": "512x512",
      "type": "image/png"
    }
  ],
  "splash_pages": null
}

Any help is highly appreciated.

done

All 29 comments

Hey @omar-dulaimi , it seems that your service worker is not being server correctly. I highly recommend not using the server.js unless you have a very good reason to use it.

Anyway, I have a scenario where I also had to configure the server.js, which was configured with express.

server.get('/sw.js', (request, response) => {
    const filePath = join(__dirname, 'public/sw.js');
    return app.serveStatic(request, response, filePath);
  });

  server.get(/workbox-[a-zA-Z0-9]+.js$/, async (request, response) => {
    const basePath = resolve(__dirname, 'public');
    const result = await FindFiles(basePath, /workbox-[a-zA-Z0-9]+.js$/);

    const [fileMatchResult] = result;
    const { dir, file } = fileMatchResult;
    const fileWithPathToServe = join(dir, file);

    return app.serveStatic(request, response, fileWithPathToServe);
  });

Try to serve the file directly with a route matching the service worker and the generated workbox-*.js

Also, the FindFiles is a class from an external lib const FindFiles = require('file-regex');

Hello @LucasMallmann, thank you for your suggestions. I actually use server.js for some custom routes. Anyways, I tried the snippet above and it didn't work as well.
Am I missing something?
Anything I should clear/cleanup that might has anything to do with that?
Chrome version related?

Hey @omar-dulaimi could you share your entire server.js configuration please?

@LucasMallmann Sure thing, here you go(Although I shouldn't do that):

const express = require('express');
const compression = require('compression');
const { parse } = require('url');
const { join } = require('path');
const dev = process.env.NODE_ENV !== 'production';
const next = require('next');
const app = next({ dev });
const handle = app.getRequestHandler();
const Cors = require('cors');
const Sentry = require('@sentry/node');
const cookieParser = require('cookie-parser');
const csrf = require('csurf');
const helmet = require('helmet');
const {
    clearCompleteCache,
    clearCacheForRequestUrl,
    cacheManager,
} = require('./ssr-cacher')(app);
const { initMiddleware } = require('./util/init-middleware');
const s3Client = require('./clients/s3');
const routes = require('./routes');
const {
    port,
    hostName,
    hostNameRegex,
    appEnv,
    sentryDsnBackend,
} = require('./config/variables');
const pathMap = require('./util/pathMap');
const isSamePage = require('./util/isSamePage');

// Initialize the cors middleware
const cors = initMiddleware(
    // You can read more about the available options here: https://github.com/expressjs/cors#configuration-options
    appEnv === 'production'
        ? Cors({ origin: ['origin'] })
        : Cors({
            origin: hostNameRegex,
        })
);

/* this is used for server only, find the web version in pages => _app */
Sentry.init({
    dsn: `${sentryDsnBackend}`,
});

app.prepare()
    .then(() => {
        const server = express();
        server.use(Sentry.Handlers.requestHandler());
        server.use(helmet());
        server.use(compression());
        server.use(cookieParser());
        server.use(csrf({ cookie: true }));

        routes.map((route) =>
            server.get(route.path, async (req, res) => {
                if (req) {
                    await cors(req, res);
                }
                const parsedUrl = parse(req.url, true);
                const { pathname } = parsedUrl;
                if ('/properties/:itemUrl' === route.path) {
                    // if (dev || appEnv === 'staging' || req.query.noCache) {
                    res.setHeader('X-Cache-Status', 'DISABLED');
                    return app.render(req, res, route.page, {
                        ...req.params,
                        itemUrl: req.params.itemUrl,
                    });
                    // } else {
                    //     return cacheManager({
                    //         req,
                    //         res,
                    //         pagePath: route.page,
                    //         queryParams: {
                    //             ...req.params,
                    //             itemUrl: req.params.itemUrl,
                    //         },
                    //     });
                    // }
                }
                if (
                    '/search/:type' === route.path ||
                    '/search/:type/:cat1' === route.path ||
                    '/search/:type/:cat1/:cat2' === route.path ||
                    '/search/:type/:cat1/:cat2/:cat3' === route.path ||
                    '/search/:type/:cat1/:cat2/:cat3/:cat4' === route.path ||
                    '/search/:type/:area' === route.path ||
                    '/search/:type/:cat1/:area' === route.path ||
                    '/search/:type/:cat1/:cat2/:area' === route.path ||
                    '/search/:type/:cat1/:cat2/:cat3/:area' === route.path ||
                    '/search/:type/:cat1/:cat2/:cat3/:cat4/:area' === route.path
                ) {
                    if (dev || appEnv === 'staging' || req.query.noCache) {
                        res.setHeader('X-Cache-Status', 'DISABLED');
                        return app.render(req, res, route.page);
                    } else {
                        return cacheManager({
                            req,
                            res,
                            pagePath: route.page,
                        });
                    }
                }
                if ('/update-list/:id' === route.path) {
                    if (validateUser(req, res)) {
                        if (dev || appEnv === 'staging' || req.query.noCache) {
                            res.setHeader('X-Cache-Status', 'DISABLED');
                            return app.render(req, res, route.page, {
                                id: req.params.id,
                            });
                        } else {
                            return cacheManager({
                                req,
                                res,
                                pagePath: route.page,
                                queryParams: {
                                    ...req.params,
                                    id: req.params.id,
                                },
                            });
                        }
                    }
                }
                if ('/user-details/:id' === route.path) {
                    if (dev || appEnv === 'staging' || req.query.noCache) {
                        res.setHeader('X-Cache-Status', 'DISABLED');
                        return app.render(req, res, route.page, {
                            id: req.params.id,
                        });
                    } else {
                        return cacheManager({
                            req,
                            res,
                            pagePath: route.page,
                            queryParams: {
                                ...req.params,
                                id: req.params.id,
                            },
                        });
                    }
                }
            })
        );

        server.get('*/new-list', async (req, res) => {
            if (req) {
                await cors(req, res);
            }
            if (validateUser(req, res)) {
                if (dev || appEnv === 'staging' || req.query.noCache) {
                    res.setHeader('X-Cache-Status', 'DISABLED');
                    return handle(req, res);
                } else {
                    return cacheManager({ req, res, pagePath: req.path });
                }
            }
        });

        server.get('/service-worker.js', (request, response) => {
            const filePath = join(__dirname, 'public/service-worker.js');
            return app.serveStatic(request, response, filePath);
        });

        server.get(/workbox-[a-zA-Z0-9]+.js$/, async (request, response) => {
            const basePath = resolve(__dirname, 'public');
            const result = await FindFiles(basePath, /workbox-[a-zA-Z0-9]+.js$/);

            const [fileMatchResult] = result;
            const { dir, file } = fileMatchResult;
            const fileWithPathToServe = join(dir, file);

            return app.serveStatic(request, response, fileWithPathToServe);
        });

        server.get('*/user', async (req, res) => {
            if (req) {
                await cors(req, res);
            }
            if (validateUser(req, res)) {
                // if (dev || appEnv === 'staging' || req.query.noCache) {
                res.setHeader('X-Cache-Status', 'DISABLED');
                return handle(req, res);
                // }
                // else {
                //     return cacheManager({ req, res, pagePath: req.path });
                // }
            }
        });

        server.get('/sitemap-index.xml', async function (req, res) {
        });

        server.get(/sitemap\-[0-9]*\.xml/, async function (req, res) {
        });

        // Do not use caching for _next files
        server.get('/_next/*', (req, res) => {
            return handle(req, res);
        });

        // Do  use caching for files
        server.get('*', async (req, res) => {
            if (req) {
                await cors(req, res);
            }
            if (dev || appEnv === 'staging' || req.query.noCache) {
                res.setHeader('X-Cache-Status', 'DISABLED');
                if (req.url.startsWith('/public/')) {
                    return app.serveStatic(req, res, join(__dirname, req.url));
                }
                return handle(req, res);
            } else {
                if (req.url.startsWith('/public/')) {
                    return app.serveStatic(req, res, join(__dirname, req.url));
                }
                if (!pathMap.has(req.url)) {
                    for (let key of pathMap.keys()) {
                        if (req.url.startsWith(key)) {
                            if (isSamePage(key, req.url)) {
                                return cacheManager({
                                    req,
                                    res,
                                    pagePath: req.url,
                                });
                            } else {
                                return handle(req, res);
                            }
                        }
                    }
                    return handle(req, res);
                } else {
                    if (pathMap.get(req.url).startsWith(req.url)) {
                        return cacheManager({ req, res, pagePath: req.path });
                    } else {
                        return handle(req, res);
                    }
                }
            }
        });

        server.use(Sentry.Handlers.errorHandler());

        server.purge('*', (req, res) => {
            if (req.query.clearCache) {
                clearCompleteCache(res, req);
            } else {
                clearCacheForRequestUrl(req, res);
            }
        });

        server.listen(port, (err) => {
            if (err) throw err;
            console.log(`> Ready on http://localhost:${port}`);
        });
    })
    .catch((error) => {
        console.log('server.js => error', error);
        Sentry.captureException(error);
    });

function validateUser(req, res) {
    if (
        req.headers.cookie &&
        req.headers.cookie.indexOf('persist:user') > -1
    ) {
        return true;
    } else {
        res.redirect('/user/login');
    }
}

Hey @omar-dulaimi , try to put the server.get('/service-worker.js')code above in your server.js. Express has an order of execution to find the routes, so if you put the service worker related routes first, you can avoid having trouble with the routes order.

Hey @LucasMallmann
I moved both handlers above, right below
server.use(csrf({ cookie: true }));
But it still didn't work.

Do I need to update NextJs to the latest version?

Don't know why I have a hunch about the issue being in this config:

module.exports = withPlugins(
    [
        [
            withPWA,
            {
                pwa: {
                    dest: 'public',
                    sw: 'service-worker.js',
                    maximumFileSizeToCacheInBytes: 3000000,
                },
            },
        ],
    ],
    nextConfig
);

@LucasMallmann Just an observation, do meta tags have to be in the Head element in _document?
I currently have them in the head page.

@omar-dulaimi static meta tags should better to put in <Head> in the _document.jsx or _document.tsx.
To help with the issue, what was the return if you directly do a HTTP GET request to /service-worker.js

Hello @shadowwalker
This this the response I get:
if(!self.define){const e=e=>{"require"!==e&&(e+=".js");let a=Promise.resolve();return s[e]||(a=new Promise(async a=>{if("document"in self){const s=document.createElement("script");s.src=e,document.head.appendChild(s),s.onload=a}else importScripts(e),a()})),a.then(()=>{if(!s[e])throw new Error(Module ${e} didn鈥檛 register its module);return s[e]})},a=(a,s)=>{Promise.all(a.map(e)).then(e=>s(1===e.length?e[0]:e))},s={require:Promise.resolve(a)};self.define=(a,c,i)=>{s[a]||(s[a]=Promise.resolve().then(()=>{let s={};const n={uri:location.origin+a.slice(1)};return Promise.all(c.map(a=>{switch(a){case"exports":return s;case"module":return n;default:return e(a)}})).then(e=>{const a=i(...e);return s.default||(s.default=a),s})}))}}define("./service-worker.js",["./workbox-4d0bff02"],(function(e){"use strict";importScripts("worker-OtlFaJqhl67OWA04z7apg.js"),e.skipWaiting(),e.clientsClaim(),e.precacheAndRoute([{url:"/",revision:"OtlFaJqhl67OWA04z7apg"},{url:"/35ijbiczu50rddft3krqkavpveurvi.html",revision:"2a1a2e747b4bc389d84d9de28d9e511d"},{url:"/_next/static/OtlFaJqhl67OWA04z7apg/_buildManifest.js",revision:"da88887825bcc16983eef8edc0cc8469"},{url:"/_next/static/OtlFaJqhl67OWA04z7apg/pages/_app.js",revision:"b5c90280bc945e2f61aa448b28a88d7e"},{url:"/_next/static/OtlFaJqhl67OWA04z7apg/pages/_error.js",revision:"0a2999b23bfd8c4bc51c02bb6ebec599"},{url:"/_next/static/OtlFaJqhl67OWA04z7apg/pages/about.js",revision:"4ec4bdbce1b1e6526b0c5e47998ed777"},{url:"/_next/static/OtlFaJqhl67OWA04z7apg/pages/contact.js",revision:"e379e36f9797281f59f347c177bdf318"},{url:"/_next/static/OtlFaJqhl67OWA04z7apg/pages/footer.js",revision:"bcc147aaee922bc47c6eb58ca65bd729"},{url:"/_next/static/OtlFaJqhl67OWA04z7apg/pages/head.js",revision:"efeaa6ce493f40cb5180e168236e3455"},{url:"/_next/static/OtlFaJqhl67OWA04z7apg/pages/header.js",revision:"4d659aaf83280a668539f3317ce87191"},{url:"/_next/static/OtlFaJqhl67OWA04z7apg/pages/install.js",revision:"f68903c536867579022862ac4389d555"},{url:"/_next/static/OtlFaJqhl67OWA04z7apg/pages/new-list.js",revision:"9b66b12bca6813c9d82ef3a5a5622dd9"},{url:"/_next/static/OtlFaJqhl67OWA04z7apg/pages/new-list/steps/list-address.js",revision:"a314b551b623135dc69bcfd929b909e5"},{url:"/_next/static/OtlFaJqhl67OWA04z7apg/pages/new-list/steps/list-details.js",revision:"0288095fbb591d803cca1dc4ab5cc044"},{url:"/_next/static/OtlFaJqhl67OWA04z7apg/pages/new-list/steps/list-features.js",revision:"658bc9f88957dddc520d2d9666989d07"},{url:"/_next/static/OtlFaJqhl67OWA04z7apg/pages/new-list/steps/list-location.js",revision:"5d9b33e61df2dc8b071ae3d1ba6d8bf5"},{url:"/_next/static/OtlFaJqhl67OWA04z7apg/pages/new-list/steps/list-photos.js",revision:"02ad8a584edb880db58f5ce34ebc8e05"},{url:"/_next/static/OtlFaJqhl67OWA04z7apg/pages/new-list/steps/list-type.js",revision:"96e0c8bd2419b1d0368aea88940ccbd6"},{url:"/_next/static/OtlFaJqhl67OWA04z7apg/pages/new-list/steps/successfully-saved.js",revision:"9b59909afcd112885a4633894b7abb04"},{url:"/_next/static/OtlFaJqhl67OWA04z7apg/pages/privacy.js",revision:"d6c04b1194a6df894c1d1c350589bec9"},{url:"/_next/static/OtlFaJqhl67OWA04z7apg/pages/search.js",revision:"382bb4d77105e5bd3cc0ff2869f7603b"},{url:"/_next/static/OtlFaJqhl67OWA04z7apg/pages/search/search-filter.js",revision:"33850ac67ba801d716f7cdfb4b34bd65"},{url:"/_next/static/OtlFaJqhl67OWA04z7apg/pages/usage.js",revision:"2fd1b15cdf3273c337ff02a80f6e87a3"},{url:"/_next/static/OtlFaJqhl67OWA04z7apg/pages/user-details.js",revision:"1f98cdda63e2b71399bf98e7276e1a1c"},{url:"/_next/static/OtlFaJqhl67OWA04z7apg/pages/user.js",revision:"c5da76cfd0d067cc912af11871b7f444"},{url:"/_next/static/OtlFaJqhl67OWA04z7apg/pages/user/facebook-login.js",revision:"41021cd016e3c9dbf6431f43b9b78b10"},{url:"/_next/static/OtlFaJqhl67OWA04z7apg/pages/user/google-login.js",revision:"17910f0fbae31cf91c9164e894181b32"},{url:"/_next/static/OtlFaJqhl67OWA04z7apg/pages/user/login.js",revision:"542fc4c4f1d11943e17ea30c4b12b005"},{url:"/_next/static/chunks/072ad9047c83f4347e8b73b21c32f6fa1d828990.c48f519101bda5daf1f9.js",revision:"a5c405339503ddb4741b60e10feed5a4"},{url:"/_next/static/chunks/0c82041cf5f8633540971772348dd3b143edc573.5d89e967ba60efaf93fb.js",revision:"7bebedcf039fe4afd638e93bc9e02d94"},{url:"/_next/static/chunks/0cbe231f47f10c04af12575985eeb9085b0c500d.dafb0dfc653ca98d828f.js",revision:"92066fb58e68e5c8dfcab52e89d3f0e5"},{url:"/_next/static/chunks/1a0f24eb.db3546348c531a1c7080.js",revision:"e604cd8ba128c3ee7cc334c6099e2dd4"},{url:"/_next/static/chunks/29107295.95a6e19065c8ececd515.js",revision:"ba4615fc2e9778cccb5c36198e94a3b7"},{url:"/_next/static/chunks/2c796e83.cc8fb9016fed687e6618.js",revision:"3ab2994a0644064ec8ab9f7dc5cb059c"},{url:"/_next/static/chunks/3459cdb83fdbb407e5e64013ab98f26929f5c6dc.592e2977da40652721ea.js",revision:"f3c7b3b217d40b79c5413083f03af4a1"},{url:"/_next/static/chunks/3d68c34bef9ef44fe03206e69ac9c32684bac1ab.422e1f9203b0009c05e1.js",revision:"6fa75ad8fd1bdac7f6a051daad5daf25"},{url:"/_next/static/chunks/4072747d.a86dba08a37c229a1c90.js",revision:"94dca179c3ac4a448f42904bd4718333"},{url:"/_next/static/chunks/438941e12c918562b85f0c09820000f825125fba.f37777566327fe6b8cf4.js",revision:"e09650affc50523dadf5d9353b77627c"},{url:"/_next/static/chunks/5365777345c2af9cbfa1fccaff576461ed0906ed.7ca3108bd7c55e970bbd.js",revision:"18ac7f418e30b162014409d9d500d344"},{url:"/_next/static/chunks/a4a7b214dfa97e3fb71a196b63afed1b73466242.8c444c925b8854cd6da2.js",revision:"0ba109e882274a2ecc2eff2e3cb2ba1d"},{url:"/_next/static/chunks/a5f2aa56.47817d5e3095f96d4458.js",revision:"9abb0a37f603aeceb7a70677b4040954"},{url:"/_next/static/chunks/a6eee13366cf35762c790985e7a08f0bcfef3f66.1813cc39e401ca946ff1.js",revision:"d823020df48c17c02d9954fcaaeb7215"},{url:"/_next/static/chunks/a6fd4e0489ffa7e7758733953b28cd6b51bcfa40.276472379d280e1331c9.js",revision:"8eef08497f90d38aec8023365af63070"},{url:"/_next/static/chunks/af5c34a4dd58adf228bff0407af34473f6d57a47.22ba2ae7425241ed23a0.js",revision:"d615ae71928d4b083ea823b94a3a3f70"},{url:"/_next/static/chunks/b5385175646870954bb3d528406b719e03e6c42d.6700e237af862732731c.js",revision:"a53b274caf0f280dd34265f3cc20f43d"},{url:"/_next/static/chunks/ca3f59ae853b632206b971f5f183814f4eaf1e3c.b712eeb81750b59a7336.js",revision:"400c628695e2e38b81228953cd0e16da"},{url:"/_next/static/chunks/e309a2ee05121024bdba36fcb2c342f3e086d32e.fec51633d1c55b47657d.js",revision:"1da97711a7e63e914dbbaa49a1fba865"},{url:"/_next/static/chunks/e813d2d86889bb428a6c8086d894ba63af5a013d.76fa0cca3bb77b41d480.js",revision:"651a0191661f80118138bf2ea6df5c28"},{url:"/_next/static/chunks/fa601543bada5b5d8cc5508691fec5620d163aae.1389828c5f64514242f7.js",revision:"a0d11060734266a67bdedcb386436bf8"},{url:"/_next/static/chunks/fd4003b8fef041d3a1008da27c2198e124173ef9.1321134bce09954660e4.js",revision:"f6331783bbe708682f807a201a5d7662"},{url:"/_next/static/chunks/fed9f225.d49e8be76a4ed25fe5ab.js",revision:"9b5cc00b3813d78da4ed347a83cbc8bd"},{url:"/_next/static/chunks/framework.5ab3ddd5dfcaa70047b4.js",revision:"d0f99cb92af730955c28451eb9748173"},{url:"/_next/static/runtime/main-73feae209c1d9993b9b9.js",revision:"dd27078559d154f6de814c05f5ca2b1e"},{url:"/_next/static/runtime/polyfills-407b1c4ccb8e78b215a1.js",revision:"7d3798b6b0d820f05cf124a93abe884a"},{url:"/_next/static/runtime/webpack-b65cab0b00afd201cbda.js",revision:"f5e6e2fca3144cc944812cfa3547f475"},{url:"/actions/add-fb-pixel-to-call-user.js",revision:"64f54956f8b5a92324cf4ebdde1f8e38"},{url:"/actions/add-fb-pixel-to-call-user.min.js",revision:"7f004e055d9f4ad010f9b600479876b7"},{url:"/actions/check-mobile-os.js",revision:"31d99627da358e95538a75491417e829"},{url:"/actions/check-mobile-os.min.js",revision:"31d99627da358e95538a75491417e829"},{url:"/actions/home-page-search-text.js",revision:"fb18a650bf8ccc7d745cfe4dff2d9337"},{url:"/actions/home-page-search-text.min.js",revision:"03882f896ec325125b25a806bfb696b1"},{url:"/actions/list-page-back-btn-action.js",revision:"6cb9903fc081ef8f8f9b2f93052d3aa7"},{url:"/actions/list-page-back-btn-action.min.js",revision:"d47b4884e19fc3479c57493a196e1c6a"},{url:"/actions/user-favorite-ignore-actions.js",revision:"27f48b194742f15073f7ee9ab22b0fbc"},{url:"/actions/user-favorite-ignore-actions.min.js",revision:"5745ace8ebe49bc3cd6775c957900803"},{url:"/css/react-input-range.min.css",revision:"2cfb830d25e0255c099eee6db90cd7a9"},{url:"/css/style.css",revision:"326c7cbf586ebfd8d48103d6836d916e"},{url:"/css/style.min.css",revision:"9d67cb2a7c1deb72de76a880f65c676f"},{url:"/images/banner-min.jpg",revision:"6dcbed195522360a51dddefb91ab4bc9"},{url:"/images/banner.jpg",revision:"b46224338f219506d43cdac888aed10e"},{url:"/images/clapping.png",revision:"5c1fbb359a9c96734a1ca6917ff4c1fc"},{url:"/images/favicon.png",revision:"3dadcf2705621e6c847da9dcfc5fb3a6"},{url:"/images/icons/android-icon-192x192.png",revision:"a8fd3aefe08fd9c6d980988f536fed94"},{url:"/images/icons/apple-icon-114x114.png",revision:"6a0324060e9089ffdf3c4a9dbb9ab173"},{url:"/images/icons/apple-icon-120x120.png",revision:"37688c456cdfddaeb844656305aaa3c3"},{url:"/images/icons/apple-icon-144x144.png",revision:"5381d3ea60da9256d9d0d967664a7238"},{url:"/images/icons/apple-icon-152x152.png",revision:"2b5ca9f4fef3dfa65828587a5cc47804"},{url:"/images/icons/apple-icon-180x180.png",revision:"8602fde41d9318eba0296e8de39c0234"},{url:"/images/icons/apple-icon-57x57.png",revision:"1369136f3346e85f78fcfa98a12270ac"},{url:"/images/icons/apple-icon-60x60.png",revision:"29ddcb626aa1514be977c32655fbdb9a"},{url:"/images/icons/apple-icon-72x72.png",revision:"41db88d290181f7ef9481e8e729013e5"},{url:"/images/icons/apple-icon-76x76.png",revision:"81a4e055433dfef9b343e5e93ebadc5b"},{url:"/images/icons/icon-128x128.png",revision:"ad8fa56705d59fc362385422d6b6d878"},{url:"/images/icons/icon-144x144.png",revision:"02e8da51c6ac04d7a940aa2f9a4b828c"},{url:"/images/icons/icon-152x152.png",revision:"4249d121e340fd3bedce260fc95d0461"},{url:"/images/icons/icon-192x192.png",revision:"0282721e0ac0780f42e5c35759655195"},{url:"/images/icons/icon-384x384.png",revision:"4aea824a85c253420787a30f2457cebd"},{url:"/images/icons/icon-512x512.png",revision:"1dc9f19f6961a42df3de763a7a2f0593"},{url:"/images/icons/icon-72x72.png",revision:"595a83aa7b85c6324899ae6b30113433"},{url:"/images/icons/icon-96x96.png",revision:"abee648653483ae518df10ceabc1c82b"},{url:"/images/logo.png",revision:"222b781eb1a3b217641bde9f542da92a"},{url:"/images/map-satellite.png",revision:"8fa7cc90807d6a665989e1aae34d50ed"},{url:"/images/map-streets.png",revision:"c79157cb8cda56e41677466ec9f09c21"},{url:"/images/map.jpg",revision:"dd3de0dc0066b7bd9c02d368d51de9ab"},{url:"/images/mobile-map.png",revision:"e30ed856f48d8a73362c1032e5dcd362"},{url:"/images/no-photo-min.jpeg",revision:"fee1af158a7af18264c17e57f5dd1eaf"},{url:"/images/user-icon.png",revision:"a02f382b56944b4d2f4dbafd0959d0af"},{url:"/images/user.svg",revision:"95b68ddeaeb0caa5beec3fd6cbcd2112"},{url:"/manifest.json",revision:"4e007b698e84a1a99b76b6666d7165cc"},{url:"/robots.txt",revision:"5220e934bd9d7753fabd0ceeb118a8be"}],{ignoreURLParametersMatching:[]}),e.cleanupOutdatedCaches(),e.registerRoute(/^https:\/\/fonts\.(?:googleapis|gstatic)\.com\/.*/i,new e.CacheFirst({cacheName:"google-fonts",plugins:[new e.ExpirationPlugin({maxEntries:4,maxAgeSeconds:31536e3,purgeOnQuotaError:!0})]}),"GET"),e.registerRoute(/^https:\/\/use\.fontawesome\.com\/releases\/.*/i,new e.CacheFirst({cacheName:"font-awesome",plugins:[new e.ExpirationPlugin({maxEntries:1,maxAgeSeconds:31536e3,purgeOnQuotaError:!0})]}),"GET"),e.registerRoute(/\.(?:eot|otf|ttc|ttf|woff|woff2|font.css)$/i,new e.StaleWhileRevalidate({cacheName:"static-font-assets",plugins:[new e.ExpirationPlugin({maxEntries:4,maxAgeSeconds:604800,purgeOnQuotaError:!0})]}),"GET"),e.registerRoute(/\.(?:jpg|jpeg|gif|png|svg|ico|webp)$/i,new e.StaleWhileRevalidate({cacheName:"static-image-assets",plugins:[new e.ExpirationPlugin({maxEntries:64,maxAgeSeconds:86400,purgeOnQuotaError:!0})]}),"GET"),e.registerRoute(/\.(?:js)$/i,new e.StaleWhileRevalidate({cacheName:"static-js-assets",plugins:[new e.ExpirationPlugin({maxEntries:16,maxAgeSeconds:86400,purgeOnQuotaError:!0})]}),"GET"),e.registerRoute(/\.(?:css|less)$/i,new e.StaleWhileRevalidate({cacheName:"static-style-assets",plugins:[new e.ExpirationPlugin({maxEntries:16,maxAgeSeconds:86400,purgeOnQuotaError:!0})]}),"GET"),e.registerRoute(/\.(?:json|xml|csv)$/i,new e.StaleWhileRevalidate({cacheName:"static-data-assets",plugins:[new e.ExpirationPlugin({maxEntries:16,maxAgeSeconds:86400,purgeOnQuotaError:!0})]}),"GET"),e.registerRoute(/\/api\/.*$/i,new e.NetworkFirst({cacheName:"apis",networkTimeoutSeconds:10,plugins:[new e.ExpirationPlugin({maxEntries:16,maxAgeSeconds:86400,purgeOnQuotaError:!0})]}),"GET"),e.registerRoute(/\/api\/.*$/i,new e.NetworkFirst({cacheName:"apis",networkTimeoutSeconds:10,plugins:[new e.ExpirationPlugin({maxEntries:16,maxAgeSeconds:86400,purgeOnQuotaError:!0})]}),"POST"),e.registerRoute(/.*/i,new e.StaleWhileRevalidate({cacheName:"others",plugins:[new e.ExpirationPlugin({maxEntries:16,maxAgeSeconds:86400,purgeOnQuotaError:!0})]}),"GET")}));

@omar-dulaimi If you look at the following line, you will see the problem:

// ...
define("./service-worker.js",["./workbox-4d0bff02"],(function(e){"use strict";importScripts("worker-OtlFaJqhl67OWA04z7apg.js")
// ...

I think you have a worker/index.js file in your project folder, right? If you have this file, next-pwa automatically generate a custom service worker for you, which is worker-OtlFaJqhl67OWA04z7apg.js in your case. In your server.js setup, you served service-worker.js and workbox-4d0bff02.js, but not the custom worker file worker-OtlFaJqhl67OWA04z7apg.js.
So you may need to modify the server.js file like:

        server.get('/service-worker.js', (request, response) => {
            const filePath = join(__dirname, 'public/service-worker.js');
            return app.serveStatic(request, response, filePath);
        });

        server.get(/worker-[a-zA-Z0-9]+.js$/, async (request, response) => {
            const basePath = resolve(__dirname, 'public');
            const result = await FindFiles(basePath, /worker-[a-zA-Z0-9]+.js$/);

            const [fileMatchResult] = result;
            const { dir, file } = fileMatchResult;
            const fileWithPathToServe = join(dir, file);

            return app.serveStatic(request, response, fileWithPathToServe);
        });

        server.get(/workbox-[a-zA-Z0-9]+.js$/, async (request, response) => {
            const basePath = resolve(__dirname, 'public');
            const result = await FindFiles(basePath, /workbox-[a-zA-Z0-9]+.js$/);

            const [fileMatchResult] = result;
            const { dir, file } = fileMatchResult;
            const fileWithPathToServe = join(dir, file);

            return app.serveStatic(request, response, fileWithPathToServe);
        });

I found above snippet could cause error when using FindFiles. Because, for example, when requesting workbox-345.js then FindFiles may return workbox-123.js. If the public folder contains both workbox-123.js and workbox-345.js, by the search of the files, workbox-123.js will be returned. Which is not what we want. So I suggest you to change to something like this:

const { parse } = require('url')
//
        server.get('/service-worker.js', (request, response) => {
            const filePath = join(__dirname, 'public/service-worker.js');
            return app.serveStatic(request, response, filePath);
        });

        server.get(/worker-[a-zA-Z0-9]+.js$/, async (request, response) => {
            const { pathname } = parse(request.url, true);
            return app.serveStatic(request, response, join(__dirname, 'public', pathname ));
        });

        server.get(/workbox-[a-zA-Z0-9]+.js$/, async (request, response) => {
            const { pathname } = parse(request.url, true);
            return app.serveStatic(request, response, join(__dirname, 'public', pathname ));
        });

OR, if you don't need custom worker, simply remove worker/index.js and rebuild.

Hello @shadowwalker, I really appreciate your time and help so far. What you found makes perfect sense, I did what you mentioned and honestly it still didn't fix the issue. I did a lighthouse check and one of the failing items was:
Does not register a service worker that controls page and start_url

So perhaps it's an issue of multiple sub issues.

I tried building the project with/without the custom service worker, but the result was the same; not working.

I found this error reported to Sentry while running the project locally:

Failed to register a ServiceWorker for scope ('http://localhost:3000/') with script ('http://localhost:3000/sw.js'): A bad HTTP response code (404) was received when fetching the script.

I think I might be getting something right now. On any AMP page, it doesn't work, but as soon as I browse to a regular page, it works!

That's weird, the scope in /public/manifest.json is set to "/".

When I turnoff the internet, the non AMP page keeps working, but the AMP page does not.

Could you use the default sw.js filename instead of service-worker.js?

Hey @shadowwalker, I just tried that, and it still only works on Non AMP pages.

Should we give up?
I have high hopes for this library.

@omar-dulaimi I understand it's frustrating to debug those issue. I would assume AMP won't work well with PWA, (will have to experiment this a bit later). Both technologies are trying to speed up page loads, but they are running in totally different direction. AMP pages will be cached on Google Server and will be served by Google to Visitors once cached. I assume for security reasons, those pages won't work well with service worker.
My suggestions here

  1. As long as non AMP pages work with PWA, moving on from this issue for now.
  2. For AMP pages, try to limit the page to only have contents, not put complex javascript on the page.

Is this issue still opening?

I'm having this issue too

I solved this issue and now it's working.

@Tom-Chang-star would you mind to share some details about how to fix the issue/

Yeah, sure. When I first raised this issue, I read this discussion for 1 hour and was very disappointed.
And I found that your next-pwa/examples can be a great help to me.
They are perfect. First, I appreciate your article.
If I hadn't found your examples, it would have taken several more days.
I used a custom service-worker and injected it into the /public/sw.js.
Maybe you already know all of these.
I used your next-pwa/examples/offline-fallback, fallback.js, next.config.js and service-worker.js.
I just added my server.js to this configuration.
`
server.get('*', (req, res) => {
const parsedUrl = url.parse(req.url, true);
const { pathname } = parsedUrl;

if (pathname === '/sw.js' || pathname.startsWith('/workbox-')) {
  const filePath = path.join(__dirname, '..', 'public', pathname);
  app.serveStatic(req, res, filePath);
} else {
  handler(req, res, req.url);
}

});

startServer();
`
I hope you add more useful articles on Github.
Thank you again and I'd like to keep in touch with you.

@shadowwalker
Hello,
I just wanted to let you and any future developers who come across this issue know how I solved it. Since our website is half amp, half SSR, what I've done was creating two separate service workers, one for amp controlling amp pages only and one for the other pages.

AMP docs on how to install/register amp service worker:
https://amp.dev/documentation/examples/components/amp-install-serviceworker/
working example provided by Nextjs:
https://github.com/vercel/next.js/tree/canary/examples/amp-first

and this is the server endpoints as suggested by you guys:

 server.get('/service-worker.js', (req, res) => {
            const filePath = join(__dirname, 'public/service-worker.js');
            return app.serveStatic(req, res, filePath);
        });

    server.get(/worker-[a-zA-Z0-9]+.js$/, async (req, res) => {
        const { pathname } = parse(req.url, true);
        return app.serveStatic(
            req,
            res,
            join(__dirname, 'public', pathname)
        );
    });

    server.get(/workbox-[a-zA-Z0-9]+.js$/, async (req, res) => {
        const { pathname } = parse(req.url, true);
        return app.serveStatic(
            req,
            res,
            join(__dirname, 'public', pathname)
        );
    });

and server endpoint for amp service worker:

    server.get('/serviceworker-amp.js', (req, res) => {
        const filePath = join(__dirname, 'public/serviceworker-amp.js');
        return app.serveStatic(req, res, filePath);
    });

next-pwa config:

const withPWA = require('next-pwa');

const config = {
    pwa: {
        disable: process.env.NODE_ENV === 'development',
        register: true,
        scope: '/search',
        sw: 'service-worker.js',
        dest: 'public',
        maximumFileSizeToCacheInBytes: 3145728,
    },
};

module.exports = process.env.NODE_ENV === 'development' ? {} : withPWA(config);

As you can see, for the non-amp service worker I had to set the scope to a single page only which is the search page.
Any ways I could make it handle all other pages that the amp service worker doesn't handle? (amp controlls / and /properties)

I think you can set the scope for non-AMP service worker to / and change rules in runtimeCaching to exclude the AMP routes. That way it will not do anything on AMP pages.

@shadowwalker Should I switch to injectManifest? or any docs/example you can point me to please.

Was this page helpful?
0 / 5 - 0 ratings

Related issues

flamedmg picture flamedmg  路  4Comments

jonahsnider picture jonahsnider  路  4Comments

maximousblk picture maximousblk  路  7Comments

paales picture paales  路  4Comments

panjiesw picture panjiesw  路  7Comments