Best Practices for Handling Updates in Progressive Web Apps

Learn the best practices for handling updates in Progressive Web Apps to ensure smooth transitions and optimal performance

Progressive Web Apps (PWAs) have revolutionized the web development landscape by combining the best features of web and mobile applications. One of the critical aspects of maintaining a PWA is handling updates efficiently. Ensuring that your app is always up-to-date with the latest features and security patches while providing a seamless user experience is vital. This article delves into the best practices for managing updates in PWAs, helping you keep your application current and your users satisfied.

Understanding the Update Process

How PWAs Handle Updates

PWAs use service workers to manage updates. Service workers act as intermediaries between your web application and the network, enabling offline capabilities and handling caching. When it comes to updates, service workers play a crucial role by checking for new versions of your application files and updating the cache accordingly.

The update process typically involves the following steps: the service worker fetches new versions of files, caches them, and then waits until all the current users have closed their existing sessions before activating the new version. This ensures that users do not experience any disruptions during their active sessions.

Challenges in Updating PWAs

Handling updates in PWAs comes with its own set of challenges. Ensuring that users always have the latest version without interrupting their experience requires a delicate balance. One common issue is the “stale while revalidate” problem, where users might see outdated content until the service worker fetches and activates the new version. Additionally, managing cache storage efficiently to prevent it from becoming bloated with old versions of files is another challenge.

To address these challenges, it’s essential to implement strategies that allow smooth updates and provide clear communication to users when updates are available. This ensures that your PWA remains efficient, up-to-date, and user-friendly.

Implementing Effective Update Strategies

Automatic Updates with Service Workers

One of the best ways to handle updates in PWAs is through automatic updates using service workers. This involves configuring the service worker to check for updates at regular intervals and cache new versions of files automatically. Here’s a basic example of how to set up automatic updates:

  1. Register the Service Worker:
if ('serviceWorker' in navigator) {
navigator.serviceWorker.register('/service-worker.js').then(registration => {
console.log('Service Worker registered with scope:', registration.scope);
}).catch(error => {
console.log('Service Worker registration failed:', error);
});
}
  1. Update the Service Worker:

In the service worker script, add logic to handle updates:

self.addEventListener('install', event => {
event.waitUntil(
caches.open('my-pwa-cache-v1').then(cache => {
return cache.addAll([
'/',
'/index.html',
'/styles.css',
'/script.js',
]);
})
);
});

self.addEventListener('activate', event => {
event.waitUntil(
caches.keys().then(cacheNames => {
return Promise.all(
cacheNames.filter(cacheName => {
return cacheName !== 'my-pwa-cache-v1';
}).map(cacheName => {
return caches.delete(cacheName);
})
);
})
);
});

self.addEventListener('fetch', event => {
event.respondWith(
caches.match(event.request).then(response => {
return response || fetch(event.request);
})
);
});

This script ensures that the service worker installs the new cache, activates it, and serves the updated content to users. Regularly checking for updates and caching new versions helps keep your PWA current without user intervention.

While automatic updates are convenient, it’s also important to give users control over when they update the app

Manual Update Notifications

While automatic updates are convenient, it’s also important to give users control over when they update the app. Implementing manual update notifications can enhance user experience by informing them about the available updates and allowing them to refresh the app at their convenience.

  1. Detect Updates in the Service Worker:

Modify the service worker to detect updates and communicate with the main app:

self.addEventListener('install', event => {
event.waitUntil(
caches.open('my-pwa-cache-v2').then(cache => {
return cache.addAll([
'/',
'/index.html',
'/styles.css',
'/script.js',
]);
})
);
});

self.addEventListener('activate', event => {
clients.claim();
});

self.addEventListener('fetch', event => {
event.respondWith(
caches.match(event.request).then(response => {
return response || fetch(event.request);
})
);
});

self.addEventListener('message', event => {
if (event.data === 'SKIP_WAITING') {
self.skipWaiting();
}
});
  1. Notify Users About the Update:

In your main application script, listen for the service worker update and notify users:

navigator.serviceWorker.addEventListener('controllerchange', () => {
window.location.reload();
});

navigator.serviceWorker.register('/service-worker.js').then(registration => {
if (registration.waiting) {
notifyUserAboutUpdate(registration.waiting);
}

registration.addEventListener('updatefound', () => {
const installingWorker = registration.installing;
installingWorker.addEventListener('statechange', () => {
if (installingWorker.state === 'installed' && navigator.serviceWorker.controller) {
notifyUserAboutUpdate(installingWorker);
}
});
});

let refreshing;
navigator.serviceWorker.addEventListener('controllerchange', () => {
if (refreshing) return;
window.location.reload();
refreshing = true;
});
});

function notifyUserAboutUpdate(worker) {
const updateNotification = document.createElement('div');
updateNotification.innerText = 'New version available. Refresh to update.';
updateNotification.style.position = 'fixed';
updateNotification.style.bottom = '0';
updateNotification.style.width = '100%';
updateNotification.style.backgroundColor = '#000';
updateNotification.style.color = '#fff';
updateNotification.style.textAlign = 'center';
updateNotification.style.padding = '1em';
document.body.appendChild(updateNotification);

updateNotification.addEventListener('click', () => {
worker.postMessage({ action: 'SKIP_WAITING' });
});
}

This approach allows users to be informed about new updates and provides them with an option to refresh the app at their convenience. This method ensures that updates do not disrupt the user experience while still keeping the app up-to-date.

Managing Cache Efficiently

Cache Versioning

Effective cache management is crucial to ensure that your PWA runs smoothly and efficiently. One of the best practices is to use cache versioning to prevent conflicts between old and new versions of cached files. By versioning your cache, you can ensure that users always get the latest version of your app without encountering issues from stale files.

To implement cache versioning, update the cache names in your service worker script:

const CACHE_NAME = 'my-pwa-cache-v2';
const URLS_TO_CACHE = [
'/',
'/index.html',
'/styles.css',
'/script.js',
];

self.addEventListener('install', event => {
event.waitUntil(
caches.open(CACHE_NAME).then(cache => {
return cache.addAll(URLS_TO_CACHE);
})
);
});

self.addEventListener('activate', event => {
const cacheWhitelist = [CACHE_NAME];
event.waitUntil(
caches.keys().then(cacheNames => {
return Promise.all(
cacheNames.map(cacheName => {
if (!cacheWhitelist.includes(cacheName)) {
return caches.delete(cacheName);
}
})
);
})
);
});

self.addEventListener('fetch', event => {
event.respondWith(
caches.match(event.request).then(response => {
return response || fetch(event.request);
})
);
});

By using a unique cache name for each version of your app, you can easily manage and delete old caches, ensuring that users always receive the latest updates without any conflicts.

Limiting Cache Storage

To prevent the cache from becoming too large and consuming excessive storage space on users’ devices, it’s important to implement strategies for limiting cache storage. This can be done by setting a maximum cache size and periodically purging old or unused files.

  1. Set a Maximum Cache Size:
const MAX_CACHE_SIZE = 50;

async function limitCacheSize(name, size) {
const cache = await caches.open(name);
const keys = await cache.keys();
if (keys.length > size) {
await cache.delete(keys[0]);
limitCacheSize(name, size);
}
}

self.addEventListener('install', event => {
event.waitUntil(
caches.open(CACHE_NAME).then(cache => {
return cache.addAll(URLS_TO_CACHE);
})
);
});

self.addEventListener('activate', event => {
event.waitUntil(
caches.keys().then(cacheNames => {
return Promise.all(
cacheNames.map(cacheName => {
if (cacheName !== CACHE_NAME) {
return caches.delete(cacheName);
}
})
);
})
);
});

self.addEventListener('fetch', event => {
event.respondWith(
caches.match(event.request).then(response => {
return response || fetch(event.request).then(fetchResponse => {
return caches.open(CACHE_NAME).then(cache => {
cache.put(event.request.url, fetchResponse.clone());
limitCacheSize(CACHE_NAME, MAX_CACHE_SIZE);
return fetchResponse;
});
});
})
);
});
  1. Purging Old or Unused Files:

In addition to limiting cache size, you can implement a strategy to purge old or unused files periodically. This can be done by setting expiration dates for cached files and removing them after a certain period.

const EXPIRATION_TIME = 7 * 24 * 60 * 60 * 1000; // 7 days in milliseconds

async function cleanCache() {
const cache = await caches.open(CACHE_NAME);
const keys = await cache.keys();
const now = Date.now();

for (const request of keys) {
const response = await cache.match(request);
const date = new Date(response.headers.get('date'));
if (now - date.getTime() > EXPIRATION_TIME) {
await cache.delete(request);
}
}
}

self.addEventListener('activate', event => {
event.waitUntil(cleanCache());
});

self.addEventListener('fetch', event => {
event.respondWith(
caches.match(event.request).then(response => {
if (response) {
return response;
}
return fetch(event.request).then(fetchResponse => {
return caches.open(CACHE_NAME).then(cache => {
cache.put(event.request.url, fetchResponse.clone());
return fetchResponse;
});
});
})
);
});

By implementing cache versioning and limiting cache storage, you can ensure that your PWA remains efficient and does not consume excessive resources on users’ devices.

Communicating Updates to Users

Clear Messaging

Clear communication with users about updates is essential to ensure they understand when and why updates are happening. This can be achieved through in-app notifications, banners, or modals that inform users about new features, bug fixes, or performance improvements.

Implementing clear messaging involves adding UI elements to your app that notify users when an update is available. Here’s an example of how to create a notification banner for updates:

  1. Create a Notification Banner:
<div id="update-banner" style="display: none; position: fixed; bottom: 0; width: 100%; background-color: #000; color: #fff; text-align: center; padding: 1em;">
New version available. <button onclick="refreshApp()">Refresh to update</button>
</div>
  1. Show the Banner When an Update is Available:
function notifyUserAboutUpdate(worker) {
const updateBanner = document.getElementById('update-banner');
updateBanner.style.display = 'block';

updateBanner.addEventListener('click', () => {
worker.postMessage({ action: 'SKIP_WAITING' });
});
}

navigator.serviceWorker.register('/service-worker.js').then(registration => {
if (registration.waiting) {
notifyUserAboutUpdate(registration.waiting);
}

registration.addEventListener('updatefound', () => {
const installingWorker = registration.installing;
installingWorker.addEventListener('statechange', () => {
if (installingWorker.state === 'installed' && navigator.serviceWorker.controller) {
notifyUserAboutUpdate(installingWorker);
}
});
});

let refreshing;
navigator.serviceWorker.addEventListener('controllerchange', () => {
if (refreshing) return;
window.location.reload();
refreshing = true;
});
});

function refreshApp() {
window.location.reload();
}

By implementing clear messaging, you ensure that users are informed about updates and can take action to refresh the app at their convenience, leading to a better user experience.

Handling User Data During Updates

When updating your PWA, it’s important to ensure that user data is preserved and not lost during the process. This involves implementing strategies to handle data synchronization and storage effectively.

  1. Synchronizing User Data:

Ensure that any user data entered while offline is synchronized with the server once the user is back online. Use Background Sync to handle data synchronization efficiently.

self.addEventListener('sync', event => {
if (event.tag === 'sync-user-data') {
event.waitUntil(syncUserData());
}
});

function syncUserData() {
// Logic to synchronize user data with the server
return fetch('/sync-endpoint', {
method: 'POST',
body: JSON.stringify({ data: 'user-data' }),
headers: {
'Content-Type': 'application/json'
}
});
}
  1. Using IndexedDB for Local Storage:

Use IndexedDB to store user data locally and ensure it is preserved during updates. IndexedDB provides a robust solution for storing complex data structures and can be used to cache user data during offline periods.

const dbPromise = idb.open('user-data-store', 1, upgradeDB => {
upgradeDB.createObjectStore('user-data', { keyPath: 'id' });
});

function saveUserData(data) {
return dbPromise.then(db => {
const tx = db.transaction('user-data', 'readwrite');
const store = tx.objectStore('user-data');
store.put(data);
return tx.complete;
});
}

function getUserData() {
return dbPromise.then(db => {
const tx = db.transaction('user-data', 'readonly');
const store = tx.objectStore('user-data');
return store.getAll();
});
}

By implementing these strategies, you ensure that user data is preserved and synchronized during updates, providing a seamless experience for users.

Monitoring and Analyzing Updates

Using Analytics to Track Update Adoption

One of the best practices for handling updates in PWAs is to monitor how users adopt these updates. This involves using analytics tools to track when users are receiving and installing updates. By analyzing this data, you can identify patterns and optimize the update process to ensure smoother transitions for your users.

To implement analytics tracking for updates, you can use tools like Google Analytics. Here’s how you can set up Google Analytics to track update events:

  1. Add Google Analytics to Your PWA:

First, include the Google Analytics script in your HTML:

<script async src="https://www.googletagmanager.com/gtag/js?id=YOUR_TRACKING_ID"></script>
<script>
window.dataLayer = window.dataLayer || [];
function gtag(){dataLayer.push(arguments);}
gtag('js', new Date());
gtag('config', 'YOUR_TRACKING_ID');
</script>
  1. Track Update Events in Your Service Worker:

Add event tracking for service worker updates:

self.addEventListener('install', event => {
event.waitUntil(
caches.open('my-pwa-cache-v2').then(cache => {
return cache.addAll([
'/',
'/index.html',
'/styles.css',
'/script.js',
]).then(() => {
gtag('event', 'service_worker_install', {
'event_category': 'Service Worker',
'event_label': 'Service Worker Installed'
});
});
})
);
});

self.addEventListener('activate', event => {
event.waitUntil(
caches.keys().then(cacheNames => {
return Promise.all(
cacheNames.filter(cacheName => {
return cacheName !== 'my-pwa-cache-v2';
}).map(cacheName => {
return caches.delete(cacheName);
})
).then(() => {
gtag('event', 'service_worker_activate', {
'event_category': 'Service Worker',
'event_label': 'Service Worker Activated'
});
});
})
);
});
  1. Track User Interaction with Update Notifications:

Track when users interact with update notifications:

function notifyUserAboutUpdate(worker) {
const updateBanner = document.getElementById('update-banner');
updateBanner.style.display = 'block';

updateBanner.addEventListener('click', () => {
worker.postMessage({ action: 'SKIP_WAITING' });
gtag('event', 'update_click', {
'event_category': 'Service Worker',
'event_label': 'User Clicked Update Notification'
});
});
}

By using analytics to track how users interact with updates, you can gain valuable insights into the effectiveness of your update strategies and make data-driven decisions to improve the update process.

Another crucial aspect of handling updates in PWAs is gathering user feedback

Gathering User Feedback

Another crucial aspect of handling updates in PWAs is gathering user feedback. Understanding how users feel about updates and any issues they encounter can help you refine your update process and improve user satisfaction.

  1. In-App Feedback Mechanism:

Implement an in-app feedback mechanism to collect user feedback directly. This can be a simple form or survey that users can fill out after an update.

<div id="feedback-form" style="display: none;">
<h3>We'd love to hear your feedback!</h3>
<textarea id="feedback-text" placeholder="Enter your feedback here"></textarea>
<button onclick="submitFeedback()">Submit</button>
</div>
  1. Show Feedback Form After Update:

Display the feedback form after an update:

function notifyUserAboutUpdate(worker) {
const updateBanner = document.getElementById('update-banner');
updateBanner.style.display = 'block';

updateBanner.addEventListener('click', () => {
worker.postMessage({ action: 'SKIP_WAITING' });
showFeedbackForm();
});
}

function showFeedbackForm() {
const feedbackForm = document.getElementById('feedback-form');
feedbackForm.style.display = 'block';
}

function submitFeedback() {
const feedbackText = document.getElementById('feedback-text').value;
gtag('event', 'user_feedback', {
'event_category': 'Feedback',
'event_label': 'User Feedback',
'event_value': feedbackText
});
alert('Thank you for your feedback!');
}
  1. Analyze Feedback Data:

Use the feedback data collected to identify common issues or suggestions and address them in future updates. This approach helps ensure that your updates meet user expectations and improve the overall experience.

Ensuring Smooth User Transitions

Graceful Degradation

Graceful degradation is a strategy that ensures your PWA continues to function, even if some of its features are not supported by the user’s browser or if an update fails to install properly. This approach is essential for providing a seamless experience and maintaining user trust.

  1. Fallback Content:

Provide fallback content for critical features that may not be supported:

if ('serviceWorker' in navigator) {
navigator.serviceWorker.register('/service-worker.js').then(registration => {
console.log('Service Worker registered with scope:', registration.scope);
}).catch(error => {
console.log('Service Worker registration failed:', error);
// Provide fallback content or notify the user
document.getElementById('offline-message').style.display = 'block';
});
} else {
// Provide fallback content for browsers that do not support service workers
document.getElementById('offline-message').style.display = 'block';
}
  1. Progressive Enhancement:

Use progressive enhancement to add features that enhance the user experience, while ensuring that the core functionality remains accessible:

if ('serviceWorker' in navigator && 'PushManager' in window) {
// Register service worker and enable push notifications
} else {
// Provide basic functionality without push notifications
console.log('Push notifications are not supported in this browser.');
}

By implementing graceful degradation, you can ensure that your PWA provides a reliable experience across different browsers and environments, even when updates fail or certain features are unsupported.

User Education

Educating users about how your PWA handles updates and what to expect can enhance their experience and reduce frustration. Provide clear and concise information about the update process, including any potential downtime or changes in functionality.

  1. Update Notes:

Include update notes in your PWA to inform users about new features, bug fixes, and other changes:

<div id="update-notes" style="display: none;">
<h3>What's New</h3>
<ul>
<li>New feature: Dark mode</li>
<li>Improved performance on slow networks</li>
<li>Fixed bugs in the user profile section</li>
</ul>
</div>
  1. Show Update Notes After an Update:

Display the update notes after an update is installed:

function notifyUserAboutUpdate(worker) {
const updateBanner = document.getElementById('update-banner');
updateBanner.style.display = 'block';

updateBanner.addEventListener('click', () => {
worker.postMessage({ action: 'SKIP_WAITING' });
showUpdateNotes();
});
}

function showUpdateNotes() {
const updateNotes = document.getElementById('update-notes');
updateNotes.style.display = 'block';
}
  1. Provide Help and Support:

Ensure that users have access to help and support resources if they encounter issues during updates:

<div id="support-section" style="display: none;">
<h3>Need Help?</h3>
<p>If you encounter any issues, please contact our support team at <a href="mailto:support@example.com">support@example.com</a>.</p>
</div>

By educating users about the update process and providing support, you can improve their experience and ensure that they feel confident using your PWA.

Advanced Techniques for PWA Updates

Using Background Sync for Seamless Data Updates

Background Sync is a powerful feature that allows your PWA to defer actions until the user has a stable internet connection. This is particularly useful for synchronizing data updates, ensuring that user actions taken offline are completed once the connection is restored.

  1. Registering Background Sync:

First, register a sync event in your service worker:

self.addEventListener('sync', event => {
if (event.tag === 'sync-user-data') {
event.waitUntil(syncUserData());
}
});

async function syncUserData() {
try {
const response = await fetch('/sync-endpoint', {
method: 'POST',
body: JSON.stringify({ data: 'user-data' }),
headers: {
'Content-Type': 'application/json'
}
});
if (!response.ok) {
throw new Error('Network response was not ok');
}
} catch (error) {
console.error('Sync failed:', error);
}
}
  1. Requesting Background Sync in Your Application:

In your main application script, request background sync when user data needs to be synchronized:

if ('serviceWorker' in navigator && 'SyncManager' in window) {
navigator.serviceWorker.ready.then(registration => {
return registration.sync.register('sync-user-data');
}).catch(err => {
console.error('Sync registration failed:', err);
});
} else {
// Fallback for browsers that don't support Background Sync
syncUserDataFallback();
}

function syncUserDataFallback() {
// Logic to sync data immediately or use another strategy
}

By implementing Background Sync, you ensure that user data updates are handled seamlessly, providing a better user experience even when connectivity is intermittent.

Managing Complex Update Scenarios

In some cases, updates may involve complex scenarios, such as migrating user data to a new format or handling significant changes in application structure. Managing these updates effectively requires careful planning and implementation.

  1. Data Migration:

When an update requires migrating user data to a new format, ensure that the migration process is handled smoothly to avoid data loss or corruption. Implement data migration logic in your service worker or application script:

self.addEventListener('activate', event => {
event.waitUntil(
(async () => {
const db = await openDatabase();
const transactions = await db.transaction(['user-data'], 'readwrite');
const store = transactions.objectStore('user-data');
const data = await store.getAll();
for (let item of data) {
// Perform data migration logic here
const migratedData = migrateDataFormat(item);
store.put(migratedData);
}
await transactions.complete;
})()
);
});

function migrateDataFormat(data) {
// Transform data to the new format
return {
...data,
newField: data.oldField
};
}
  1. Handling Breaking Changes:

If an update includes breaking changes that might disrupt the user experience, consider implementing a phased rollout or feature flags to gradually introduce changes and minimize impact:

self.addEventListener('activate', event => {
const breakingChangeVersion = 'v2';
const currentVersion = 'v1';

event.waitUntil(
(async () => {
if (currentVersion !== breakingChangeVersion) {
await showBreakingChangeNotification();
}
})()
);
});

async function showBreakingChangeNotification() {
const clients = await self.clients.matchAll();
clients.forEach(client => {
client.postMessage({ type: 'BREAKING_CHANGE', message: 'A major update is available. Please refresh your browser.' });
});
}

By carefully managing complex update scenarios, you can ensure a smooth transition for your users and maintain data integrity and application stability.

Leveraging Progressive Enhancement

Adding New Features Gradually

Progressive enhancement is a development strategy that ensures basic functionality is available to all users while enhancing the experience for those with modern browsers and capabilities. This approach is particularly useful for PWAs, as it allows you to introduce new features gradually without breaking the existing functionality.

  1. Feature Detection:

Use feature detection to determine if a browser supports a new feature before enabling it:

if ('serviceWorker' in navigator && 'PushManager' in window) {
// Enable push notifications
} else {
console.log('Push notifications are not supported in this browser.');
}
  1. Graceful Fallbacks:

Provide graceful fallbacks for features that may not be supported in older browsers:

if ('serviceWorker' in navigator) {
navigator.serviceWorker.register('/service-worker.js').then(registration => {
console.log('Service Worker registered with scope:', registration.scope);
}).catch(error => {
console.log('Service Worker registration failed:', error);
// Fallback logic here
document.getElementById('offline-message').style.display = 'block';
});
} else {
// Fallback content for browsers that do not support service workers
document.getElementById('offline-message').style.display = 'block';
}

By leveraging progressive enhancement, you can ensure that your PWA remains functional across a wide range of devices and browsers while gradually introducing new features to enhance the user experience.

Improving Accessibility

Ensuring that your PWA is accessible to all users, including those with disabilities, is an essential aspect of modern web development. Implementing best practices for accessibility can help you create a more inclusive application and improve the overall user experience.

  1. Use Semantic HTML:

Use semantic HTML elements to provide meaningful structure to your content, making it easier for assistive technologies to interpret and navigate:

<header>
<h1>Welcome to My PWA</h1>
<nav>
<ul>
<li><a href="/home">Home</a></li>
<li><a href="/about">About</a></li>
<li><a href="/contact">Contact</a></li>
</ul>
</nav>
</header>
  1. Provide Keyboard Navigation:

Ensure that all interactive elements are accessible via keyboard and provide visual focus indicators:

<button id="menu-toggle" aria-expanded="false">Menu</button>
<nav id="menu" hidden>
<ul>
<li><a href="/home">Home</a></li>
<li><a href="/about">About</a></li>
<li><a href="/contact">Contact</a></li>
</ul>
</nav>

<script>
document.getElementById('menu-toggle').addEventListener('click', function() {
const menu = document.getElementById('menu');
const expanded = this.getAttribute('aria-expanded') === 'true' || false;
this.setAttribute('aria-expanded', !expanded);
menu.hidden = expanded;
});
</script>
  1. Use ARIA Landmarks and Roles:

Implement ARIA (Accessible Rich Internet Applications) landmarks and roles to improve navigation and interaction for users of assistive technologies:

<main role="main">
<article role="article">
<header>
<h2>Article Title</h2>
</header>
<p>Article content goes here.</p>
</article>
</main>

By focusing on accessibility, you can create a PWA that is usable and enjoyable for a broader audience, ensuring that all users have a positive experience.

Conclusion

Handling updates in Progressive Web Apps (PWAs) is crucial for maintaining a seamless user experience while ensuring that your app remains current and secure. By implementing effective update strategies, such as automatic updates with service workers, manual update notifications, and clear communication with users, you can keep your PWA up-to-date without disrupting the user experience.

Efficient cache management through versioning and limiting storage, along with clear messaging and data preservation strategies, ensures that your PWA remains efficient and user-friendly. By following these best practices, you can provide a high-quality, reliable PWA that meets the demands of modern users.

If you have any questions or need further assistance with handling updates in your PWA, feel free to reach out. Thank you for reading, and best of luck with your Progressive Web App development journey!

Read Next: