Blog

Insights and articles about Web3 development, software engineering, and the latest in technology.

Unlocking Offline Experiences with Service Workers

Unlocking Offline Experiences with Service Workers

<h4>This article will include the following:</h4> <ol> <li>Overview. What are service workers</li> <li>Capabilities</li> <li>Benefits</li> <li>Scope</li> <li>How to register one</li> <li>Service worker Life cycle</li> <li>Use cases</li> </ol> <h4>Overview</h4> <p>Nowadays users expect web apps to start on slow or flaky network connections, or even offline. Google did a <a href="https://www.marketingdive.com/news/google-53-of-mobile-users-abandon-sites-that-take-over-3-seconds-to-load/426070/">study</a> about the percentage of users that will abandon a website if it loads for more than 3 seconds.</p> <p>As developers, our job is to make the user journey as smooth and straight forward as possible. This requires from us to use different tools and techniques to make that possible. One of these tools are service workers.</p> <p>Service workers are a fundamental part of PWAs (Progressive Web Apps) They are background scripts that run independently of the web page, intercepting network requests and caching resources, providing offline capabilities (even when a user has no internet connection a progressive web app can still load and function correctly, as the service worker will serve cached assets). Also, a PWA can load quickly and efficiently, as it doesn’t have to wait for network requests to complete.</p> <figure><img alt="" src="https://cdn-images-1.medium.com/max/1024/1*z83FJ-X0OAFN8boeX2IURQ.png"></figure><p>However, while service workers provide many benefits, they do require careful planning and implementation; service worker implementation requires special functions and methods. Developers need to ensure that service workers do not interfere with the functionality of the PWA and that they handle updates and errors correctly.</p> <h4>Service worker Capabilities</h4> <p>Service Workers offer a wide range of capabilities that can supercharge your web applications. We’ll delve into key features like caching strategies, background sync, and push notifications. You’ll discover how to leverage these capabilities to create blazing-fast, reliable, and engaging web experiences.</p> <p>Some of the most used capabilities are:</p> <ol> <li> <strong>Offline Support</strong>: Service Workers enable web applications to work offline or in low-network conditions by intercepting and caching network requests and responses. This allows users to access content and functionality even when they are not connected to the internet.</li> <li> <strong>Caching</strong>: Developers can control the caching of assets such as HTML, CSS, JavaScript, images, and other resources. This helps in improving the performance of web apps by serving cached content from the local storage instead of making repeated network requests.</li> <li> <strong>Background Sync:</strong> Service Workers can schedule background sync tasks. This feature is particularly useful for apps that require periodic data synchronization, like email clients or social media apps. It ensures that data is updated in the background, even if the app is not currently open.</li> <li> <strong>Data Background Fetch</strong>: This feature allows web apps to request and process data in the background, enabling scenarios like pre-fetching data to improve user experience.</li> </ol> <h4>Benefits of using service workers</h4> <p>Service workers are easy and straight forward optimization of your web app that we often underestimate. They make it possible to:</p> <ul> <li>Decrease your page load time</li> <li>Implement better caching</li> <li>Give a mobile application like feel to your application, providing functionalities such as home screen icons for mobile browsers, sending push notifications to the user, etc.</li> </ul> <h4>How to register a service worker</h4> <p>The code snippet bellow is checking if service worker property exists on the navigator object, then register the service worker that will be defined in the sw.js file. This tells the browser to register a service worker for the domain the file is being served from and whatever logic exists in the sw.js file is the worker for it.</p> <pre>// your main JavaScript file<br><br>if ('serviceWorker' in navigator) {<br> window.addEventListener('load', function() {<br> navigator.serviceWorker.register('/sw.js').then(function(registration) {<br> // Registration successful<br> console.log('ServiceWorker registration was successful in scope: ', registration.scope);<br> }, function(err) {<br> // Registration failed<br> console.log('ServiceWorker registration failed: ', err);<br> });<br> });<br>}</pre> <h4>Verify if a service worker is registered</h4> <p>We need to open the developer tools in order to verify if a service worker is registered correctly.</p> <p>In Firefox and Chromium-based browsers the process looks like this:</p> <ol> <li>Open developer tools and click the <strong>Application</strong> tab.</li> <li>In the left pane, select <strong>Service Workers</strong>.</li> <li>Check that the service worker’s script URL appears with the status “Activated”. On Firefox the status can be “Running” or “Stopped”.</li> </ol> <p>Once registered the service worker should look something like this:</p> <figure><img alt="Service worker — developer tools on Chrome" src="https://cdn-images-1.medium.com/max/1024/1*sBOqRAaIEeznq2Hyn3aoXg.png"><figcaption>Service worker — developer tools on Chrome</figcaption></figure><h4>Scope</h4> <p>Scope refers to the folder of your service worker. A service worker that lives at example.com/pwa/sw.js can control any navigation at the /<em>pwa</em> path or below, such as example.com/pwa/demo. Service workers can only control items such as pages and workers (collectively "clients") in their scope. Scope applies to browser tabs and PWA windows.</p> <p>Only <em>one</em> service worker per scope is allowed. When active and running, only one instance is typically available no matter how many clients are in memory (such as PWA windows or browser tabs).</p> <p><em>The most common implementation of the service worker intercept all the requests related to your PWA. You should set the scope of your service worker as close to the root of the app as possible. You should not put it inside, for instance, a JavaScript folder or have it loaded from a CDN.</em></p> <h4>Life cycle</h4> <ol> <li>Registration: The first step is to register a Service Worker in your web application. This typically happens in your main JavaScript file or a dedicated service worker file. You use the navigator.serviceWorker.register() method to register a service worker script. The Service Worker script is then downloaded and installed in the background.</li> <li>Installation: After registration, the browser downloads and installs the Service Worker script. During the installation phase, you can specify which resources (e.g., HTML, CSS, JavaScript files) the Service Worker should cache for offline use. This is often done in the install event handler.</li> <li>Activation: Once the Service Worker is successfully installed, it enters the activation phase. During activation, the new Service Worker takes control of any open tabs or windows that are under its scope. This is where you can remove old caches and perform cleanup tasks from previous Service Worker versions. The activate event handler is commonly used for this purpose.</li> <li>Fetch and Intercept: After activation, the Service Worker is active and can intercept network requests made by the web application. This allows you to implement various caching strategies, including cache-first, network-first, or stale-while-revalidate. You handle fetch events in the fetch event listener.</li> <li>Update: If there’s a new version of the Service Worker available (detected during the registration phase), it goes through the installation and activation phases again. However, the new version won’t take control until all tabs using the old Service Worker are closed.</li> <li>Termination: Service Workers can be terminated by the browser when they’re not in use or when the browser is under memory pressure. They can also be manually unregistered.</li> </ol> <h4>Use cases</h4> <p>We could not finish the article without mentioning a couple of use cases:</p> <ol> <li> <strong>Offline analytics</strong> — Capturing telemetry related to users in the web app is highly useful when it comes to user behavior analytics. Typical analytic frameworks require an active connection to send these data to a server. However, with service workers, you can capture user behavior in client side and send it to an analytics engine with background sync. Implementing this by yourself might be a cumbersome task. Google has come up with a tool to support offline analytics named Workbox Google Analytics.</li> <li> <strong>Automatic failovers — </strong>Imagine you have multiple servers to fetch data, and one of your servers is starting to fail. Using service workers, we can quickly implement a fallback mechanism to get data/resources from another server.</li> <li> <strong>Synchronizing data in background — </strong>Service workers can be customized so that when the user performs a post request like edit or update in an app with an unstable connection, it can defer that request and only send it to the server when the connection is stable. The way it does it is in the background through caching and doesn’t affect the offline user experience.</li> </ol> <h4>Conclusion</h4> <p>Service workers are a great way for enhancing the performance, reliability, and engagement of web applications with little effort. By intercepting network requests, caching resources, and providing real-time updates, service workers enable developers to create progressive web apps that can function like native apps.</p> <p>The main concepts that we need to keep in mind when developing a service worker are: <strong>Scope</strong>, <strong>Lifecycle</strong>,<strong> Life span</strong>.</p> <h4>Further reading:</h4> <ol> <li><a href="https://developer.mozilla.org/en-US/docs/Web/API/Service_Worker_API">https://developer.mozilla.org/en-US/docs/Web/API/Service_Worker_API</a></li> <li><a href="https://blog.openreplay.com/a-practical-guide-to-service-workers/">https://blog.openreplay.com/a-practical-guide-to-service-workers/</a></li> <li><a href="https://blog.bitsrc.io/using-service-workers-with-react-27a4c5e2d1a9">https://blog.bitsrc.io/using-service-workers-with-react-27a4c5e2d1a9</a></li> </ol> <img src="https://medium.com/_/stat?event=post.clientViewed&amp;referrerSource=full_rss&amp;postId=5613c6772585" width="1" height="1" alt="">

7 min read
Efficient React Web Apps: 5 Optimization Tricks

Efficient React Web Apps: 5 Optimization Tricks

<p>Are you looking to take your React web application to the next level in terms of performance and efficiency? Look no further!</p> <p>In this article, we will explore a short list of React optimization techniques that can help transform your web application into a high-performing powerhouse.</p> <p><em>Did you know that the average user’s patience for a web page to load before they leave out of frustration is about 3 seconds (according to a </em><a href="https://www.marketingdive.com/news/google-53-of-mobile-users-abandon-sites-that-take-over-3-seconds-to-load/426070/"><em>study that Google performed</em></a><em>)</em></p> <p><strong>So here are some ways to handle it:</strong></p> <ol> <li> <strong>Code Splitting</strong>— Code splitting allows you to divide your React application into smaller chunks, loading only the necessary code for each specific page or feature. Repetitive logic in the code should be extracted and reused. This will improve the initial loading time and reduces the overall bundle size.</li> <li> <strong>Lazy loading </strong>— This ties back to the previous point. Lazy loading is a technique that allows us to defer the loading of non-critical resources or content until they are actually needed. Implementing Lazy loading could significantly improve the performance and the user experience. It will be particularly beneficial for large media files, such as images or videos, which are not immediately visible to the user.</li> <li> <strong>Remove redundant imports / Bundle cost optimization</strong> — We know that the bigger the bundle that we serve to the user, the more time and resources it will require. This is something to keep in mind while writing code (sometimes we don’t need to import the whole library if we are using only one specific method of it). A great tool that could help with this one is the <em>“</em><strong><em>Import Cost</em></strong><em>”</em> extension for VSCode. It will automatically show you in real-time the size of the package that you are importing (marking with a different color scheme for the bigger packages and urging you for optimizing your imports). Another set of great tools which could help with this are <strong><em>BundlePhobia.com</em></strong> (which helps you find an npm package and its alternatives) and <strong><em>source-map-explorer</em></strong> (which will show you a treemap visualization of the packages in use)</li> <li> <strong>Avoid unnecessary re-renders</strong> — Re-renders are quite costly and will slow down our application or cause a bad user experience. A component gets re-rendered in React by changing its <strong><em>props</em></strong> or <strong><em>state. </em></strong>This hints us that we have to make sure we are properly handling a component’s props and state. One of the most common ways that could be overlooked is the misuse of the <strong>useEffect</strong> hook and its dependencies. We have to make sure that only relevant dependencies are added to the dependency array for every useEffect.</li> <li> <strong><em>Take advantage of useMemo and useCallback </em></strong><em>— </em><strong>useCallback</strong> and <strong>useMemo</strong> are both React Hooks that help optimize the performance of a React application by memoizing values. They both accept a function as an argument and return a memoized version of the function. The difference between them is the type of value they return. <strong>useCallback</strong> returns a memoized callback function, while <strong>useMemo</strong> returns a memoized value. Both hooks can be used to optimize the performance of your React components by avoiding unnecessary re-creations of functions or values.</li> </ol> <h4><strong>Bonus tip:</strong></h4> <p>While performing any of the mentioned optimization methods we would need to have a way to measure if we are improving things and how much. One tool that I have found extremely useful is <a href="https://developer.chrome.com/docs/lighthouse/overview/"><strong>Lighthouse</strong></a><strong>. </strong>Generating Lighthouse reports is a great way to track your progress and get some custom tips. <br>Some of the indicators that you might want to improve are —<strong> First Contentful Pain (FCP), Largest Contentful Pain (LCP), Speed Index (SI),</strong> etc.</p> <h4><strong>Conclusion</strong></h4> <p>The adoption of the techniques mentioned above is crucial for improving the performance of your React application. There might be also other approaches that needs to be done in order to reach peak performance and this is why the usage of tools that will analyze the performance of your application is crucial. <br><em>Performance optimization is not a sprint, but rather a marathon</em>. All of the principles should be kept in mind while writing code and it is wise to make regular revisions in order to deliver high-quality code and avoid introducing technical debt in the future.</p> <h4><em>PS: Here is the list of tools mentioned:</em></h4> <ol> <li><a href="https://bundlephobia.com/"><strong><em>BundlePhobia.com</em></strong></a></li> <li><a href="https://www.npmjs.com/package/source-map-explorer"><strong><em>source-map-explorer</em></strong></a></li> <li><a href="https://marketplace.visualstudio.com/items?itemName=wix.vscode-import-cost"><strong><em>Import Cost extension</em></strong></a></li> <li><a href="https://developer.chrome.com/docs/lighthouse/overview/"><strong><em>Lighthouse</em></strong></a></li> </ol> <img src="https://medium.com/_/stat?event=post.clientViewed&amp;referrerSource=full_rss&amp;postId=d47c6cae0fc3" width="1" height="1" alt="">

4 min read