DEV Community

Cover image for How I optimized loading time using service workers in frontend
Nikola Perišić
Nikola Perišić

Posted on

How I optimized loading time using service workers in frontend

Did you have a situation when website loading takes too much like this gif below?

🔍 Have you heard of service workers?

In this post, we'll dive into what service workers are 🤔, how they work ⚙️, and how I used them to improve the performance of my web application.


What is a Service Worker? 🤖

A service worker is a background script that runs separately from the web page, and can:

  • Cache assets 🗂️
  • Intercept network requests 🌐
  • Deliver offline functionality 🚫

Key Characteristics of Service Workers:

  1. Runs in the background: Service workers are not tied to the page's lifecycle, meaning they can continue running even if the page is closed
  2. Event-driven: Service workers listen to specific events, such as network requests (fetch event)
  3. No DOM access: Unlike other web workers, service workers don’t have access to the DOM directly. They operate in the background, managing resources and network traffic

Here's a simple example of Service Worker script:

if ('serviceWorker' in navigator) {
  window.addEventListener('load', function() {
    navigator.serviceWorker.register('/service-worker.js').then(function(registration) {
      console.log('Service Worker registered with scope:', registration.scope);
    }).catch(function(error) {
      console.error('Service Worker registration failed:', error);
    });
  });
}
Enter fullscreen mode Exit fullscreen mode

🔍 In this snippet, the service worker (service-worker.js) is registered when the page loads. If the registration is successful, it logs the service worker’s scope; otherwise, it logs an error.


My Challenge: Integrating a Unity WebGL Game into a web page

I was integrating a Unity WebGL game into a .NET Razor website. The game consisted of large ".wasm" and ".data" files alongside JavaScript files that were crucial for the game to load. However, the first time the page loaded, it took over 20 seconds! 🚨 This was primarily due to the size of the ".wasm" and ".data" files.


The Solution: Caching with Service Workers

I created a "ServiceWorker.js" file, which caches all the necessary game assets to reduce the loading time. Here’s what the file contains:

const cacheName = "$Project_name";
const contentToCache = [
    "Build/Game.loader.js",
    "Build/Game.framework.js",
    "Build/Game.data",
    "Build/Game.wasm",
    "TemplateData/game_style.css"
];

self.addEventListener('install', function (e) {
    console.log('[Service Worker] Install');

    e.waitUntil((async function () {
        try {
            const cache = await caches.open(cacheName);
            console.log('[Service Worker] Caching all: app shell and content');
            for (const resource of contentToCache) {
                try {
                    await cache.add(resource);
                } catch (error) {
                    console.error(`[Service Worker] Failed to cache: ${resource}`, error);
                }
            }
        } catch (error) {
            console.error('[Service Worker] Failed to open cache', error);
        }
    })());
});

self.addEventListener('fetch', function (e) {
     e.respondWith((async function () {
        try {
            const response = await caches.match(e.request);
            console.log(`[Service Worker] Fetching resource: ${e.request.url}`);
            if (response) return response;

            const fetchResponse = await fetch(e.request);
            const cache = await caches.open(cacheName);
            console.log(`[Service Worker] Caching new resource: ${e.request.url}`);
            await cache.put(e.request, fetchResponse.clone());
            return fetchResponse;
        } catch (error) {
            console.error(`[Service Worker] Error fetching resource: ${e.request.url}`, error);
            throw error;
        }
    })());
});
Enter fullscreen mode Exit fullscreen mode

What does this code do?

This service worker script performs the following actions:

  • Caches essential files needed for the game to run, including ".wasm", ".data", and JS files during the install phase.
  • Intercepts fetch requests to serve cached resources if available. If the resource isn’t in the cache, it fetches it from the network, caches it, and returns the response.

The Result: Significant Performance Boost

By caching these assets, the loading time was reduced from 20+ seconds to under 3 seconds 🚀. The next time users visit the page, the assets are already stored locally, drastically speeding up the experience.

⚠️ Be cautious! The Caching Problem

While this approach works wonders for improving performance, it introduced another issue: outdated cached files. Since we're actively developing the Unity game and releasing new versions, the cached files needed to be cleared every time a new version was deployed.

To solve this, I created a pipeline script that automatically unregisters the old service worker and clears the cache whenever we publish a new game build. This ensures that users always load the latest assets without being stuck with old, cached versions.


Conclusion

Implementing service workers in your web application can dramatically improve performance, especially when dealing with large assets like in my Unity WebGL game. However, it’s important to handle caching carefully to avoid serving outdated files.

Have you had similar experiences optimizing load times? Did you use service workers or other techniques? Share your thoughts and insights in the comments below! 💬

Hope you learned something new today! ✌️

Top comments (0)