Skip to content

Service Worker Install Event — Pre-Caching Resources for Offline

DodaTech Updated 2026-06-28 6 min read

In this tutorial, you will learn about Service Worker Install Event. We cover key concepts, practical examples, and best practices to help you master this topic.

The install event pre-caches critical resources when a service worker first registers, ensuring your PWA works offline immediately after the initial visit by storing key assets in the browser cache.

What You'll Learn

By the end of this tutorial, you will understand how the install event works, what resources to pre-cache, how to handle failed cache operations, and how to version your caches effectively.

Why It Matters

The install event is your only chance to cache resources before the user needs them. If the install fails to cache critical assets, your app cannot work offline. Getting the install right determines whether your PWA feels instant or broken when offline.

Real-World Use

A travel guide PWA pre-caches its app shell, destination images, and route maps during installation. Users who install the PWA before a trip can browse all content offline without downloading anything during their journey.

What Happens During Install

When a user visits your PWA for the first time, the service worker registers and fires the install event. During this event, you open a cache and add the resources your app needs to function offline.

Install Event Flow
    Browser registers sw.js
         ↓
    Install event fires
         ↓
    Open cache (caches.open())
         ↓
    Add resources (cache.addAll())
         ↓
    ╔══════════════╗
    ║  All succeed ║ → Installation succeeds → Service worker activates
    ╚══════════════╝
         ↓
    ╔══════════════╗
    ║  Any fails   ║ → Installation fails → Service worker discarded
    ╚══════════════╝

Think of the install event like packing a suitcase for a trip. You decide what to bring (pre-cache resources) before you leave (go offline). If you forget something critical (fail to cache), you cannot go back once you are on the plane (offline).

What to Pre-Cache

Not every resource should be pre-cached. Choose resources that are:

  • Critical for the app shell: HTML, CSS, JavaScript that renders the core UI
  • Small and stable: Logo icons, fonts, and small images
  • Used on every page: Global styles, shared scripts, navigation components
  • Needed offline: The offline fallback page

Do NOT pre-cache:

  • Large media files (videos, high-res images)
  • API responses (cache these dynamically)
  • Third-party scripts from different origins
  • Resources that change frequently
// Pre-caching the app shell
const CACHE_NAME = 'my-pwa-cache-v1';
const PRECACHE_LIST = [
    '/',
    '/index.html',
    '/styles/main.css',
    '/styles/theme.css',
    '/scripts/app.js',
    '/scripts/router.js',
    '/images/logo.svg',
    '/images/icon-192.png',
    '/offline.html',
    '/favicon.ico'
];

self.addEventListener('install', event => {
    console.log('Install: Starting pre-cache');

    event.waitUntil(
        caches.open(CACHE_NAME)
            .then(cache => {
                console.log('Install: Cache opened, adding resources');
                return cache.addAll(PRECACHE_LIST);
            })
            .then(() => {
                console.log('Install: All resources cached successfully');
                return self.skipWaiting();
            })
    );
});

Output:

Install: Starting pre-cache
Install: Cache opened, adding resources
Install: All resources cached successfully

Handling Cache Failures

If any resource in cache.addAll() fails to download, the entire installation fails. This is by design — it ensures your app never activates with a partial cache. Handle this by providing fallbacks:

// Install with fallback handling
self.addEventListener('install', event => {
    event.waitUntil(
        caches.open(CACHE_NAME)
            .then(cache => {
                const cachePromises = PRECACHE_LIST.map(url => {
                    return fetch(url)
                        .then(response => {
                            if (!response.ok) {
                                throw new Error(`Failed to fetch: ${url}`);
                            }
                            return cache.put(url, response);
                        })
                        .catch(error => {
                            console.warn(`Skipping ${url}: ${error.message}`);
                            // Return a cached offline page as fallback
                            return cache.add('/offline.html');
                        });
                });
                return Promise.all(cachePromises);
            })
            .then(() => self.skipWaiting())
    );
});

This approach attempts each resource individually. If one resource fails, the others still cache. The app works with a degraded but functional offline experience.

Versioning Caches

Always version your cache names. When you update your PWA, the new service worker creates a new cache with a different version. The old cache persists until the activate event cleans it.

// Cache versioning strategy
const CACHE_PREFIX = 'my-pwa';
const CACHE_VERSION = 2;
const CACHE_NAME = `${CACHE_PREFIX}-v${CACHE_VERSION}`;

// Update the version number when you change cached resources
// Old cache: my-pwa-v1
// New cache: my-pwa-v2

The version number lets you manage cache migrations. When you update resources, bump the version, list the new URLs, and delete the old cache during activation.

Testing the Install

Use Chrome DevToolsk "DevTools" >}} to verify your install works:

// DevTools testing script (run in console)
navigator.serviceWorker.register('/sw.js').then(reg => {
    if (reg.installing) {
        console.log('Service worker installing');
        reg.installing.addEventListener('statechange', () => {
            console.log('State:', reg.installing.state);
            if (reg.installing.state === 'installed') {
                caches.open('my-pwa-cache-v1').then(cache => {
                    cache.keys().then(keys => {
                        console.log('Cached URLs:', keys.length);
                        keys.forEach(request => {
                            console.log(' -', request.url);
                        });
                    });
                });
            }
        });
    }
});

Output:

Service worker installing
State: installing
State: installed
Cached URLs: 10
 - https://example.com/
 - https://example.com/index.html
 - https://example.com/styles/main.css
...

Progressive Pre-Caching

For larger applications, pre-cache only the minimum app shell and lazy-cache the rest. This keeps the install fast while ensuring offline functionality:

// Progressive caching: core now, rest later
const CORE_ASSETS = [
    '/',
    '/index.html',
    '/styles/core.css',
    '/scripts/main.js'
];

const DEFERRED_ASSETS = [
    '/styles/themes/dark.css',
    '/images/hero.jpg',
    '/scripts/analytics.js'
];

self.addEventListener('install', event => {
    event.waitUntil(
        caches.open(CACHE_NAME)
            .then(cache => {
                console.log('Caching core assets');
                return cache.addAll(CORE_ASSETS);
            })
            .then(() => {
                console.log('Core assets cached, deferring remaining');
                return self.skipWaiting();
            })
    );
});

// Cache deferred assets during fetch events
self.addEventListener('fetch', event => {
    const url = new URL(event.request.url);

    if (DEFERRED_ASSETS.includes(url.pathname)) {
        event.respondWith(
            caches.open(CACHE_NAME).then(cache => {
                return cache.match(event.request).then(response => {
                    const fetchPromise = fetch(event.request).then(networkResponse => {
                        cache.put(event.request, networkResponse.clone());
                        return networkResponse;
                    });
                    return response || fetchPromise;
                });
            })
        );
    }
});

Common Mistakes

  1. Pre-caching every page of the app. Users do not visit every page. Cache the app shell and let fetch events cache pages on demand.
  2. Using large URLs in pre-cache. cache.addAll() downloads every URL sequentially. Large files (videos, high-res images) slow the install and risk failure.
  3. Not handling install failures. When cache.addAll() fails, the service worker is discarded. Users get no offline support. Include error handling.
  4. Pre-caching dynamic content. API responses, user profiles, and real-time data change constantly. Cache these dynamically with fetch strategies, not during install.
  5. Mixing HTTP and HTTPS URLs. Service workers require HTTPS. Mixed content errors cause cache operations to fail silently.

Practice Questions

  1. What resources should be pre-cached during the install event?
  2. Why does cache.addAll() fail the entire installation if one resource fails?
  3. How do you version caches and why is versioning important?
  4. What is the difference between pre-caching and dynamic caching?
  5. How would you handle a failed resource during pre-caching without failing the entire install?

Challenge: Create a service worker that pre-caches an app shell of 8 resources. Make one resource deliberately fail (use a 404 URL) and handle the error so the install still succeeds with a partial cache.

FAQ

Can I pre-cache resources from other domains?

No, cache.addAll() only works for same-origin URLs. For cross-origin resources, fetch them during the install event and cache them manually using cache.put().

How much data can I pre-cache?

Chrome allows approximately 50MB per origin, but the browser may prompt users for permission beyond 50MB. Firefox and Safari have smaller limits. Keep pre-cache under 10MB.

Does pre-caching affect page load speed?

Pre-caching happens after the page loads (in the background). It does not block rendering. The impact on perceived performance is minimal because it runs in a separate thread.

Can I update pre-cached resources without changing the service worker?

No. Pre-cached resources are tied to the service worker version. To update them, change the service worker (even one byte), which triggers the update lifecycle.

What happens if the user has no internet during install?

The install event fails because cache.addAll() fetches resources from the network. The service worker will retry on the next page visit that triggers an update check.

Mini Project

Create a service worker that pre-caches 10 resources for a simple app (HTML, CSS, JS, images, fonts, and an offline page). Version the cache with a prefix and number. Implement a function that logs all cached URLs to the console after install. Test by registering the worker and checking Chrome DevTools → Cache Storage.

What's Next

The install event is complete. Now learn how the activate event cleans up old caches and prepares your PWA for the new version.

Built by the developers of DodaTech

Doda Browser, DodaZIP & Durga Antivirus Pro