<?xml version="1.0" encoding="UTF-8"?>
<rss version="2.0" xmlns:atom="http://www.w3.org/2005/Atom" xmlns:dc="http://purl.org/dc/elements/1.1/">
  <channel>
    <title>DEV Community: Abdulhakeem Adekola</title>
    <description>The latest articles on DEV Community by Abdulhakeem Adekola (@saghe_dev).</description>
    <link>https://dev.to/saghe_dev</link>
    <image>
      <url>https://media2.dev.to/dynamic/image/width=90,height=90,fit=cover,gravity=auto,format=auto/https:%2F%2Fdev-to-uploads.s3.us-east-2.amazonaws.com%2Fuploads%2Fuser%2Fprofile_image%2F4239919%2F0b6303c1-cdfd-4bf6-b0c4-a1e94e5ed46e.jpg</url>
      <title>DEV Community: Abdulhakeem Adekola</title>
      <link>https://dev.to/saghe_dev</link>
    </image>
    <atom:link rel="self" type="application/rss+xml" href="https://dev.to/feed/saghe_dev"/>
    <language>en</language>
    <item>
      <title>Building Resilient Web Applications: Surviving Unreliable Networks and Low Bandwidth (Part 1)</title>
      <dc:creator>Abdulhakeem Adekola</dc:creator>
      <pubDate>Wed, 11 Nov 2026 22:16:28 +0000</pubDate>
      <link>https://dev.to/saghe_dev/building-resilient-web-applications-surviving-unreliable-networks-and-low-bandwidth-part-1-46b5</link>
      <guid>https://dev.to/saghe_dev/building-resilient-web-applications-surviving-unreliable-networks-and-low-bandwidth-part-1-46b5</guid>
      <description>&lt;p&gt;&lt;em&gt;Making web applications available when the network isn't.&lt;/em&gt;&lt;/p&gt;

&lt;p&gt;Imagine opening an application, waiting several seconds for it to load, and then losing your internet connection.&lt;/p&gt;

&lt;p&gt;You refresh the page, and everything disappears.&lt;/p&gt;

&lt;p&gt;Perhaps you were trying to submit a form, view some information, or complete an important task. Suddenly, an unstable network has made the entire application unusable.&lt;/p&gt;

&lt;p&gt;For many developers, this might seem like an ordinary network failure. But for users who regularly deal with unreliable connectivity, expensive data, and inconsistent network speeds, it is a frustrating part of their everyday experience.&lt;/p&gt;

&lt;p&gt;This raises an important engineering question:&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;How do we build web applications that remain useful when the network becomes unreliable?&lt;/strong&gt;&lt;/p&gt;

&lt;p&gt;I recently delivered a workshop on &lt;em&gt;Building Resilient Apps for Unreliable Networks &amp;amp; Low Bandwidth&lt;/em&gt;. The goal was to explore practical techniques developers can use to make web applications more resilient, particularly in environments where internet connectivity cannot always be guaranteed.&lt;/p&gt;

&lt;p&gt;In this two-part series, I'll share the concepts, implementation decisions, challenges, and lessons from building a Resilient Task Board.&lt;/p&gt;

&lt;p&gt;In this first part, we'll explore browser storage, Service Workers, caching strategies, and how to make an application's interface available offline.&lt;/p&gt;

&lt;p&gt;Let's get into it.&lt;/p&gt;

&lt;h2&gt;
  
  
  1. The Network Is Not Always Reliable
&lt;/h2&gt;

&lt;p&gt;Most modern web applications are designed around an assumption: the network will be available whenever the user needs it.&lt;/p&gt;

&lt;p&gt;A typical application might follow this architecture:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;User
  |
  v
Frontend Application
  |
  v
Backend API
  |
  v
Database
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;When the user performs an action, the frontend sends a request to the backend. The backend processes it, communicates with the database, and returns a response.&lt;/p&gt;

&lt;p&gt;This works perfectly when connectivity is stable.&lt;/p&gt;

&lt;p&gt;But what happens when:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;The network becomes extremely slow?&lt;/li&gt;
&lt;li&gt;A request takes several seconds to complete?&lt;/li&gt;
&lt;li&gt;The user loses connectivity halfway through an operation?&lt;/li&gt;
&lt;li&gt;The backend becomes temporarily unavailable?&lt;/li&gt;
&lt;li&gt;The user refreshes the page while offline?&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;In many applications, these situations result in loading screens, network errors, or completely unusable interfaces.&lt;/p&gt;

&lt;p&gt;The problem isn't necessarily that the backend is poorly implemented. Sometimes, the application simply has no strategy for dealing with an unavailable network.&lt;/p&gt;

&lt;p&gt;We need to start thinking beyond the happy path.&lt;/p&gt;

&lt;p&gt;Instead of asking only, "How does my application work when the network is available?", we should also ask, "What should happen when it isn't?"&lt;/p&gt;

&lt;h2&gt;
  
  
  Before we move on; Let's look at understanding the Network: Bandwidth, Latency, and Reliability
&lt;/h2&gt;

&lt;p&gt;Before exploring how to make web applications resilient, it is important to understand the network conditions that can make an otherwise functional application frustrating to use.&lt;/p&gt;

&lt;p&gt;Two concepts are particularly important: &lt;strong&gt;bandwidth&lt;/strong&gt; and &lt;strong&gt;latency&lt;/strong&gt;.&lt;/p&gt;

&lt;h3&gt;
  
  
  1. Bandwidth: How Much Data Can Move?
&lt;/h3&gt;

&lt;p&gt;Bandwidth refers to the maximum amount of data a network connection can transfer within a given period. It is commonly measured in bits per second (bps), such as Mbps (megabits per second).&lt;/p&gt;

&lt;p&gt;Think of bandwidth as the width of a road.&lt;/p&gt;

&lt;p&gt;A wider road allows more vehicles to travel through at the same time. Similarly, a connection with higher bandwidth can transfer more data within a given period.&lt;/p&gt;

&lt;p&gt;When bandwidth is low:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;Large images take longer to load.&lt;/li&gt;
&lt;li&gt;JavaScript bundles and other assets take longer to download.&lt;/li&gt;
&lt;li&gt;Pages containing many resources become slower to load.&lt;/li&gt;
&lt;li&gt;Data-heavy applications become expensive and frustrating to use.&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;For example, an application that loads a 5 MB JavaScript bundle may feel reasonably fast on a high-bandwidth connection but painfully slow on a limited connection.&lt;/p&gt;

&lt;h3&gt;
  
  
  2. Latency: How Long Does It Take to Get a Response?
&lt;/h3&gt;

&lt;p&gt;Latency is the time it takes for data to travel from one point to another and for a response to come back. It is commonly measured in milliseconds (ms).&lt;/p&gt;

&lt;p&gt;Think of latency as the time it takes for a vehicle to travel from one end of a road to another.&lt;/p&gt;

&lt;p&gt;Even when the road is wide, a long distance or delay can mean that the vehicle takes a while to arrive.&lt;/p&gt;

&lt;p&gt;In web applications, latency affects how quickly a request receives a response.&lt;/p&gt;

&lt;p&gt;For example:&lt;/p&gt;

&lt;ol&gt;
&lt;li&gt;A user clicks the &lt;strong&gt;Create Task&lt;/strong&gt; button.&lt;/li&gt;
&lt;li&gt;The browser sends a request to the backend.&lt;/li&gt;
&lt;li&gt;The backend processes the request.&lt;/li&gt;
&lt;li&gt;The server sends a response.&lt;/li&gt;
&lt;li&gt;The interface updates with the result.&lt;/li&gt;
&lt;/ol&gt;

&lt;p&gt;If the network has high latency, the user may wait several seconds before seeing the result, even if the amount of data being transferred is very small.&lt;/p&gt;

&lt;p&gt;This is why an application can feel slow even when it does not transfer much data.&lt;/p&gt;

&lt;h3&gt;
  
  
  3. Low Bandwidth and High Latency Are Not the Same
&lt;/h3&gt;

&lt;p&gt;These two conditions are related, but they describe different problems.&lt;/p&gt;

&lt;div class="table-wrapper-paragraph"&gt;&lt;table&gt;
&lt;thead&gt;
&lt;tr&gt;
&lt;th&gt;Condition&lt;/th&gt;
&lt;th&gt;What it means&lt;/th&gt;
&lt;th&gt;Typical effect&lt;/th&gt;
&lt;/tr&gt;
&lt;/thead&gt;
&lt;tbody&gt;
&lt;tr&gt;
&lt;td&gt;Low bandwidth&lt;/td&gt;
&lt;td&gt;Limited data transfer capacity&lt;/td&gt;
&lt;td&gt;Large resources take longer to download&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;High latency&lt;/td&gt;
&lt;td&gt;Long delay before a response arrives&lt;/td&gt;
&lt;td&gt;Requests feel slow, even when they transfer little data&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Unstable connection&lt;/td&gt;
&lt;td&gt;Connection quality changes or drops unexpectedly&lt;/td&gt;
&lt;td&gt;Requests may fail or become unpredictable&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Offline&lt;/td&gt;
&lt;td&gt;No usable network connection&lt;/td&gt;
&lt;td&gt;Network-dependent operations cannot complete&lt;/td&gt;
&lt;/tr&gt;
&lt;/tbody&gt;
&lt;/table&gt;&lt;/div&gt;

&lt;p&gt;A user can experience high latency with plenty of bandwidth, or low bandwidth with relatively low latency. They can also experience both at the same time.&lt;/p&gt;

&lt;h3&gt;
  
  
  4. Why These Conditions Matter to Developers
&lt;/h3&gt;

&lt;p&gt;A common mistake is to build and test applications under ideal network conditions, then assume users will experience the same thing.&lt;/p&gt;

&lt;p&gt;But real-world networks are not always predictable.&lt;/p&gt;

&lt;p&gt;A user might:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;Have a fast connection at home but a weak connection while commuting.&lt;/li&gt;
&lt;li&gt;Experience high latency during network congestion.&lt;/li&gt;
&lt;li&gt;Have limited mobile data and avoid downloading unnecessary resources.&lt;/li&gt;
&lt;li&gt;Lose connectivity while submitting a form or creating a record.&lt;/li&gt;
&lt;li&gt;Regain connectivity after spending several minutes offline.&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;These situations expose weaknesses in applications that depend entirely on uninterrupted communication with a backend.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;The goal is not simply to make an application load faster. It is to make the application remain useful when the network becomes slow, unreliable, or unavailable.&lt;/strong&gt;&lt;/p&gt;

&lt;p&gt;That is the foundation of building resilient web applications.&lt;/p&gt;

&lt;h2&gt;
  
  
  2. Understanding the Browser's Offline Capabilities
&lt;/h2&gt;

&lt;p&gt;Before implementing anything, I wanted to understand what the browser already provides.&lt;/p&gt;

&lt;p&gt;There are several browser APIs that can help us build applications capable of working with unreliable connectivity.&lt;/p&gt;

&lt;p&gt;The important ones for this project are:&lt;/p&gt;

&lt;h3&gt;
  
  
  LocalStorage
&lt;/h3&gt;

&lt;p&gt;LocalStorage provides a simple key-value storage mechanism in the browser.&lt;/p&gt;

&lt;p&gt;It stores strings, meaning structured objects must be serialized before storage, usually with &lt;code&gt;JSON.stringify()&lt;/code&gt;.&lt;/p&gt;

&lt;p&gt;It is useful for things like:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;User preferences.&lt;/li&gt;
&lt;li&gt;Theme settings.&lt;/li&gt;
&lt;li&gt;Simple application configuration.&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;However, it is synchronous and doesn't provide the querying, indexing, and transaction capabilities we need for a more structured offline data layer.&lt;/p&gt;

&lt;h3&gt;
  
  
  IndexedDB
&lt;/h3&gt;

&lt;p&gt;IndexedDB is an asynchronous, browser-based database designed for storing structured data.&lt;/p&gt;

&lt;p&gt;Unlike LocalStorage, it allows us to store JavaScript objects, create indexes, and perform database transactions.&lt;/p&gt;

&lt;p&gt;For an application like our Task Board, it can store:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;Previously retrieved tasks.&lt;/li&gt;
&lt;li&gt;Locally created tasks.&lt;/li&gt;
&lt;li&gt;Pending operations waiting to synchronize with the backend.&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;Think of it as a local database that allows the frontend to work with structured application data without constantly depending on the network.&lt;/p&gt;

&lt;h3&gt;
  
  
  Cache API
&lt;/h3&gt;

&lt;p&gt;The Cache API allows us to store and retrieve HTTP request and response objects.&lt;/p&gt;

&lt;p&gt;It is particularly useful for caching:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;HTML documents.&lt;/li&gt;
&lt;li&gt;JavaScript and CSS files.&lt;/li&gt;
&lt;li&gt;Images.&lt;/li&gt;
&lt;li&gt;Fonts.&lt;/li&gt;
&lt;li&gt;Selected network responses.&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;One important distinction: the Cache API and IndexedDB solve different problems.&lt;/p&gt;

&lt;p&gt;The Cache API is useful for storing resources and HTTP responses, while IndexedDB is better suited to structured application data.&lt;/p&gt;

&lt;h3&gt;
  
  
  Service Workers
&lt;/h3&gt;

&lt;p&gt;A Service Worker is a JavaScript file that runs separately from the main browser thread.&lt;/p&gt;

&lt;p&gt;It acts as an intermediary between the application, the browser, and the network.&lt;/p&gt;

&lt;p&gt;It can intercept network requests, serve cached responses, and provide offline fallbacks.&lt;/p&gt;

&lt;p&gt;These four technologies form an important foundation for building resilient web applications.&lt;/p&gt;

&lt;h2&gt;
  
  
  3. Introducing Service Workers
&lt;/h2&gt;

&lt;p&gt;A Service Worker is one of the most important browser technologies for offline-capable web applications.&lt;/p&gt;

&lt;p&gt;It allows us to control how certain network requests are handled.&lt;/p&gt;

&lt;p&gt;For example, instead of allowing every request to go directly to the network, we can intercept it and decide whether to:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;Retrieve a cached response.&lt;/li&gt;
&lt;li&gt;Fetch a fresh response from the network.&lt;/li&gt;
&lt;li&gt;Return a cached response when the network fails.&lt;/li&gt;
&lt;li&gt;Apply a different strategy depending on the resource being requested.&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;Service Workers have a lifecycle consisting of three major stages:&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Registration:&lt;/strong&gt; The application registers the Service Worker with the browser.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Installation:&lt;/strong&gt; The browser installs it. This is where we can prepare caches and download essential resources.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Activation:&lt;/strong&gt; The Service Worker becomes active and can take control of eligible pages.&lt;/p&gt;

&lt;p&gt;Here's a basic registration example:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight typescript"&gt;&lt;code&gt;&lt;span class="k"&gt;if&lt;/span&gt; &lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s2"&gt;"serviceWorker"&lt;/span&gt; &lt;span class="k"&gt;in&lt;/span&gt; &lt;span class="n"&gt;navigator&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
  &lt;span class="nb"&gt;window&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;addEventListener&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s2"&gt;"load"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="o"&gt;=&amp;gt;&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
    &lt;span class="n"&gt;navigator&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;serviceWorker&lt;/span&gt;
      &lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;register&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s2"&gt;"/sw.js"&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
      &lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;then&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;registration&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="o"&gt;=&amp;gt;&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
        &lt;span class="nb"&gt;console&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;log&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s2"&gt;"Service Worker registered:"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;registration&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;scope&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
      &lt;span class="p"&gt;}&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
      &lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="k"&gt;catch&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;error&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="o"&gt;=&amp;gt;&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
        &lt;span class="nb"&gt;console&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;error&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s2"&gt;"Service Worker registration failed:"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;error&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
      &lt;span class="p"&gt;}&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
  &lt;span class="p"&gt;}&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
&lt;span class="p"&gt;}&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;The browser's support for Service Workers should be checked before attempting registration.&lt;/p&gt;

&lt;p&gt;For our project, I created a &lt;code&gt;sw.js&lt;/code&gt; file and registered it from the application's entry point.&lt;/p&gt;

&lt;p&gt;But registering a Service Worker alone doesn't make an application work offline.&lt;/p&gt;

&lt;p&gt;We need to teach it what to cache and how to respond when the network is unavailable.&lt;/p&gt;

&lt;h2&gt;
  
  
  4. Understanding the Application Shell
&lt;/h2&gt;

&lt;p&gt;One of the first concepts I explored was the Application Shell.&lt;/p&gt;

&lt;p&gt;The Application Shell is the collection of essential resources required to load the basic structure and interface of an application.&lt;/p&gt;

&lt;p&gt;For a typical frontend application, this might include:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;HTML document.&lt;/li&gt;
&lt;li&gt;JavaScript bundles.&lt;/li&gt;
&lt;li&gt;CSS stylesheets.&lt;/li&gt;
&lt;li&gt;Essential fonts.&lt;/li&gt;
&lt;li&gt;Icons and images.&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;Consider a React application.&lt;/p&gt;

&lt;p&gt;The browser first loads the HTML document. That document references JavaScript and CSS files. The JavaScript executes and renders the React interface, while the CSS provides the styling.&lt;/p&gt;

&lt;p&gt;What happens if we cache only the HTML document?&lt;/p&gt;

&lt;p&gt;The browser may successfully retrieve the HTML while offline, but if the JavaScript bundle is unavailable, React cannot render the application.&lt;/p&gt;

&lt;p&gt;The result could be a blank page.&lt;/p&gt;

&lt;p&gt;This was an important distinction for me:&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Caching the HTML document is not the same as making the entire application shell available offline.&lt;/strong&gt;&lt;/p&gt;

&lt;p&gt;We need to account for all the resources required to render the interface.&lt;/p&gt;

&lt;h2&gt;
  
  
  5. Pre-caching and Runtime Caching
&lt;/h2&gt;

&lt;p&gt;The next step was understanding two important caching approaches.&lt;/p&gt;

&lt;h3&gt;
  
  
  Pre-caching
&lt;/h3&gt;

&lt;p&gt;Pre-caching involves downloading important resources and storing them in Cache Storage before the user needs them.&lt;/p&gt;

&lt;p&gt;This is commonly done during the Service Worker's installation phase.&lt;/p&gt;

&lt;p&gt;For example:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight javascript"&gt;&lt;code&gt;&lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="no"&gt;CACHE_NAME&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="s2"&gt;"resilient-task-board-v1"&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;

&lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="no"&gt;APP_SHELL_ASSETS&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="p"&gt;[&lt;/span&gt;
  &lt;span class="s2"&gt;"/"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
  &lt;span class="s2"&gt;"/index.html"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
  &lt;span class="s2"&gt;"/favicon.svg"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
&lt;span class="p"&gt;]&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;

&lt;span class="n"&gt;self&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;addEventListener&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s2"&gt;"install"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;event&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="o"&gt;=&amp;gt;&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
  &lt;span class="n"&gt;event&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;waitUntil&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;
    &lt;span class="n"&gt;caches&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;open&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="no"&gt;CACHE_NAME&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;then&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;cache&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="o"&gt;=&amp;gt;&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
      &lt;span class="k"&gt;return&lt;/span&gt; &lt;span class="n"&gt;cache&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;addAll&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="no"&gt;APP_SHELL_ASSETS&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
    &lt;span class="p"&gt;}&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
  &lt;span class="p"&gt;)&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
&lt;span class="p"&gt;}&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;A few things are happening here:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;code&gt;caches.open()&lt;/code&gt; creates or opens a named cache.&lt;/li&gt;
&lt;li&gt;
&lt;code&gt;cache.addAll()&lt;/code&gt; fetches the listed resources and stores their responses.&lt;/li&gt;
&lt;li&gt;
&lt;code&gt;event.waitUntil()&lt;/code&gt; keeps the installation event alive until the caching operation settles.&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;If a required resource fails to download, the installation can fail.&lt;/p&gt;

&lt;h3&gt;
  
  
  Runtime caching
&lt;/h3&gt;

&lt;p&gt;Runtime caching stores resources as the application requests them.&lt;/p&gt;

&lt;p&gt;For example, if the browser requests a JavaScript file and successfully retrieves it from the network, the Service Worker can save a copy in Cache Storage.&lt;/p&gt;

&lt;p&gt;On a subsequent request, the cached copy can be returned.&lt;/p&gt;

&lt;p&gt;This is particularly useful for resources whose filenames are generated during the build process.&lt;/p&gt;

&lt;p&gt;Modern frontend build tools often generate hashed filenames, such as:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;/assets/index-a83f21.js
/assets/index-b72c41.css
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;These filenames can change whenever the application is rebuilt.&lt;/p&gt;

&lt;p&gt;That means we need to think carefully about how we discover and cache the actual production assets.&lt;/p&gt;

&lt;p&gt;The browser's Cache API remains the same regardless of the framework or build tool. What changes is how we obtain the list of files to cache.&lt;/p&gt;

&lt;h2&gt;
  
  
  6. Choosing the Right Caching Strategy
&lt;/h2&gt;

&lt;p&gt;Not every resource should be handled in the same way.&lt;/p&gt;

&lt;p&gt;Two common strategies are Network-first and Cache-first.&lt;/p&gt;

&lt;h3&gt;
  
  
  Network-first
&lt;/h3&gt;

&lt;p&gt;With Network-first, the Service Worker attempts to retrieve a fresh response from the network.&lt;/p&gt;

&lt;p&gt;If the request fails, it falls back to Cache Storage.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;Request
   |
   v
Try Network
   |
   +---- Success ----&amp;gt; Return network response
   |
   +---- Failure ----&amp;gt; Return cached response
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;This is useful when we want fresh content whenever connectivity is available but still need an offline fallback.&lt;/p&gt;

&lt;p&gt;I used this approach for navigation requests.&lt;/p&gt;

&lt;h3&gt;
  
  
  Cache-first
&lt;/h3&gt;

&lt;p&gt;With Cache-first, the Service Worker checks Cache Storage before attempting a network request.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;Request
   |
   v
Check Cache
   |
   +---- Found ----&amp;gt; Return cached response
   |
   +---- Missing --&amp;gt; Try Network
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;This works well for static resources such as JavaScript, CSS, fonts, and icons.&lt;/p&gt;

&lt;p&gt;For our Task Board, I used Cache-first for static resources and Network-first for navigation.&lt;/p&gt;

&lt;p&gt;The important lesson is that caching strategies should be selected according to the nature of the resource and how fresh it needs to be.&lt;/p&gt;

&lt;h2&gt;
  
  
  7. Building the Service Worker for the Task Board
&lt;/h2&gt;

&lt;p&gt;For the Resilient Task Board, I started with a simple Service Worker and gradually improved it.&lt;/p&gt;

&lt;p&gt;My implementation introduced:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;A versioned cache.&lt;/li&gt;
&lt;li&gt;Pre-caching of stable application entry resources.&lt;/li&gt;
&lt;li&gt;Cleanup of outdated caches during activation.&lt;/li&gt;
&lt;li&gt;Navigation fallback to cached HTML.&lt;/li&gt;
&lt;li&gt;Cache-first handling for static resources.&lt;/li&gt;
&lt;li&gt;Runtime caching for successfully retrieved static assets.&lt;/li&gt;
&lt;li&gt;Bypassing API requests so the application's API layer could handle network failures.&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;The following is a simplified representation of the request-handling logic:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight javascript"&gt;&lt;code&gt;&lt;span class="n"&gt;self&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;addEventListener&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s2"&gt;"fetch"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;event&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="o"&gt;=&amp;gt;&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
  &lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="n"&gt;request&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;event&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;request&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;

  &lt;span class="k"&gt;if&lt;/span&gt; &lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;request&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;method&lt;/span&gt; &lt;span class="o"&gt;!==&lt;/span&gt; &lt;span class="s2"&gt;"GET"&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
    &lt;span class="k"&gt;return&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
  &lt;span class="p"&gt;}&lt;/span&gt;

  &lt;span class="k"&gt;if&lt;/span&gt; &lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;request&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;url&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;includes&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s2"&gt;"/api/"&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
    &lt;span class="k"&gt;return&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
  &lt;span class="p"&gt;}&lt;/span&gt;

  &lt;span class="k"&gt;if&lt;/span&gt; &lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;request&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;mode&lt;/span&gt; &lt;span class="o"&gt;===&lt;/span&gt; &lt;span class="s2"&gt;"navigate"&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
    &lt;span class="n"&gt;event&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;respondWith&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;
      &lt;span class="nb"&gt;fetch&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;request&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="k"&gt;catch&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="k"&gt;async&lt;/span&gt; &lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="o"&gt;=&amp;gt;&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
        &lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="n"&gt;cachedPage&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="k"&gt;await&lt;/span&gt; &lt;span class="n"&gt;caches&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;match&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s2"&gt;"/index.html"&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;

        &lt;span class="k"&gt;return&lt;/span&gt; &lt;span class="n"&gt;cachedPage&lt;/span&gt; &lt;span class="o"&gt;||&lt;/span&gt; &lt;span class="nc"&gt;Response&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;error&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
      &lt;span class="p"&gt;}&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
    &lt;span class="p"&gt;)&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;

    &lt;span class="k"&gt;return&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
  &lt;span class="p"&gt;}&lt;/span&gt;

  &lt;span class="n"&gt;event&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;respondWith&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;
    &lt;span class="n"&gt;caches&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;match&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;request&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;then&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;cachedResponse&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="o"&gt;=&amp;gt;&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
      &lt;span class="k"&gt;if&lt;/span&gt; &lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;cachedResponse&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
        &lt;span class="k"&gt;return&lt;/span&gt; &lt;span class="n"&gt;cachedResponse&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
      &lt;span class="p"&gt;}&lt;/span&gt;

      &lt;span class="k"&gt;return&lt;/span&gt; &lt;span class="nb"&gt;fetch&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;request&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
    &lt;span class="p"&gt;}&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
  &lt;span class="p"&gt;)&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
&lt;span class="p"&gt;}&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;This is a simplified excerpt, not the complete production implementation. In the actual project, I also added cache versioning, activation cleanup, and runtime caching of successful static responses.&lt;/p&gt;

&lt;p&gt;Notice that API requests are deliberately excluded.&lt;/p&gt;

&lt;p&gt;At this stage, the goal was to make the application's interface available offline, not to implement offline API operations. Those are separate concerns that we'll explore in Part 2.&lt;/p&gt;

&lt;h2&gt;
  
  
  8. A Debugging Lesson: Development Servers Can Mislead You
&lt;/h2&gt;

&lt;p&gt;One of the more interesting challenges I encountered happened during testing.&lt;/p&gt;

&lt;p&gt;After successfully caching the application resources, I enabled Offline in Chrome DevTools and refreshed the application.&lt;/p&gt;

&lt;p&gt;It failed.&lt;/p&gt;

&lt;p&gt;The browser console showed errors similar to:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;GET /@vite/client net::ERR_INTERNET_DISCONNECTED

GET /src/main.tsx net::ERR_INTERNET_DISCONNECTED

GET /@react-refresh net::ERR_INTERNET_DISCONNECTED
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;At first, this looked like a Service Worker problem.&lt;/p&gt;

&lt;p&gt;But there was another issue.&lt;/p&gt;

&lt;p&gt;I was testing through Vite's development server.&lt;/p&gt;

&lt;p&gt;Vite injects development-specific resources, including its client and React Refresh functionality. These resources were not part of the production assets I had cached.&lt;/p&gt;

&lt;p&gt;I had cached production resources but was testing a development environment.&lt;/p&gt;

&lt;p&gt;The solution was to test using the production build:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;npm run build
npm run preview
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;This allowed me to test the actual production output rather than development-specific modules.&lt;/p&gt;

&lt;p&gt;After making that adjustment and refining the Service Worker, the application shell successfully loaded offline.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;The lesson:&lt;/strong&gt; Always test offline behaviour against the environment and assets your users will actually receive. A development server can introduce behaviour that doesn't exist in production.&lt;/p&gt;

&lt;h2&gt;
  
  
  9. What We Have Achieved So Far
&lt;/h2&gt;

&lt;p&gt;At this point, our Task Board can load its basic interface without an internet connection after the necessary resources have been cached.&lt;/p&gt;

&lt;p&gt;That's a meaningful improvement, but it isn't the end of the journey.&lt;/p&gt;

&lt;p&gt;Imagine opening the Task Board offline.&lt;/p&gt;

&lt;p&gt;The interface loads successfully, but where are the tasks?&lt;/p&gt;

&lt;p&gt;The backend API is still unavailable.&lt;/p&gt;

&lt;p&gt;The Service Worker has helped us cache the application's resources, but we haven't yet provided a reliable local source for the application's actual data.&lt;/p&gt;

&lt;p&gt;This brings us to the next challenge.&lt;/p&gt;

&lt;h2&gt;
  
  
  What's Next?
&lt;/h2&gt;

&lt;p&gt;In Part 2, we'll move beyond making the interface available offline.&lt;/p&gt;

&lt;p&gt;We'll explore how IndexedDB can provide local application data, how to handle offline mutations, and how to synchronize pending operations when connectivity returns.&lt;/p&gt;

&lt;p&gt;We'll also look at some of the harder problems involved in building resilient applications:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;Optimistic updates.&lt;/li&gt;
&lt;li&gt;Offline operation queues.&lt;/li&gt;
&lt;li&gt;Synchronization and retries.&lt;/li&gt;
&lt;li&gt;Idempotency.&lt;/li&gt;
&lt;li&gt;Conflict resolution.&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;Because an application that merely opens offline isn't necessarily resilient.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;The real goal is to build applications that continue to serve users, preserve their actions, and recover gracefully when the network comes back.&lt;/strong&gt;&lt;/p&gt;




&lt;p&gt;&lt;em&gt;This article is based on my workshop, "Building Resilient Apps for Unreliable Networks &amp;amp; Low Bandwidth," and the practical implementation of the Resilient Task Board.&lt;/em&gt;&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Explore the project:&lt;/strong&gt; Follow along with the original, non-resilient version of the &lt;a href="https://github.com/SAGHEDEV/resillient-frontend-app" rel="noopener noreferrer"&gt;Resilient Task Board on GitHub&lt;/a&gt; and see how we progressively improve it to handle unreliable networks and offline conditions.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Part 2 coming soon.&lt;/strong&gt;&lt;/p&gt;

</description>
      <category>architecture</category>
      <category>frontend</category>
      <category>webdev</category>
      <category>typescript</category>
    </item>
  </channel>
</rss>
