We are trying to host a PWA nuxt and the service worker will register on localhost:3000 only using
npm start
Trying to access it from 192.168.0.2:3000 will cause the following :
Page is not served from a secure origin 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.
nuxt.config.js
export default {
// Global page headers: https://go.nuxtjs.dev/config-head
head: {
title: 'ONX',
htmlAttrs: {
lang: 'en'
},
meta: [
{ charset: 'utf-8' },
{ name: 'viewport', content: 'width=device-width, initial-scale=1' },
{ hid: 'description', name: 'description', content: '' },
{ name: 'format-detection', content: 'telephone=no' }
],
link: [
{ rel: 'icon', type: 'image/x-icon', href: '/favicon.ico' }
]
},
// Global CSS: https://go.nuxtjs.dev/config-css
css: [],
// Plugins to run before rendering page: https://go.nuxtjs.dev/config-plugins
plugins: [
//{src: '~/plugins/VueCtkDateTimePicker.js', ssr: false }, // datepicker plugin here
{ src: '~/plugins/DateTimePicker.js' },
{ src: '~/plugins/VueTabulator.js', mode: 'client', ssr: false }
// { src: '~/plugins/Filters.js' }
],
// Auto import components: https://go.nuxtjs.dev/config-components
components: true,
// Modules for dev and build (recommended): https://go.nuxtjs.dev/config-modules
buildModules: [
'@nuxtjs/device',
'@nuxtjs/pwa',
],
// Modules: https://go.nuxtjs.dev/config-modules
modules: [
// https://go.nuxtjs.dev/bootstrap
'bootstrap-vue/nuxt',
'@nuxtjs/axios',
'@nuxtjs/proxy',
'@nuxt/content',
],
proxy: {
'/api': {
target: 'http://target:81',
pathRewrite: {
'^/api': '/',
},
},
changeOrigin: true,
},
bootstrapVue: {
// Install the `IconsPlugin` plugin (in addition to `BootstrapVue` plugin)
icons: true
},
// Build Configuration: https://go.nuxtjs.dev/config-build
build: {
extend(config, ctx) {
if (ctx.isDev) {
config.devtool = ctx.isClient ? 'source-map' : 'inline-source-map'
}
},
},
server: {
host: '0.0.0.0',
port: 8000, // default: 3000
},
pwa: {
manifest: {
"short_name": "my_project",
"name": "my_project",
"background_color": "white",
"display": "standalone",
"scope": "/",
"theme_color": "white"
},
}
}
sw.js
importScripts(...[options.workboxURL, ...options.importScripts])
initWorkbox(workbox, options)
workboxExtensions(workbox, options)
precacheAssets(workbox, options)
cachingExtensions(workbox, options)
runtimeCaching(workbox, options)
offlinePage(workbox, options)
routingExtensions(workbox, options)
function getProp(obj, prop) {
return prop.split('.').reduce((p, c) => p[c], obj)
}
function initWorkbox(workbox, options) {
if (options.config) {
// Set workbox config
workbox.setConfig(options.config)
}
if (options.cacheNames) {
// Set workbox cache names
workbox.core.setCacheNameDetails(options.cacheNames)
}
if (options.clientsClaim) {
// Start controlling any existing clients as soon as it activates
workbox.core.clientsClaim()
}
if (options.skipWaiting) {
workbox.core.skipWaiting()
}
if (options.cleanupOutdatedCaches) {
workbox.precaching.cleanupOutdatedCaches()
}
if (options.offlineAnalytics) {
// Enable offline Google Analytics tracking
workbox.googleAnalytics.initialize()
}
}
function precacheAssets(workbox, options) {
if (options.preCaching.length) {
workbox.precaching.precacheAndRoute(options.preCaching, options.cacheOptions)
}
}
function runtimeCaching(workbox, options) {
const requestInterceptor = {
requestWillFetch({ request }) {
if (request.cache === 'only-if-cached' && request.mode === 'no-cors') {
return new Request(request.url, { ...request, cache: 'default', mode: 'no-cors' })
}
return request
},
fetchDidFail(ctx) {
ctx.error.message =
'[workbox] Network request for ' + ctx.request.url + ' threw an error: ' + ctx.error.message
console.error(ctx.error, 'Details:', ctx)
},
handlerDidError(ctx) {
ctx.error.message =
`[workbox] Network handler threw an error: ` + ctx.error.message
console.error(ctx.error, 'Details:', ctx)
return null
}
}
for (const entry of options.runtimeCaching) {
const urlPattern = new RegExp(entry.urlPattern)
const method = entry.method || 'GET'
const plugins = (entry.strategyPlugins || [])
.map(p => new (getProp(workbox, p.use))(...p.config))
plugins.unshift(requestInterceptor)
const strategyOptions = { ...entry.strategyOptions, plugins }
const strategy = new workbox.strategies[entry.handler](strategyOptions)
workbox.routing.registerRoute(urlPattern, strategy, method)
}
}
function offlinePage(workbox, options) {
if (options.offlinePage) {
// Register router handler for offlinePage
workbox.routing.registerRoute(new RegExp(options.pagesURLPattern), ({ request, event }) => {
const strategy = new workbox.strategies[options.offlineStrategy]
return strategy
.handle({ request, event })
.catch(() => caches.match(options.offlinePage))
})
}
}
function workboxExtensions(workbox, options) {
}
function cachingExtensions(workbox, options) {
}
function routingExtensions(workbox, options) {
}
manifest.json - visible on chrome debugger
{
"name": "my_project",
"short_name": "my_project",
"icons": [
{
"src": "/_nuxt/icons/icon_64x64.64bc82.png",
"sizes": "64x64",
"type": "image/png",
"purpose": "any maskable"
},
{
"src": "/_nuxt/icons/icon_120x120.64bc82.png",
"sizes": "120x120",
"type": "image/png",
"purpose": "any maskable"
},
{
"src": "/_nuxt/icons/icon_144x144.64bc82.png",
"sizes": "144x144",
"type": "image/png",
"purpose": "any maskable"
},
{
"src": "/_nuxt/icons/icon_152x152.64bc82.png",
"sizes": "152x152",
"type": "image/png",
"purpose": "any maskable"
},
{
"src": "/_nuxt/icons/icon_192x192.64bc82.png",
"sizes": "192x192",
"type": "image/png",
"purpose": "any maskable"
},
{
"src": "/_nuxt/icons/icon_384x384.64bc82.png",
"sizes": "384x384",
"type": "image/png",
"purpose": "any maskable"
},
{
"src": "/_nuxt/icons/icon_512x512.64bc82.png",
"sizes": "512x512",
"type": "image/png",
"purpose": "any maskable"
}
],
"start_url": "/?standalone=true",
"display": "standalone",
"background_color": "white",
"theme_color": "white",
"lang": "en",
"scope": "/"
}
For anyone who may face this issue, @Lawrence Cherone's comment was right; I needed a valid certificate. How to generate a local cert using MKCert.
Install and add MKcert to local root CA
mkcert -install
Create cert for local application (in the folder of the app run)
mkcert 192.168.0.2
Add the cert to nuxt.config.js
server: {
https: {
key: fs.readFileSync(path.resolve(__dirname, '192.168.0.2-key.pem')),
cert: fs.readFileSync(path.resolve(__dirname, '192.168.0.2.pem'))
},
}
Clean your browser cookies and cache
Access https://192.168.0.2
, et voilà: the PWA add can be installed
Bonus: For people trying to get it to work on a mobile device:
Convert your rootCA.pem
to rootCA.crt
. How to convert pem to crt:
openssl x509 -outform der -in rootCA.pem -out rootCA.crt
Import the crt
cert to your mobile device. How to import CRT on android mobile. (May vary depending on the device.)