74

I'm using create-react-app with an express server.

create-react-app has a pre-configured ServiceWorker that caches local assets (https://github.com/facebook/create-react-app/blob/master/packages/react-scripts/template/README.md#making-a-progressive-web-app).

The problem I encountered when I tried to publish on my server is that the service-worker.js file was available, but I was getting errors in my browser console when it tried to register it.

On Firefox, I got this error:

Error during service worker registration:

TypeError: ServiceWorker script at https://example.com/service-worker.js for scope https://example.com/ encountered an error during installation.

On Chrome, I get the more descriptive error:

Error during service worker registration: DOMException: Failed to register a ServiceWorker: The script has an unsupported MIME type ('text/html').

Sure enough, if I look in the Network tab of my developer tools and search for the service-worker.js file, I can see that it has the wrong MIME type in the HTTP headers.

I could not understand, though, why it has the wrong MIME type?

Stephen Ostermiller
  • 23,933
  • 14
  • 88
  • 109
MattSidor
  • 2,599
  • 4
  • 21
  • 32

20 Answers20

47

Applying this change:

'/service-worker.js'  // ❌
'./service-worker.js' // ✅

fixed my issue.

(in navigator.serviceWorker.register('/service-worker.js'))


Or maybe you need:

'%PUBLIC_URL%/serviceworker.js'

Thank @DanielLord for his comment.

Mir-Ismaili
  • 13,974
  • 8
  • 82
  • 100
34

In my Express server application, I have one wildcard (asterisk / *) route that redirects to index.html:

// Load React App
// Serve HTML file for production
if (env.name === "production") {
  app.get("*", function response(req, res) {
    res.sendFile(path.join(__dirname, "public", "index.html"));
  });
}

This is a very common design pattern. However, it means that any requests for unknown text files initially get redirected to index.html, and therefore return with the MIME type of "text/html", even if it's actually a JavaScript or SVG or some other kind of plaintext file.

The solution I found was to add a specific route for service-worker.js before the wildcard route:

app.get("/service-worker.js", (req, res) => {
  res.sendFile(path.resolve(__dirname, "public", "service-worker.js"));
});
app.get("*", function response(req, res) {
  res.sendFile(path.join(__dirname, "public", "index.html"));
});

Now, when the browser looks for service-worker.js, Express will return it with the correct MIME type.

(Note that if you try adding the service-worker.js route after the wildcard, it won't work because the wildcard route will override.)

MattSidor
  • 2,599
  • 4
  • 21
  • 32
  • Please what should I specify if my `index.html` file is in client/public folder and not just a public folder ? I try this `res.sendFile(path.resolve(__dirname, "client", "public", "service-worker.js"));` but it returns A bad HTTP response code (404) was received when fetching the script. – Nevada Mar 18 '21 at 00:58
25

I think that the service worker file is not present at http://localhost:3000/service-worker.js so the server is returning index.html instead. Then the registration function has no idea of what to do with a index.html file and tells you that the MIME-type is not correct.

A quick fix would be to copy the service-worker.js file to the public folder so that when you hit http://localhost:3000/service-worker.js you see the file in the browser.

Remember to go to ChromeDev > Applications > ServiceWorkers and hit Unsubscribe. in order to remove the errored one. Remember also disable cache

  • If you can't reach your /public/service-worker.js. That it somewhat always renders index.html, be sure to check your vhost configuration if you have one. My co-workers added rules that was making the SW.js file impossible to serve because of redirection rules. – BiasInput Jan 29 '21 at 15:51
9

ServiceWorker supported MIME type are 'text/javascript', application/javascript and application/x-javascript. go to your server file and set

    response.writeHead(201, {
        'Content-Type': 'application/javascript'
    });
WapShivam
  • 964
  • 12
  • 20
5

In case, you're working on NodeJS, serviceWorker file should place at public folder. If you're using Webpack to export serviceWorker to public, you need to use 'copy-webpack-plugin'.

Hoang Duong
  • 328
  • 4
  • 12
4

I use nginx to serve react app and I resolved this issue adding mime types to nginx:

http {
    include    mime.types; // <--- this line

    # another config details
}

Also you can find mime.types file here

zemil
  • 3,235
  • 2
  • 24
  • 33
3

When running webpack dev server, it renders default index.html for missing resource.

If you use your browser to load http://localhost:3000/sw.js, you will actually see a rendered react app (started by the index.html). That's why the mime type is html, not javascript.

This fallback to index.html is designed to support SPA with front-end routes. For example /product/123/reviews has no such html file in backend, the JS SPA app takes care of it in front-end.

Why your file is missing? I guess you created that file in the root folder of your project.

Webpack dev server doesn't serve from that root folder.

Move your sw.js into public/ folder of your project, it will be served up as expected.

Otabek Butcher
  • 545
  • 8
  • 19
2

React: public/index.html

<script> 
  if ('serviceWorker' in navigator) {
    window.addEventListener('sw-cached-site.js', function() {
      navigator.serviceWorker.register('service-worker.js', {
        scope: '/',
      });
    });
  } 
  window.process = { env: { NODE_ENV: 'production' } };
</script>

Note: Full app/site public/sw-cached-site.js

const cacheName = 'v1';
self.addEventListener('activate', (e) =>{
  e.waitUntil(
    caches.keys().then(cacheNames => {
      return Promise.all(
        cacheNames.map(cache => {
          if(cache !== cacheName) { 
            return caches.delete(cache);
          }
        })
      )
    })
  )
});
self.addEventListener('fetch', e => { 
  e.respondWith(
      fetch(e.request)
        .then(res => {
            const resClone = res.clone();
            caches
                .open(cacheName)
                .then(cache => {
                    cache.put(e.request, resClone);
                }); 
                return res;
        }).catch(err => caches.match(e.request).then(res => res))
  );
});
Oscar Corona
  • 567
  • 4
  • 5
2

Make sure that the service worker file is generated (Chrome DevTools -> Sources -> Filesystem). You may receive such an error if the service worker file is not generated at all (check your deployment script).

  • Thanks. This was my issue. Looks like the bootstrap / template has changed since the tutorial was written. In may case there was a serviceWorker.js file - but it was in `/src/` not `/public/`. – Morvael Mar 10 '20 at 10:36
2

In my case, for Vue I needed to generate the mockServiceWorker.js in the public directory using the command-line tool as described here:

https://mswjs.io/docs/getting-started/integrate/browser

This was not clear from the samples to me. Specifically the CLI command to run is:

npx msw init ./public
D2TheC
  • 2,203
  • 20
  • 23
1

I had the same issue with React the "Service Worker registration error: Unsupported MIME type ('text/html')" and realised it comes from the index.html directly. This file has a header mime type 'text/html' but the service-worker.js that is loaded via script tag inside has the mime type 'text/javascript'. So the only way was to avoid this error to place into the src folder and import the service-worker.js into the index.js where my App component container was.

lortschi
  • 2,768
  • 2
  • 18
  • 12
1

For all those who are using node.js or express.js you might have to add this line in your main server.js file (if your service worker file is in the root directory, if it is in some other directory add that path)

app.use(express.static(__dirname + "/"));

This is because the serviceworker.js is a static file and when you try to register it, express tries to find it in static directory that you defined, hence the above line.

Viraj Doshi
  • 771
  • 6
  • 26
0

The script has an unsupported MIME type ('text/html'). Failed to load resource: net::ERR_INSECURE_RESPONSE (index):1 Uncaught (in promise) DOMException: Failed to register a ServiceWorker: The script has an unsupported MIME type ('text/html').

Code for template.js root file

export default ({ markup, css }) => {
  return `<!doctype html>
      <html lang="en">
        <head>
          <meta charset="utf-8">
          <meta name="viewport" content="width=device-width, initial-scale=1.0">
          <title>MERN Marketplace</title>
          <link rel="stylesheet" href="https://fonts.googleapis.com/css?family=Roboto:100,300,400">
          <link rel="stylesheet" href="https://fonts.googleapis.com/icon?family=Material+Icons">

          <style>
              a{
                text-decoration: none
              }
          </style>
          <link rel="manifest" href="./manifest.json">
        </head>
        <body style="margin:0">
            <div id="root">${markup}</div>
          <style id="jss-server-side">${css}</style>
          <script type="text/javascript" src="/dist/bundle.js"></script>
          <script>
          if ('serviceWorker' in navigator) {
            navigator.serviceWorker.register('/sw.js').then(function() { 
              console.log("Service Worker Registered"); 
            });
          }
          </script>
        </body>
      </html>`;
};
0

I was facing the same issue and removing other firebase libraries from my config solve the issue for me Check this on Github


// Change this 
var firebaseConfig = {
    apiKey: "*********",
    authDomain: "**********",
    databaseURL: "*********",
    projectId: "***********",
    storageBucket: "************",
    messagingSenderId: "***********",
    appId: "************"
};

// To =>
var firebaseConfig = {
  apiKey: "*****************",
  projectId: "**********",
  messagingSenderId: "*******",
  appId: "***********"
};

Qais Khateeb
  • 48
  • 1
  • 4
0

I guess, service worker file should be on root path. In my case, I cannot use root path, so I redirect service-worker.js to my sub path using apache redirect rule.

 RewriteRule "^/service-worker.js$"  "/sub/service-worker.js"
9Dragons
  • 137
  • 1
  • 4
0

I had similar issue when hosting a site with python. None of the solutions here worked for me and I came across a bug posted in chrome dev forum which managed to resolve my issue. Apparently it was windows related issue in my case as the registry entry for .js files had content type set to text/plain due to some previous configuration which I had to change back to application/javascript.

TLDR:

  1. Open Registry Editor
  2. Go to Computer\HKEY_CLASSES_ROOT\.js
  3. Change Content Type to application/javascript
  4. Restart Chrome and run your site

Hope this helps some one in case none of the above works.

0

This error is because the application (Angular in this case) don't found the file service-worker.js (ngsw-config.js or every else name file).

I solved the problem by adding in angular.json:

"serviceWorker": true,
"ngswConfigPath": "service-worker.json",

This should be added to all configurations: production, testing and staging, for example.

Matias de Andrea
  • 194
  • 2
  • 11
0

create serviceWorker.js then paste this code.

const CACHE_NAME = 'offline';
const OFFLINE_URL = 'offline.html';


const isLocalhost = Boolean(
    window.location.hostname === 'localhost' ||
      // [::1] is the IPv6 localhost address.
      window.location.hostname === '[::1]' ||
      // 127.0.0.0/8 are considered localhost for IPv4.
      window.location.hostname.match(
        /^127(?:\.(?:25[0-5]|2[0-4][0-9]|[01]?[0-9][0-9]?)){3}$/
      )
  );
  
  export function register(config) {
    if (process.env.NODE_ENV === 'production' && 'serviceWorker' in navigator) {
      // The URL constructor is available in all browsers that support SW.
      const publicUrl = new URL(process.env.PUBLIC_URL, window.location.href);
      if (publicUrl.origin !== window.location.origin) {
        // Our service worker won't work if PUBLIC_URL is on a different origin
        // from what our page is served on. This might happen if a CDN is used to
        // serve assets; see https://github.com/facebook/create-react-app/issues/2374
        return;
      }
  
      window.addEventListener('load', () => {
        const swUrl = `${process.env.PUBLIC_URL}/service-worker.js`;
  
        if (isLocalhost) {
          // This is running on localhost. Let's check if a service worker still exists or not.
          checkValidServiceWorker(swUrl, config);
  
          // Add some additional logging to localhost, pointing developers to the
          // service worker/PWA documentation.
          navigator.serviceWorker.ready.then(() => {
            console.log(
              'This web app is being served cache-first by a service '
            );
          });
        } else {
          // Is not localhost. Just register service worker
          registerValidSW(swUrl, config);
        }
      });
    }
  }
  
  function registerValidSW(swUrl, config) {
    navigator.serviceWorker
      .register(swUrl)
      .then(registration => {
        registration.onupdatefound = () => {
          const installingWorker = registration.installing;
          if (installingWorker == null) {
            return;
          }
          installingWorker.onstatechange = () => {
            if (installingWorker.state === 'installed') {
              if (navigator.serviceWorker.controller) {
                // At this point, the updated precached content has been fetched,
                // but the previous service worker will still serve the older
                // content until all client tabs are closed.
                console.log(
                  'New content is available and will be used when all '
                );
  
                // Execute callback
                if (config && config.onUpdate) {
                  config.onUpdate(registration);
                }
              } else {
                // At this point, everything has been precached.
                // It's the perfect time to display a
                // "Content is cached for offline use." message.
                console.log('Content is cached for offline use.');
  
                // Execute callback
                if (config && config.onSuccess) {
                  config.onSuccess(registration);
                }
              }
            }
          };
        };
      })
      .catch(error => {
        console.error('Error during service worker registration:', error);
      });
  }
  
  function checkValidServiceWorker(swUrl, config) {
    // Check if the service worker can be found. If it can't reload the page.
    fetch(swUrl, {
      headers: { 'Service-Worker': 'script' },
    })
      .then(response => {
        // Ensure service worker exists, and that we really are getting a JS file.
        const contentType = response.headers.get('content-type');
        if (
          response.status === 404 ||
          (contentType != null && contentType.indexOf('javascript') === -1)
        ) {
          // No service worker found. Probably a different app. Reload the page.
          navigator.serviceWorker.ready.then(registration => {
            registration.unregister().then(() => {
              window.location.reload();
            });
          });
        } else {
          // Service worker found. Proceed as normal.
          registerValidSW(swUrl, config);
        }
      })
      .catch(() => {
        console.log(
          'No internet connection found. App is running in offline mode.'
        );

        const cache = caches.open(CACHE_NAME);
        const cachedResponse = cache.match(OFFLINE_URL);

        return cachedResponse;
      });
  }
  
  export function unregister() {
    if ('serviceWorker' in navigator) {
      navigator.serviceWorker.ready
        .then(registration => {
          registration.unregister();
        })
        .catch(error => {
          console.error(error.message);
        });
    }
  }

and go to your public folder then create offline.html after that import the serviceWorker.js to index.js

import * as serviceWorker from "./serviceWorker";

at the bottom of index.js paste the code below.

serviceWorker.register();

Thank you for reading my solution, I hope you fix the problem . Keep it up!

0

for reactjs this work for me

<script src="https://cdnjs.cloudflare.com/ajax/libs/react-dom/16.6.0/umd/react-dom.production.min.js">
  if("serviceWorker" in navigator){
        window.addEventListener("load",()=>{
          navigator.serviceWorker.register("%PUBLIC_URL%/serviceworker.js")
          .then((reg)=>{
            console.log("Success",reg.scope)
          }).catch((err)=>{
            console.log("Error",err)
          })
        })
      }
</script>
-1
self.addEventListener('fetch', function(event) {});

Add Above code on your service-worker.js file

  • 1
    Welcome to SO! Please note that you've just answered an old question. We usually answer this kind of question if there has been any change in a relevant software version which caused a deprecation of the accepted answer's syntax. If your answer doesn't add new information please consider deleting it. – My Koryto Sep 22 '20 at 10:55