Build an Offline-Ready Resource Index with a Service Worker

A static resource index can remain usable during an unreliable connection if it caches a small application shell, keeps the most recent resource data, and shows a clear offline state. A service worker provides these capabilities without turning the site into a complex application. The important part is to cache selectively: preserve the index and interface, but do not attempt to copy every external destination.
BEACON OF HOPE, oceans, illumination, clouds, lights, sea, beams, night time, HD wallpaper | Peakpx

Define the Offline Promise First

"Works offline" can mean several different things depending on the architecture. For a static resource index, a realistic promise focuses on preserving core navigation and saved context. The homepage and primary navigation remain available, allowing visitors to view the last successfully loaded resource list while local search and category filters continue to operate. External destinations are clearly labeled as requiring an active connection, and the interface explicitly explains when displayed information might be outdated.

Do not promise that external resources themselves will open offline. They belong to other sites and may require authentication, dynamic server requests, or real-time data. The offline feature should preserve orientation and saved context rather than attempting to imitate remote services.

Use a Small File Structure

A heavy framework is entirely optional for an offline index. A minimal, static setup provides everything necessary:

Plaintext
public/
├─ index.html
├─ offline.html
├─ styles.css
├─ app.js
├─ service-worker.js
├─ resources.json
└─ icons/
 └─ directory-icon.png
The application shell consists of the HTML, CSS, JavaScript, and essential local image assets, while resources.json contains the directory data. The service worker decides when to fetch fresh content from the network and when to return a cached response. To test the interface against a realistic collection, inspect a public index containing several groups and destinations. For instance, a reference such as 주소구조대 can help you check whether a cached layout remains understandable when categories vary in length, serving as a structural baseline while you write original labels for your project.

Step 1: Register the Service Worker

Register the service worker only after confirming browser support. Registration failures must never prevent the standard online version of the page from functioning properly.

JavaScript
if ("serviceWorker" in navigator) {
 window.addEventListener("load", async () => {
 try {
 const registration = await navigator.serviceWorker.register(
 "service-worker.js",
 { scope: "./" }
 );
 console.info(
 "Offline support ready",
 registration.scope
 );
 } catch (error) {
 console.warn("Offline support unavailable", error);
 }
 });
}
The main application must operate independently of registration status. Visitors browsing in private modes, restrictive corporate environments, or unsupported browsers should continue to receive a standard online experience without errors.

Step 2: Version the Caches

Use explicit cache names and update the version string whenever an essential application shell file changes in a way that requires replacing the active copy.

JavaScript
const SHELL_CACHE = "resource-shell-v3";
const DATA_CACHE = "resource-data-v1";
const SHELL_FILES = [
 "./",
 "index.html",
 "offline.html",
 "styles.css",
 "app.js",
 "icons/directory-icon.png"
];
Separating the shell cache from the data cache enables different update strategies. Interface assets can be replaced as a versioned group, whereas resource data can be updated dynamically whenever a network request succeeds. Avoid adding unnecessary files to the precache list, as large install transactions increase the risk of failure and consume storage on the user's device.

Step 3: Precache the Essential Shell

During the service worker installation lifecycle, open the designated shell cache and populate it with the required local assets.

JavaScript
self.addEventListener("install", event => {
 event.waitUntil(
 caches
 .open(SHELL_CACHE)
 .then(cache => cache.addAll(SHELL_FILES))
 );
});
If any specified file fails to download, cache.addAll() rejects and the service worker installation fails entirely. This behavior is deliberate, as a broken application shell causes more user confusion than lacking offline capabilities altogether. Always verify every relative asset path during deployment, particularly when hosting the site inside a repository subdirectory. Avoid calling skipWaiting() automatically in early iterations, as replacing an active service worker mid-session can mix legacy page state with new cached assets. Presenting a controlled update banner gives visitors a safer way to reload.

Step 4: Remove Obsolete Caches

When a new service worker activates, clear out obsolete caches associated with older application releases.

JavaScript
self.addEventListener("activate", event => {
 const allowed = new Set([SHELL_CACHE, DATA_CACHE]);
 event.waitUntil(
 caches.keys().then(names =>
 Promise.all(
 names
 .filter(name =>
 name.startsWith("resource-") &&
 !allowed.has(name)
 )
 .map(name => caches.delete(name))
 )
 )
 );
});
Verifying cache prefixes prevents accidental deletion of unrelated site data, as shared web origins may host multiple applications. Only delete cache keys owned by your specific project scope.

Step 5: Use Network-First for Resource Data

Directory data requires fresh network content whenever available. If the network request fails due to connectivity issues, fall back to the most recently cached copy.

JavaScript
async function resourceData(request) {
 const cache = await caches.open(DATA_CACHE);
 try {
 const response = await fetch(request);
 if (response.ok) {
 await cache.put(request, response.clone());
 }
 return response;
 } catch {
 const cached = await cache.match(request);
 if (cached) return cached;
 throw new Error("No resource data available");
 }
}
Include a generatedAt or reviewedAt timestamp inside resources.json. When displaying cached data, render this date prominently near the offline status indicator. HTTP header timestamps alone do not clarify when the underlying content was last audited by an editor.

Step 6: Use Cache-First for the App Shell

Versioned application shell files should be served directly from cache to maximize performance. If a file is missing from cache, the worker falls back to the network.

JavaScript
async function appShell(request) {
 const cached = await caches.match(request);
 if (cached) return cached;
 return fetch(request);
}
This strategy accelerates repeat visits and guarantees UI availability offline. The explicit cache version string ensures future deployments cleanly update the shell.

Step 7: Route Only Same-Origin Requests

Avoid intercepting every outbound network request. External destinations should pass through the browser's standard network handler without service worker interference.

JavaScript
self.addEventListener("fetch", event => {
 const request = event.request;
 const url = new URL(request.url);
 if (
 request.method !== "GET" ||
 url.origin !== self.location.origin
 ) {
 return;
 }
 if (url.pathname.endsWith("resources.json")) {
 event.respondWith(resourceData(request));
 return;
 }
 if (request.mode === "navigate") {
 event.respondWith(navigationResponse(request));
 return;
 }
 event.respondWith(appShell(request));
});
Ensure pathname matching logic accounts for host deployment subdirectories. Test both staging and production environments to confirm relative path handling.

Step 8: Provide a Navigation Fallback

When handling page navigation, attempt a network fetch first, fall back to a cached page, and default to a dedicated offline document if the network fails.

JavaScript
async function navigationResponse(request) {
 try {
 return await fetch(request);
 } catch {
 return (
 await caches.match(request) ||
 await caches.match("offline.html")
 );
 }
}
Keep the offline fallback document simple and direct. Inform the user that connectivity is unavailable, provide a link returning to the cached homepage, and omit interactive controls that require an active connection.

Step 9: Show Connection State Without Overstating It

Browsers trigger online and offline events, but these indicators reflect local network interface status rather than true end-to-end reachability.

JavaScript
function updateConnectionNotice() {
 const notice = document.querySelector("#connection-notice");
 notice.hidden = navigator.onLine;
}
window.addEventListener("online", updateConnectionNotice);
window.addEventListener("offline", updateConnectionNotice);
updateConnectionNotice();
Phrasing should accurately describe current capabilities, such as stating that the user appears offline and the page is rendering saved directory data. Avoid declaring that every listed external resource remains reachable or up to date.

Step 10: Handle Updates Clearly

When a newly installed service worker is waiting in the background, display a modest update banner featuring a refresh button. Allow visitors to finish their current task before prompting a page reload. If a release modifies the underlying data schema, ensure the updated frontend code handles the previous cached data format gracefully for at least one release cycle.

Deploy incremental changes rather than massive updates. Modifying the user interface, service worker logic, cache keys, and data schemas simultaneously makes deployment bugs difficult to trace.

Test With Real Network Conditions

While developer tools effectively simulate offline environments, testing on physical mobile hardware over real connections remains necessary. Verify that a first visit online successfully caches the application shell, and that subsequent visits render cleanly with network hardware disabled. Check that previously loaded directory data persists, while first-time offline visits route to a clear fallback page. Confirm that external outbound links bypass service worker storage and that updated JSON datasets replace legacy cache files upon reconnection. Ensure obsolete shell caches purge automatically after version bumps, missing shell assets produce visible console logs during installation, and offline notifications remain fully accessible via keyboard and screen reader navigation. Finally, confirm that preview and production deployment paths resolve local assets identically, auditing cached entries using browser storage panels.

Privacy and Storage Boundaries

Do not store private API responses, authenticated pages, query parameters containing user details, or cross-origin assets inside service worker caches. This implementation is tailored strictly for a public static index and its local application shell.

Users can clear site storage at any time. The resource index must remain fully functional online even if service worker initialization fails or cached data is purged. Offline access serves as an enhancement rather than a requirement for core operations.

Final Checklist

Before deploying your offline worker, verify that your offline guarantees are clearly defined and limited to essential shell assets. Ensure cache keys utilize explicit version strings, and verify that obsolete caches are purged using project-specific prefixes. Confirm that resource data follows a network-first strategy, while application shell files rely on cache-first routing. Ensure cross-origin requests bypass the worker, cached JSON data displays an editorial review date, navigation routes feature a dedicated offline fallback page, and all update and error states remain intuitive and transparent.

Final Takeaway

An offline-ready resource index does not need to store the entire web. Preserving the lightweight user interface, retaining the most recent directory data, and maintaining transparency around data freshness produces a faster, more resilient static site that respects storage constraints and external site boundaries.