<?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>Forem: Kevin Basset</title>
    <description>The latest articles on Forem by Kevin Basset (@progressier).</description>
    <link>https://forem.com/progressier</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.amazonaws.com%2Fuploads%2Fuser%2Fprofile_image%2F773519%2F00a47959-e1b5-48ba-8d53-22ca5633f1ed.jpg</url>
      <title>Forem: Kevin Basset</title>
      <link>https://forem.com/progressier</link>
    </image>
    <atom:link rel="self" type="application/rss+xml" href="https://forem.com/feed/progressier"/>
    <language>en</language>
    <item>
      <title>10 real-life PWA examples you can learn from in 2025</title>
      <dc:creator>Kevin Basset</dc:creator>
      <pubDate>Thu, 16 Jan 2025 03:11:29 +0000</pubDate>
      <link>https://forem.com/progressier/11-real-life-pwa-examples-you-can-learn-from-in-2025-3l8a</link>
      <guid>https://forem.com/progressier/11-real-life-pwa-examples-you-can-learn-from-in-2025-3l8a</guid>
      <description>&lt;h2&gt;
  
  
  1. Redmenta
&lt;/h2&gt;

&lt;p&gt;&lt;a href="https://redmenta.com/en" rel="noopener noreferrer"&gt;Redmenta&lt;/a&gt; is an AI-powered platform designed to help teachers and students create personalized learning paths from existing lessons. The software generates learning activities for students and enables teachers to track progress.&lt;/p&gt;

&lt;p&gt;As a &lt;em&gt;Progressive Web App&lt;/em&gt; (PWA), Redmenta can be installed on any device directly from the browser, without the need for app store downloads.&lt;/p&gt;

&lt;p&gt;&lt;a href="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Fvkw0w74a7qmmlqwij5s7.jpeg" class="article-body-image-wrapper"&gt;&lt;img src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Fvkw0w74a7qmmlqwij5s7.jpeg" alt="Redmenta Installation PWA Example" width="800" height="742"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;Perfect example of a great modern app: built with React, powered by AI, beautifully designed, mobile-responsive, and distributed via the open web.&lt;/p&gt;

&lt;h2&gt;
  
  
  2. Fodmapedia
&lt;/h2&gt;

&lt;p&gt;&lt;a href="https://fodmapedia.com/index-en" rel="noopener noreferrer"&gt;Fodmapedia&lt;/a&gt; is an app designed to assist individuals with &lt;a href="https://en.wikipedia.org/wiki/Irritable_bowel_syndrome" rel="noopener noreferrer"&gt;Irritable Bowel Syndrome&lt;/a&gt; in managing their symptoms through a low-FODMAP diet. This PWA example, developed without code with &lt;a href="https://bubble.io/" rel="noopener noreferrer"&gt;Bubble&lt;/a&gt;, helps users identify which items are suitable for their dietary needs. As this is a tool meant to be used daily, the option to install the website as an app is available right from the home page:&lt;/p&gt;

&lt;p&gt;&lt;a href="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fmiro.medium.com%2Fv2%2Fresize%3Afit%3A1400%2Fformat%3Awebp%2F0%2AAtdknH8qXqhadIBV" class="article-body-image-wrapper"&gt;&lt;img src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fmiro.medium.com%2Fv2%2Fresize%3Afit%3A1400%2Fformat%3Awebp%2F0%2AAtdknH8qXqhadIBV" alt="Fodmapedia Installation Banner" width="1038" height="1600"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;Tap on “&lt;em&gt;Install&lt;/em&gt;” and you’ll be guided through step-by-step instructions to add the PWA to your home screen. The instructions are smartly tailored to your specific device. With modern web technology, PWAs can now be &lt;a href="https://intercom.help/progressier/en/articles/5703021-on-what-devices-can-you-install-a-pwa" rel="noopener noreferrer"&gt;installed from almost anywhere&lt;/a&gt;.&lt;/p&gt;

&lt;p&gt;&lt;a href="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fmiro.medium.com%2Fv2%2Fresize%3Afit%3A1400%2Fformat%3Awebp%2F0%2AnG8uhqoqMxqZ4ZD-" class="article-body-image-wrapper"&gt;&lt;img src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fmiro.medium.com%2Fv2%2Fresize%3Afit%3A1400%2Fformat%3Awebp%2F0%2AnG8uhqoqMxqZ4ZD-" alt="Fodmapedia Detailed PWA Install Instructions" width="1133" height="1372"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;For users who may have missed the initial prompt, &lt;a href="https://fodmapedia.com/index-en" rel="noopener noreferrer"&gt;Fodmapedia&lt;/a&gt; offers a convenient installation link right in the header. This link takes you to a dedicated installation page, complete with app screenshots, detailed information, and even reviews.&lt;/p&gt;

&lt;p&gt;&lt;a href="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Fi3a8ae1ss1r8mc57si5z.jpeg" class="article-body-image-wrapper"&gt;&lt;img src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Fi3a8ae1ss1r8mc57si5z.jpeg" alt="Fodmapedia Desktop Installation Link" width="800" height="498"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;&lt;a href="https://fodmapedia.com/index-en" rel="noopener noreferrer"&gt;Fodmapedia&lt;/a&gt; is a great example of a PWA that demonstrates how solving a clear, tangible problem for users can be achieved without code and distributed outside of app stores.&lt;/p&gt;

&lt;p&gt;While the PWA format isn’t a universal solution for every app, it’s often the ideal choice for niche, informational apps that users rely on daily. Seamless integration, no commission fees on in-app payments, and effortless updates. It’s hard to beat that level of simplicity and efficiency.&lt;/p&gt;

&lt;h2&gt;
  
  
  3. Bingo em Casa
&lt;/h2&gt;

&lt;p&gt;&lt;a href="https://bingoemcasa.net/en" rel="noopener noreferrer"&gt;Bingo em Casa&lt;/a&gt; is a Brazilian sports betting and casino app that opted for the PWA format over native — a clear choice given that their app falls under prohibited categories on both &lt;a href="https://play.google/developer-content-policy/" rel="noopener noreferrer"&gt;Google Play&lt;/a&gt; and the &lt;a href="https://support.apple.com/guide/adguide/unacceptable-or-prohibited-content-guidelines-apd527d891a8/icloud" rel="noopener noreferrer"&gt;App Store&lt;/a&gt;.&lt;/p&gt;

&lt;p&gt;In fact, PWAs are an ideal solution for apps in sensitive industries such as gambling, adult content, crypto, cannabis, and health. With a PWA, you retain full control over distribution while still ensuring your app can live conveniently on users’ home screens, without having to ask them to download dodgy executable files.&lt;/p&gt;

&lt;p&gt;&lt;a href="https://bingoemcasa.net/en" rel="noopener noreferrer"&gt;Bingo em Casa&lt;/a&gt; encourages users to install its PWA directly from the login page. Installation is instant, and once the app is launched from your home screen, you’ll be prompted to enable push notifications. This flow is particularly effective — on iOS (starting with version 16.4), push notifications are only available in installed PWAs.&lt;/p&gt;

&lt;p&gt;&lt;a href="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2F0g8nchrbnmqwz27jqb1x.jpeg" class="article-body-image-wrapper"&gt;&lt;img src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2F0g8nchrbnmqwz27jqb1x.jpeg" alt="Bingo Em Case PWA Installation Flow" width="800" height="414"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;Unlike intrusive websites that present notification prompts immediately upon launch, Bingo em Casa thoughtfully waits until you’ve demonstrated meaningful engagement with the app before gracefully inviting you to opt in for notifications. Nicely done!&lt;/p&gt;

&lt;h2&gt;
  
  
  4. Nekodex
&lt;/h2&gt;

&lt;p&gt;&lt;a href="https://nekodex.org/" rel="noopener noreferrer"&gt;Nekodex&lt;/a&gt; is a crypto wallet app. Like the previous PWA example, it leverages the web format to sidestep app store restrictions while delivering a seamless user experience.&lt;/p&gt;

&lt;p&gt;With its beautiful animations and polished interface, this web app is virtually indistinguishable from a native app once installed. Some developers assume PWAs are less visually appealing or less powerful than native apps. &lt;a href="https://nekodex.org/" rel="noopener noreferrer"&gt;Nekodex&lt;/a&gt; proves otherwise, showcasing that yes, a PWA can look and feel super sleek.&lt;/p&gt;

&lt;p&gt;&lt;a href="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2F5jqqgem43j9a28d7nfi0.gif" class="article-body-image-wrapper"&gt;&lt;img src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2F5jqqgem43j9a28d7nfi0.gif" alt="Nekodex Animations" width="760" height="1556"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;Their cross-device installation approach is also very clever. Click “&lt;em&gt;Launch App&lt;/em&gt;” in the top-right corner of their website to display a code. Scan the code with your phone, and you’ll be guided to install the app directly. Smart!&lt;/p&gt;

&lt;p&gt;&lt;a href="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Fz0m5q2lqniwsrxge9y7b.jpeg" class="article-body-image-wrapper"&gt;&lt;img src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Fz0m5q2lqniwsrxge9y7b.jpeg" alt="Nekodex PWA Example Desktop to Mobile" width="800" height="490"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;h2&gt;
  
  
  5. Run247
&lt;/h2&gt;

&lt;p&gt;&lt;a href="https://run247.com/" rel="noopener noreferrer"&gt;Run247&lt;/a&gt; is a global community for trail and ultra running enthusiasts, offering news, events, and expert advice. Along with its sister app, &lt;a href="https://tri247.com/" rel="noopener noreferrer"&gt;Tri247&lt;/a&gt;, it has chosen the PWA format for distribution.&lt;/p&gt;

&lt;p&gt;Unlike the primarily mobile-focused PWAs mentioned earlier, &lt;a href="https://run247.com/" rel="noopener noreferrer"&gt;Run247&lt;/a&gt; caters equally to both desktop and mobile users. A particularly nice touch is the “&lt;em&gt;Add App&lt;/em&gt;” button located in the top-left corner of the site. With a single click, you can instantly install the app on your computer or phone, ensuring easy access across devices.&lt;/p&gt;

&lt;p&gt;&lt;a href="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Fzfiactl4y5s7st8l1j1v.jpeg" class="article-body-image-wrapper"&gt;&lt;img src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Fzfiactl4y5s7st8l1j1v.jpeg" alt="RUN247 PWA Example" width="800" height="695"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;PWA installation isn’t limited to mobile devices — it’s also available on macOS (via Safari, Chrome, Brave, or Edge), Windows (using Chrome, Brave, or Edge), and even ChromeOS!&lt;/p&gt;

&lt;p&gt;This PWA example handles the installation logic really thoroughly. For example, below is what happens if you click “&lt;em&gt;Add app&lt;/em&gt;” while using one of the &lt;a href="https://intercom.help/progressier/en/articles/5703021-on-what-devices-can-you-install-a-pwa" rel="noopener noreferrer"&gt;few browsers incompatible with PWA installation&lt;/a&gt; (mostly just Firefox on Desktop, really).&lt;/p&gt;

&lt;p&gt;&lt;a href="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Filp7g9pict2uz9lz86yy.jpeg" class="article-body-image-wrapper"&gt;&lt;img src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Filp7g9pict2uz9lz86yy.jpeg" alt="RUN247 PWA Installation on Firefox" width="800" height="695"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;h2&gt;
  
  
  6. Fou d’la bouffe
&lt;/h2&gt;

&lt;p&gt;&lt;a href="https://foubouffe.com/en/" rel="noopener noreferrer"&gt;Fou d’la Bouffe&lt;/a&gt; is a meal delivery service operating in Quebec and Ontario, a region where both French and English are spoken daily. To cater to this bilingual audience, the app is available in both languages. Upon loading the page, the app detects the user’s language and prompts users to install the PWA accordingly:&lt;/p&gt;

&lt;p&gt;&lt;a href="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fmiro.medium.com%2Fv2%2Fresize%3Afit%3A1400%2Fformat%3Awebp%2F0%2Axgtld7UrnfmCnAuL" class="article-body-image-wrapper"&gt;&lt;img src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fmiro.medium.com%2Fv2%2Fresize%3Afit%3A1400%2Fformat%3Awebp%2F0%2Axgtld7UrnfmCnAuL" alt="Foudbouffe PWA Example" width="1400" height="699"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;For a meal delivery service, securing a spot on customers’ home screens is of the utmost importance. And with advanced features like &lt;a href="https://progressier.com/pwa-capabilities/geolocation" rel="noopener noreferrer"&gt;geolocation&lt;/a&gt; and &lt;a href="https://progressier.com/pwa-capabilities/push-notifications" rel="noopener noreferrer"&gt;push notifications&lt;/a&gt;, this PWA example perfectly shows what the web is capable of.&lt;/p&gt;

&lt;h2&gt;
  
  
  7. Music League
&lt;/h2&gt;

&lt;p&gt;&lt;a href="https://musicleague.com/" rel="noopener noreferrer"&gt;Music League&lt;/a&gt; is a cross-platform music discovery game. Like many native apps, its landing page offers a concise overview of the app along with installation options. However, unlike most native apps, &lt;a href="https://musicleague.com/" rel="noopener noreferrer"&gt;Music League&lt;/a&gt; provides remarkable flexibility:&lt;/p&gt;

&lt;p&gt;&lt;a href="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Fl33xf1ntetinlvyjckgz.jpeg" class="article-body-image-wrapper"&gt;&lt;img src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Fl33xf1ntetinlvyjckgz.jpeg" alt="Music League Installation Links" width="800" height="453"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;There’s no mandatory installation — you can play the game directly in your browser or choose to install it on your phone if you prefer. This is a brilliant example of how to leverage the PWA format effectively. By eliminating the friction of navigating traditional app stores, the developers of &lt;a href="https://musicleague.com/" rel="noopener noreferrer"&gt;Music League&lt;/a&gt; increase adoption of their app.&lt;/p&gt;

&lt;p&gt;Most native apps &lt;em&gt;could&lt;/em&gt; provide a web option, but many avoid doing so because of the extra development effort. That’s where PWAs really shine — their biggest strength is having just one codebase that works the same way on every platform. Compare that to native apps, where an iOS app, an Android app, and a web app are basically three separate projects to build and maintain.&lt;/p&gt;

&lt;h2&gt;
  
  
  8. Sky Freebies
&lt;/h2&gt;

&lt;p&gt;&lt;a href="https://skyfreebies.co.uk/" rel="noopener noreferrer"&gt;Sky Freebies&lt;/a&gt; is a platform that helps you discover great deals on free items and samples from various retailers offering promotions.&lt;/p&gt;

&lt;p&gt;But as you can imagine, freebies don’t last forever! That’s why users need to check the site regularly to catch the latest offers. To make this even easier, they’ve added a handy installation widget that’s always available as you browse the site:&lt;/p&gt;

&lt;p&gt;&lt;a href="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2F7of8sdaogrp556v4cppq.jpeg" class="article-body-image-wrapper"&gt;&lt;img src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2F7of8sdaogrp556v4cppq.jpeg" alt="Sky Freebies PWA Installation" width="800" height="1286"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;Once you install the app and launch it, you’re greeted with the content itself. &lt;a href="https://skyfreebies.co.uk/" rel="noopener noreferrer"&gt;Sky Freebies&lt;/a&gt; detects the &lt;a href="https://intercom.help/progressier/en/articles/7999596-understanding-pwa-display-modes" rel="noopener noreferrer"&gt;display mode&lt;/a&gt; and tailors the user experience accordingly. The entire experience is a smooth, seamless journey — from visiting the homepage to launching the app directly from the home screen.&lt;/p&gt;

&lt;p&gt;&lt;a href="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Fzh1vegj4epi6by8zfan5.png" class="article-body-image-wrapper"&gt;&lt;img src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Fzh1vegj4epi6by8zfan5.png" alt="Sky Freebies Standalone PWA" width="800" height="1286"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;&lt;a href="https://skyfreebies.co.uk/" rel="noopener noreferrer"&gt;Sky Freebies&lt;/a&gt; also makes great use of &lt;a href="https://progressier.com/pwa-capabilities/push-notifications" rel="noopener noreferrer"&gt;push notifications&lt;/a&gt;, alerting you to new offers as soon as they’re added to the app. Of course, these notifications are entirely opt-in.&lt;/p&gt;

&lt;p&gt;&lt;a href="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Fcp3gk7q6y4zfepmfv8d4.jpeg" class="article-body-image-wrapper"&gt;&lt;img src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Fcp3gk7q6y4zfepmfv8d4.jpeg" alt="Sky Freebies Push Notifications" width="800" height="1286"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;Interestingly, while the app is fully functional on the web, it’s also available for download on &lt;a href="https://play.google.com/store/apps/details?id=uk.co.skyfreebies.app" rel="noopener noreferrer"&gt;Google Play&lt;/a&gt; or the &lt;a href="https://apps.microsoft.com/detail/9ph3v90881z8" rel="noopener noreferrer"&gt;Windows Store&lt;/a&gt;. While Apple restricts PWAs, Google and Microsoft are more open to web apps. So, choosing the PWA format doesn’t mean giving up on users who are accustomed to the traditional app stores.&lt;/p&gt;

&lt;h2&gt;
  
  
  9. The Bedford Guide
&lt;/h2&gt;

&lt;p&gt;PWA is an ideal format for local guides, and &lt;a href="https://thebedfordguide.co.uk/" rel="noopener noreferrer"&gt;The Bedford Guide&lt;/a&gt; is a perfect example. It helps you explore the best restaurants, bars, attractions, and other hotspots in Bedford, UK.&lt;/p&gt;

&lt;p&gt;Planning to visit Bedford? Simply install the website on your device for easy, on-the-go access whenever you need it.&lt;/p&gt;

&lt;p&gt;&lt;a href="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Fmbpkqaibwhapr10j6skv.jpeg" class="article-body-image-wrapper"&gt;&lt;img src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Fmbpkqaibwhapr10j6skv.jpeg" alt="Bedford PWA Installation" width="800" height="498"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;Developed with WordPress, &lt;a href="https://thebedfordguide.co.uk/" rel="noopener noreferrer"&gt;The Bedford Guide&lt;/a&gt; greets you with a pretty &lt;a href="https://progressier.com/pwa-icons-and-ios-splash-screen-generator" rel="noopener noreferrer"&gt;splash screen&lt;/a&gt; upon opening, just like a native app!&lt;/p&gt;

&lt;h2&gt;
  
  
  10. Photopea
&lt;/h2&gt;

&lt;p&gt;Last but certainly not least, I have to mention &lt;a href="https://photopea.com/" rel="noopener noreferrer"&gt;Photopea&lt;/a&gt;, which, in my humble opinion, is one of the greatest pieces of software ever built on the web. In a nutshell, it’s a free Photoshop alternative, fully developed using web technologies like HTML, CSS, and JavaScript.&lt;/p&gt;

&lt;p&gt;Like the other examples, you can install it as a PWA on your device. I have it in my Mac’s dock and use it almost daily. And it works so well that I honestly can’t tell it’s not a native app.&lt;/p&gt;

&lt;p&gt;&lt;a href="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2For91ev7fmdxaplpaz6ge.jpeg" class="article-body-image-wrapper"&gt;&lt;img src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2For91ev7fmdxaplpaz6ge.jpeg" alt="Photopea PWA Example" width="800" height="521"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;What’s truly remarkable about &lt;a href="https://photopea.com/" rel="noopener noreferrer"&gt;Photopea&lt;/a&gt; though is what it actually does. Enabling designers to tackle such complex tasks directly on the web was nothing short of genius.&lt;/p&gt;

&lt;p&gt;Skeptical about the potential of PWAs? Try Photopea — it might just change your perspective.&lt;/p&gt;

</description>
      <category>pwa</category>
      <category>webdev</category>
      <category>javascript</category>
    </item>
    <item>
      <title>Differences between background_color and theme_color in a PWA manifest</title>
      <dc:creator>Kevin Basset</dc:creator>
      <pubDate>Wed, 18 Oct 2023 03:36:52 +0000</pubDate>
      <link>https://forem.com/progressier/differences-between-backgroundcolor-and-themecolor-in-a-pwa-manifest-bj0</link>
      <guid>https://forem.com/progressier/differences-between-backgroundcolor-and-themecolor-in-a-pwa-manifest-bj0</guid>
      <description>&lt;p&gt;Every PWA has an app manifest. Within that document, you can define two key colors: the &lt;code&gt;background_color&lt;/code&gt; property and the &lt;code&gt;theme_color&lt;/code&gt; property. While the &lt;a href="https://www.w3.org/TR/appmanifest"&gt;official W3C specs&lt;/a&gt; provide definitions for these properties, their practical applications might not be immediately clear. In this article, we'll delve into where and how each of these properties is used in practice.&lt;/p&gt;

&lt;h2&gt;
  
  
  About &lt;code&gt;background_color&lt;/code&gt;
&lt;/h2&gt;

&lt;p&gt;Let's start with the &lt;code&gt;background_color&lt;/code&gt;. This property plays a crucial role in the visual presentation of your PWA on various platforms.&lt;/p&gt;

&lt;p&gt;Android generates a splash screen for your app based on the &lt;code&gt;background_color&lt;/code&gt; specified in the manifest. This creates a visually cohesive experience for users during the initial loading phase.&lt;/p&gt;

&lt;p&gt;&lt;a href="https://media.dev.to/cdn-cgi/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Fc3b8tytjgrwnqk7r3qwe.jpg" class="article-body-image-wrapper"&gt;&lt;img src="https://media.dev.to/cdn-cgi/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Fc3b8tytjgrwnqk7r3qwe.jpg" alt="Android splash screen based on background_color" width="800" height="450"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;If you use &lt;a href="https://progressier.com"&gt;Progressier&lt;/a&gt;, this color is also automatically used for the splash screens on iPhones and iPads.&lt;/p&gt;

&lt;p&gt;&lt;a href="https://media.dev.to/cdn-cgi/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2F1ubxwa3okgku0q5raoky.jpg" class="article-body-image-wrapper"&gt;&lt;img src="https://media.dev.to/cdn-cgi/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2F1ubxwa3okgku0q5raoky.jpg" alt="same background_color and theme_color on an iOS splash screen" width="800" height="450"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;This &lt;code&gt;background_color&lt;/code&gt; parameter also controls the color of the body of a desktop app during its loading phase.&lt;/p&gt;

&lt;p&gt;&lt;a href="https://media.dev.to/cdn-cgi/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2F1zlaz9qc6l5td6txox8q.jpg" class="article-body-image-wrapper"&gt;&lt;img src="https://media.dev.to/cdn-cgi/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2F1zlaz9qc6l5td6txox8q.jpg" alt="background_color vs theme_color in desktop PWA" width="800" height="450"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;h2&gt;
  
  
  About &lt;code&gt;theme_color&lt;/code&gt;
&lt;/h2&gt;

&lt;p&gt;Now, let's explore the &lt;code&gt;theme_color&lt;/code&gt;, which serves as the manifest equivalent of the &lt;code&gt;theme-color&lt;/code&gt; &lt;a href="https://developer.mozilla.org/en-US/docs/Web/HTML/Element/meta/name/theme-color"&gt;HTML meta tag&lt;/a&gt;. Its primary role is to control the color of the status bar on your PWA.&lt;/p&gt;

&lt;p&gt;&lt;a href="https://media.dev.to/cdn-cgi/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2F8ntxk8mwyr6npcvop3zz.jpg" class="article-body-image-wrapper"&gt;&lt;img src="https://media.dev.to/cdn-cgi/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2F8ntxk8mwyr6npcvop3zz.jpg" alt="Same meta tag theme-color and manifest theme_color" width="800" height="450"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;&lt;code&gt;theme_color&lt;/code&gt; is used while the app is loading. After the initial loading phase, browsers typically look to the &lt;code&gt;theme-color&lt;/code&gt; in your code. If both exist, the meta tag property takes precedence.&lt;/p&gt;

&lt;p&gt;&lt;a href="https://media.dev.to/cdn-cgi/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Famz4maiaob83maea3can.jpg" class="article-body-image-wrapper"&gt;&lt;img src="https://media.dev.to/cdn-cgi/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Famz4maiaob83maea3can.jpg" alt="Different meta tag theme-color and manifest theme_color" width="800" height="450"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;In essence, the &lt;code&gt;theme_color&lt;/code&gt; property in the manifest mainly influences the status bar's color during the splash screen phase. For post-loading color adjustments, it's more convenient to rely on the &lt;code&gt;theme-color&lt;/code&gt; meta tag, which can be easily modified on the client side.&lt;/p&gt;

&lt;p&gt;Note that the &lt;code&gt;theme-color&lt;/code&gt; meta tag can also affect the URL bar's appearance in light mode. In dark mode, most browsers automatically choose a dark shade for the URL bar and its surroundings.&lt;/p&gt;

&lt;h2&gt;
  
  
  My Recommendation
&lt;/h2&gt;

&lt;p&gt;To simplify your PWA development, consider using the same value for &lt;code&gt;theme_color&lt;/code&gt; as you do for &lt;code&gt;background_color&lt;/code&gt;. This approach ensures a seamless splash screen experience.&lt;/p&gt;

&lt;p&gt;This recommendation becomes particularly evident when you look at the splash screen of this &lt;a href="https://install.page/elonmusk"&gt;gag/demo app&lt;/a&gt; with different manifest &lt;code&gt;theme_color&lt;/code&gt; properties.&lt;/p&gt;

&lt;p&gt;&lt;a href="https://media.dev.to/cdn-cgi/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Flk82jl4h3k9oh99coc81.jpg" class="article-body-image-wrapper"&gt;&lt;img src="https://media.dev.to/cdn-cgi/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Flk82jl4h3k9oh99coc81.jpg" alt="Same theme_color and background_color" width="800" height="450"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;&lt;a href="https://media.dev.to/cdn-cgi/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2F8oul2kfw400desa28map.jpg" class="article-body-image-wrapper"&gt;&lt;img src="https://media.dev.to/cdn-cgi/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2F8oul2kfw400desa28map.jpg" alt="Different theme_color and background_color" width="800" height="450"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;If you want to change the color of the status bar after the splash screen has disappeared, simply modify the &lt;code&gt;theme-color&lt;/code&gt; meta tag in your app's HTML:&lt;/p&gt;

&lt;p&gt;Alternatively, with &lt;a href="https://progressier.com"&gt;Progressier&lt;/a&gt;, this is how it works:&lt;/p&gt;

&lt;p&gt;&lt;a href="https://media.dev.to/cdn-cgi/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Fxm040e33fbhxaqy1bbud.png" class="article-body-image-wrapper"&gt;&lt;img src="https://media.dev.to/cdn-cgi/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Fxm040e33fbhxaqy1bbud.png" alt="Screenshot of the Icons and Colors section of the Progressier dashboard" width="800" height="279"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;em&gt;Fill color&lt;/em&gt; controls &lt;code&gt;background_color&lt;/code&gt; and &lt;code&gt;theme_color&lt;/code&gt; in your app manifest (so you get a perfect splash screen).&lt;/li&gt;
&lt;li&gt;
&lt;em&gt;Status bar color&lt;/em&gt; controls the &lt;code&gt;theme-color&lt;/code&gt; meta tag after the splash screen has disappeared.&lt;/li&gt;
&lt;/ul&gt;

&lt;h2&gt;
  
  
  Conclusion
&lt;/h2&gt;

&lt;p&gt;In conclusion, understanding the roles of &lt;code&gt;background_color&lt;/code&gt; and &lt;code&gt;theme_color&lt;/code&gt; in your PWA manifest is crucial for ensuring a consistent and visually appealing user experience across platforms. &lt;/p&gt;

&lt;p&gt;While &lt;code&gt;background_color&lt;/code&gt; sets the stage during the loading phase, &lt;code&gt;theme_color&lt;/code&gt; is most useful for controlling the status bar color during that same phase. For post-loading adjustments, opt for the &lt;code&gt;theme-color&lt;/code&gt; meta tag, and consider using tools like &lt;a href="https://progressier.com"&gt;Progressier&lt;/a&gt; for effortless color management.&lt;/p&gt;

</description>
      <category>pwa</category>
      <category>pwabuilder</category>
      <category>webdev</category>
      <category>javascript</category>
    </item>
    <item>
      <title>How to fix iOS push subscriptions getting terminated after 3 notifications</title>
      <dc:creator>Kevin Basset</dc:creator>
      <pubDate>Fri, 30 Jun 2023 03:28:49 +0000</pubDate>
      <link>https://forem.com/progressier/how-to-fix-ios-push-subscriptions-being-terminated-after-3-notifications-39a7</link>
      <guid>https://forem.com/progressier/how-to-fix-ios-push-subscriptions-being-terminated-after-3-notifications-39a7</guid>
      <description>&lt;p&gt;With iOS 16.4, Apple has added push notifications to installed PWAs. However, you may have noticed that iOS push subscriptions appear to be automatically terminated after 3 push notifications.&lt;/p&gt;

&lt;p&gt;You send a notification, then a second, then a third. Everything works fine. However, when sending the fourth, everything breaks.&lt;/p&gt;

&lt;p&gt;Well, the good news is that it's fairly easy to fix! &lt;/p&gt;

&lt;p&gt;TL;DR: It's a problem with your push implementation.&lt;/p&gt;

&lt;p&gt;Of course, push subscriptions are never automatically terminated when implemented correctly. This would defeat the whole purpose of the feature!&lt;/p&gt;

&lt;p&gt;&lt;em&gt;(Side note: if you don't want to bother with these boring technical implementation details, go ahead and sign up for &lt;a href="https://progressier.com" rel="noopener noreferrer"&gt;Progressier&lt;/a&gt; instead).&lt;/em&gt;&lt;/p&gt;

&lt;h2&gt;
  
  
  How to fix push subscriptions being terminated on iOS Safari
&lt;/h2&gt;

&lt;p&gt;When you send a push notification to a subscribed user, the service worker for the matching domain fires a push event. The &lt;a href="https://developer.mozilla.org/en-US/docs/Web/API/PushEvent" rel="noopener noreferrer"&gt;push event handler&lt;/a&gt; in your service worker is responsible for showing the notification to the user.&lt;/p&gt;

&lt;p&gt;However, one very important detail that many implementations are missing is that &lt;strong&gt;your event handler must absolutely show the notification before the event itself terminates.&lt;/strong&gt;&lt;/p&gt;

&lt;p&gt;Notifications are displayed using &lt;a href="https://developer.mozilla.org/en-US/docs/Web/API/ServiceWorkerRegistration/showNotification" rel="noopener noreferrer"&gt;registration.showNotification()&lt;/a&gt;. This function returns a Promise that resolves once the notification has been shown to the user.&lt;/p&gt;

&lt;p&gt;Almost all cases of push subscriptions being terminated by Safari can be explained by push event handlers missing &lt;a href="https://developer.mozilla.org/en-US/docs/Web/API/ExtendableEvent/waitUntil" rel="noopener noreferrer"&gt;event.waitUntil()&lt;/a&gt;. This is a common issue. Even the &lt;a href="https://developer.mozilla.org/en-US/docs/Web/API/PushEvent" rel="noopener noreferrer"&gt;sample code on MDN&lt;/a&gt; contains that problem:&lt;/p&gt;

&lt;p&gt;&lt;a href="https://media.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Fsqfs356cnhd4hi66ncuf.png" class="article-body-image-wrapper"&gt;&lt;img src="https://media.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Fsqfs356cnhd4hi66ncuf.png" alt="Faulty push event handler on MDN"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;❌ Incorrect:&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;

self.addEventListener("push", function(e){
    self.registration.showNotification(e.data.title, e.data);
});


&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;

&lt;p&gt;If you use the sample code above, push notifications won't work properly in the long run on iOS because the promise returned by &lt;a href="https://developer.mozilla.org/en-US/docs/Web/API/ServiceWorkerRegistration/showNotification" rel="noopener noreferrer"&gt;registration.showNotification()&lt;/a&gt; will often resolve after the push event handler has finished.&lt;/p&gt;

&lt;p&gt;❌ Also incorrect:&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;

self.addEventListener("push", async function(e){    
  await self.registration.showNotification(e.data.title, e.data);
});


&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;

&lt;p&gt;Making the event listener asynchronous and awaiting the &lt;a href="https://developer.mozilla.org/en-US/docs/Web/API/ServiceWorkerRegistration/showNotification" rel="noopener noreferrer"&gt;registration.showNotification()&lt;/a&gt; promise to resolve looks like a very tempting solution. But you'll still run into the same issues. &lt;/p&gt;

&lt;p&gt;Here is why: it's not enough for your event &lt;em&gt;handler&lt;/em&gt; to wait for the work to be finished. You have to proactively tell the service worker what the event &lt;em&gt;itself&lt;/em&gt; requires to be considered finished. This is counterintuitive because you rarely (if ever?) see that sort of behavior anywhere in client-side or server-side logic.&lt;/p&gt;

&lt;p&gt;✅ Correct:&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;

self.addEventListener("push", function(e){   
  e.waitUntil(   
    self.registration.showNotification(e.data.title, e.data)   
  );
});


&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;

&lt;p&gt;This is a simplified example. In most cases, your code will do a few other things before invoking &lt;a href="https://developer.mozilla.org/en-US/docs/Web/API/ServiceWorkerRegistration/showNotification" rel="noopener noreferrer"&gt;registration.showNotification()&lt;/a&gt;. Just remember to wrap that last bit in &lt;a href="https://developer.mozilla.org/en-US/docs/Web/API/ExtendableEvent/waitUntil" rel="noopener noreferrer"&gt;event.waitUntil()&lt;/a&gt; and remember that &lt;a href="https://developer.mozilla.org/en-US/docs/Web/API/ExtendableEvent/waitUntil" rel="noopener noreferrer"&gt;event.waitUntil()&lt;/a&gt; expects a Promise (not a function) as an argument.&lt;/p&gt;

&lt;h2&gt;
  
  
  So why are push notifications working in other browsers if my implementation is faulty?
&lt;/h2&gt;

&lt;p&gt;This is because of a concept called &lt;em&gt;silent push&lt;/em&gt;. A &lt;em&gt;silent push&lt;/em&gt; occurs when the browser receives a notification but fails to display it to the user (or does so with a delay).&lt;/p&gt;

&lt;p&gt;When you omit &lt;a href="https://developer.mozilla.org/en-US/docs/Web/API/ExtendableEvent/waitUntil" rel="noopener noreferrer"&gt;event.waitUntil()&lt;/a&gt;, Safari considers the notification to be a &lt;em&gt;silent push&lt;/em&gt; because the event terminates before the notification is displayed (even if it is shown shortly thereafter).&lt;/p&gt;

&lt;p&gt;Silent push has always been frowned upon across the board. In fact, when you &lt;a href="https://developer.mozilla.org/en-US/docs/Web/API/PushManager/subscribe" rel="noopener noreferrer"&gt;register a push subscription&lt;/a&gt;, all browsers require you to configure it as &lt;a href="https://developer.mozilla.org/en-US/docs/Web/API/PushSubscriptionOptions/userVisibleOnly" rel="noopener noreferrer"&gt;userVisibleOnly&lt;/a&gt;, meaning you guarantee that you will only send notifications that you will actually show to users.&lt;/p&gt;

&lt;p&gt;This is a security measure to prevent apps to do work in the background without users knowing about it. If you accidentally do 3 of those on Safari, the push subscription is canceled. Any further attempts would result in errors.&lt;/p&gt;

&lt;p&gt;Other browsers have a different way of handling this. For example, Chrome does not invalidate push subscriptions, so as long as you call &lt;a href="https://developer.mozilla.org/en-US/docs/Web/API/ServiceWorkerRegistration/showNotification" rel="noopener noreferrer"&gt;registration.showNotification()&lt;/a&gt; before the event handler terminates, you'll likely bypass Chrome's &lt;em&gt;anti-silent-push&lt;/em&gt; security measure (which is to show a default &lt;em&gt;&lt;a href="https://web.dev/push-notifications-handling-messages/" rel="noopener noreferrer"&gt;This site has been updated in the background&lt;/a&gt;&lt;/em&gt; notification). &lt;/p&gt;

&lt;p&gt;&lt;a href="https://media.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Fi5yeqxuyds6fnea6xuft.png" class="article-body-image-wrapper"&gt;&lt;img src="https://media.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Fi5yeqxuyds6fnea6xuft.png" alt="Screenshot from web.dev about the default Chrome push notification for silent push"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;What seemed to work in many apps for years was always wrong. Many developers are claiming Apple released a bad push implementation — but this is not what's happening here. Apple's implementation is arguably closer to the &lt;a href="https://www.w3.org/TR/push-api/" rel="noopener noreferrer"&gt;W3C specs&lt;/a&gt; than Google's. If your setup worked in other browsers, it's essentially because... you were able to avoid the issue by chance. In any case, implement &lt;a href="https://developer.mozilla.org/en-US/docs/Web/API/ExtendableEvent/waitUntil" rel="noopener noreferrer"&gt;event.waitUntil()&lt;/a&gt; properly now. You'll eliminate timing randomness and make your implementation more future-proof.&lt;/p&gt;

&lt;p&gt;That's it for me! Hopefully, now you know how to get around push subscriptions being canceled after 3 push notifications. If you have any questions, feel free to contact me &lt;a href="https://progressier.com" rel="noopener noreferrer"&gt;here&lt;/a&gt;.&lt;/p&gt;

</description>
      <category>webdev</category>
      <category>pwa</category>
      <category>javascript</category>
    </item>
    <item>
      <title>How to translate a PWA app manifest</title>
      <dc:creator>Kevin Basset</dc:creator>
      <pubDate>Tue, 11 Oct 2022 03:38:22 +0000</pubDate>
      <link>https://forem.com/progressier/how-to-translate-a-pwa-app-manifest-2e7i</link>
      <guid>https://forem.com/progressier/how-to-translate-a-pwa-app-manifest-2e7i</guid>
      <description>&lt;p&gt;As of October 2022, there's no native way to localize properties of the &lt;a href="https://developer.mozilla.org/en-US/docs/Web/Manifest" rel="noopener noreferrer"&gt;web app manifest&lt;/a&gt; in multiple languages. Although there's been an &lt;a href="https://github.com/w3c/manifest/issues/676" rel="noopener noreferrer"&gt;open request for that since 2018&lt;/a&gt;, so far there's no definitive way to achieve this with a conventional static app manifest file.&lt;/p&gt;

&lt;p&gt;In this article, I'll tell you about the three methods you can use to localize your PWA manifest, including the one I'm using to provide the localization functionality at &lt;a href="https://progressier.com?ref=devtolocalizemanifest" rel="noopener noreferrer"&gt;Progressier&lt;/a&gt;.&lt;/p&gt;

&lt;h2&gt;
  
  
  Method 1: Static app manifests
&lt;/h2&gt;

&lt;p&gt;If your app has properly scoped language versions hosted at their own URLs, you can simply create different app manifest for each of them.&lt;/p&gt;

&lt;p&gt;Say your English app is at &lt;code&gt;https://example.com/en/&lt;/code&gt; and your French app is at &lt;code&gt;https://example.com/fr/&lt;/code&gt;, then you can simply host the manifests at &lt;code&gt;https://example.com/en/manifest.json&lt;/code&gt; and &lt;code&gt;https://example.com/fr/manifest.json&lt;/code&gt; respectively.&lt;/p&gt;

&lt;p&gt;This would work similarly if your English app is at &lt;code&gt;example.com&lt;/code&gt; and your French app is at &lt;code&gt;fr.example.com&lt;/code&gt;. You'd then host your manifests at &lt;code&gt;example.com/manifest.json&lt;/code&gt; and &lt;code&gt;fr.example.com/manifest.json&lt;/code&gt;&lt;/p&gt;

&lt;p&gt;The first drawback of that method is that browsers consider each entity unique PWAs. So when a user installs the English version from &lt;code&gt;example.com&lt;/code&gt;, they'll be prompted to install the French version when they access &lt;code&gt;fr.example.com&lt;/code&gt;.&lt;/p&gt;

&lt;p&gt;Not great.&lt;/p&gt;

&lt;p&gt;Counterintuitively, in this specific case, having &lt;a href="https://web.dev/building-multiple-pwas-on-the-same-domain/#overlappingnested-paths" rel="noopener noreferrer"&gt;overlapping scopes&lt;/a&gt; would actually be the better option. &lt;/p&gt;

&lt;p&gt;The second issue is that this method violates &lt;a href="https://en.wikipedia.org/wiki/Don%27t_repeat_yourself" rel="noopener noreferrer"&gt;Don't Repeat Yourself&lt;/a&gt; principles. In the long run, this is harder to manage and tends to lead to errors. It also doesn't scale well — if tomorrow your app is available in 20 different languages, do you really want to modify 20 different manifest files whenever you update your logo?&lt;/p&gt;

&lt;h2&gt;
  
  
  Method 2: Server-side dynamic app manifest generation
&lt;/h2&gt;

&lt;p&gt;In general, one PWA = one app manifest. A PWA typically isn't updatable after installation if the URL of the manifest changes. So you should strive to keep the URL of the manifest unchanged as much as possible — even if it might be possible to do things a bit differently now thanks to the new &lt;a href="https://developer.chrome.com/blog/pwa-manifest-id/" rel="noopener noreferrer"&gt;id&lt;/a&gt; property.&lt;/p&gt;

&lt;p&gt;A more scalable approach is to generate the manifest server-side instead of simply storing it as a static asset. This is the method I use at &lt;a href="https://progressier.com?ref=devtolocalizemanifest" rel="noopener noreferrer"&gt;Progressier&lt;/a&gt;.&lt;/p&gt;

&lt;p&gt;Browsers automatically add a &lt;a href="https://developer.mozilla.org/en-US/docs/Web/HTTP/Headers/Accept-Language" rel="noopener noreferrer"&gt;Accept-Language&lt;/a&gt; header to every &lt;code&gt;fetch&lt;/code&gt; request. The Accept-Language is determined based on the user's preferred language set in their browser. If you're using Chrome, you can define your own list of preferred languages at &lt;code&gt;chrome://settings/languages&lt;/code&gt;.&lt;/p&gt;

&lt;p&gt;Here is how to translate your manifest server-side:&lt;/p&gt;

&lt;ol&gt;
&lt;li&gt;Link to the manifest as usual in your client-side code. Simply add &lt;code&gt;&amp;lt;link rel="manifest" href="https://example.com/manifest.json"&amp;gt;&lt;/code&gt; between the &lt;code&gt;&amp;lt;head&amp;gt;&lt;/code&gt; and &lt;code&gt;&amp;lt;/head&amp;gt;&lt;/code&gt; tags in your HTML document.&lt;/li&gt;
&lt;li&gt;In your server-side code, add a GET route for &lt;code&gt;/manifest.json&lt;/code&gt; &lt;/li&gt;
&lt;li&gt;Get the &lt;code&gt;Accept-Language&lt;/code&gt; header&lt;/li&gt;
&lt;li&gt;See if you have localized content that matches that header. If so, use it in your app manifest. Else, fall back to your default language.&lt;/li&gt;
&lt;/ol&gt;

&lt;p&gt;Here is a complete example using Node/Express:&lt;/p&gt;


&lt;div class="ltag_gist-liquid-tag"&gt;
  
&lt;/div&gt;


&lt;p&gt;This is a user-level setting, and since the manifest is automatically fetched by the browser based on the meta tag you add to the &lt;code&gt;&amp;lt;head&amp;gt;&lt;/code&gt; of the HTML document, it's not possible to directly overwrite the &lt;code&gt;Accept-Language&lt;/code&gt; header. &lt;/p&gt;

&lt;p&gt;In other words, it won't work hand in hand with a dropdown menu that would allow users to select the language themselves — it'll just still use their browser language.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;&amp;lt;link rel="manifest" href="https://example.com/manifest.json"&amp;gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;The above does not offer any way to overwrite headers added to the &lt;code&gt;fetch&lt;/code&gt; request for that specific asset.&lt;/p&gt;

&lt;p&gt;There's one slightly convoluted way to achieve this: by intercepting the &lt;code&gt;fetch&lt;/code&gt; request in the service worker, and then manually changing the &lt;code&gt;Accept-Language&lt;/code&gt;. Since you'll need the service worker to be registered for that to work, the very first fetch request for the manifest might not return a JSON file in the right language. Plus, you'll have to make sure it uses the right caching strategy.&lt;/p&gt;

&lt;h2&gt;
  
  
  Method 3: Progressier
&lt;/h2&gt;

&lt;p&gt;If you're using &lt;a href="https://progressier.com?ref=devtolocalizemanifest" rel="noopener noreferrer"&gt;Progressier&lt;/a&gt;, localizing your app manifest is fairly easy. Since the platform is entirely no-code, all you have to do is click on the &lt;em&gt;Localize&lt;/em&gt; button next to any of your manifest's parameters, choose a language, and translate. The rest is automatically taken care of for you.&lt;/p&gt;

&lt;p&gt;&lt;a href="https://media.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Fw97s582ls4u5acc9bdio.gif" class="article-body-image-wrapper"&gt;&lt;img src="https://media.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Fw97s582ls4u5acc9bdio.gif" alt="Localizing an app manifest in Progressier"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;In Progressier, you can localize any of the following manifest parameters: &lt;code&gt;name&lt;/code&gt;, &lt;code&gt;short_name&lt;/code&gt;, &lt;code&gt;description&lt;/code&gt;, &lt;code&gt;start_url&lt;/code&gt;, &lt;code&gt;screenshots&lt;/code&gt; in any of these 108 languages: &lt;em&gt;English, Afrikaans, Albanian, Amharic, Arabic, Armenian, Azerbaijani, Basque, Belarusian, Bengali, Bosnian, Bulgarian, Catalan, Cebuano, Chinese, Corsican, Croatian, Czech, Danish, Dutch, Esperanto, Estonian, Finnish, French, Frisian, Galician, Georgian, German, Greek, Gujarati, Haitian Creole, Hausa, Hawaiian, Hebrew, Hindi, Hmong, Hungarian, Icelandic, Igbo, Indonesian, Irish, Italian, Japanese, Javanese, Kannada, Kazakh, Khmer, Kinyarwanda, Korean, Kurdish, Kyrgyz, Lao, Latin, Latvian, Lithuanian, Luxembourgish, Macedonian, Malagasy, Malay, Malayalam, Maltese, Maori, Marathi, Mongolian, Myanmar (Burmese), Nepali, Norwegian, Nyanja (Chichewa), Odia (Oriya), Pashto, Persian, Polish, Portuguese, Punjabi, Romanian, Russian, Samoan, Scots Gaelic, Serbian, Sesotho, Shona, Sindhi, Sinhala (Sinhalese), Slovak, Slovenian, Somali, Spanish, Sundanese, Swahili, Swedish, Tagalog (Filipino), Tajik, Tamil, Tatar, Telugu, Thai, Turkish, Turkmen, Ukrainian, Urdu, Uyghur, Uzbek, Vietnamese, Welsh, Xhosa, Yiddish, Yoruba, Zulu&lt;/em&gt;.&lt;/p&gt;

&lt;p&gt;It also works pretty well with the &lt;code&gt;screenshots&lt;/code&gt; property. Progressier comes with a complete solution for &lt;a href="https://progressier.com/pwa-screenshots-generator" rel="noopener noreferrer"&gt;designing and managing screenshots&lt;/a&gt;.&lt;/p&gt;

&lt;p&gt;Questions? Feedback? Please let me know!&lt;/p&gt;

</description>
      <category>javascript</category>
      <category>webdev</category>
      <category>pwa</category>
      <category>tutorial</category>
    </item>
    <item>
      <title>Why haven’t PWAs killed native Apps Yet?</title>
      <dc:creator>Kevin Basset</dc:creator>
      <pubDate>Thu, 08 Sep 2022 02:04:38 +0000</pubDate>
      <link>https://forem.com/progressier/why-havent-pwas-killed-native-apps-yet-378o</link>
      <guid>https://forem.com/progressier/why-havent-pwas-killed-native-apps-yet-378o</guid>
      <description>&lt;blockquote&gt;
&lt;p&gt;&lt;em&gt;Kevin Basset is the Founder at &lt;a href="https://progressier.com?ref=d09" rel="noopener noreferrer"&gt;Progressier&lt;/a&gt;, a software toolkit used by 5,000+ apps to emancipate themselves from the app stores.&lt;/em&gt;&lt;/p&gt;
&lt;/blockquote&gt;

&lt;p&gt;On paper, PWAs are the perfect alternative to native apps: only one code base to manage, instant updates that require no approval, and no commissions to pay on in-app purchases. What’s not to like?&lt;/p&gt;

&lt;p&gt;Although &lt;em&gt;Progressive Web Apps&lt;/em&gt; have gone a long way since their inception, they’re not quite at the point where they can act as a perfect replacement for native apps.&lt;/p&gt;

&lt;p&gt;So… what is still missing in 2022? Why haven’t they become the default format for apps just yet?&lt;/p&gt;

&lt;h2&gt;
  
  
  The PWA identity problem
&lt;/h2&gt;

&lt;p&gt;I’ve written about &lt;a href="https://javascript.plainenglish.io/i-solved-the-biggest-problem-with-pwas-2996d02c5728" rel="noopener noreferrer"&gt;this specific topic in more details&lt;/a&gt;, but PWAs still suffer from their reputation of being second-rate apps — or worse, in some cases, of not being apps &lt;em&gt;at all&lt;/em&gt;.&lt;/p&gt;

&lt;p&gt;In 2022, the default behavior is still to go look for apps on either Google Play or the App Store. Funnily enough, installing an app directly from a website is both faster and more convenient. But users still aren’t accustomed to this without dedicated &lt;a href="https://progressier.com/features/in-app-pwa-promotion?ref=m09" rel="noopener noreferrer"&gt;prompts and promotion elements&lt;/a&gt;.&lt;/p&gt;

&lt;p&gt;At the core of the problem is the question of trust. Downloading an app from a third party means that the third party in question (Google or Apple) attests that the app is safe to download.&lt;/p&gt;

&lt;p&gt;But here is the thing: PWAs don’t need Google and Apple‘s approval… because they’re already guaranteed to be secure — by design. A PWA can’t read your phone’s contacts, send SMS on your behalf, or access any of your phone’s features that could expose your private information.&lt;/p&gt;

&lt;p&gt;The value proposition of an “app store” is twofold: &lt;em&gt;logistics&lt;/em&gt; (installing your app on the user’s device) and &lt;em&gt;promotion&lt;/em&gt; (getting more people to discover your app). Arguably, app stores no longer do such a great job at the latter. And the logistic of installing PWAs is embedded within the browser itself.&lt;/p&gt;

&lt;p&gt;In 2022, the whole &lt;em&gt;app store paradigm&lt;/em&gt; is redundant.&lt;/p&gt;

&lt;h2&gt;
  
  
  iOS push notifications
&lt;/h2&gt;

&lt;p&gt;&lt;iframe class="tweet-embed" id="tweet-1533893843799748610-682" src="https://platform.twitter.com/embed/Tweet.html?id=1533893843799748610"&gt;
&lt;/iframe&gt;

  // Detect dark theme
  var iframe = document.getElementById('tweet-1533893843799748610-682');
  if (document.body.className.includes('dark-theme')) {
    iframe.src = "https://platform.twitter.com/embed/Tweet.html?id=1533893843799748610&amp;amp;theme=dark"
  }



&lt;/p&gt;

&lt;p&gt;After many years of seemingly hopeless wait, Apple has &lt;a href="https://kevinbasset.medium.com/everything-you-need-to-know-about-web-push-on-ios-macos-5a4b3b715b24" rel="noopener noreferrer"&gt;finally announced&lt;/a&gt; that push notifications will be coming to iOS in 2023. This is great news. Until now, you could send notifications to your Android/Windows/macOS users, but not to your iOS users.&lt;/p&gt;

&lt;p&gt;For many developers, this meant that it wasn’t possible to rely on push notifications completely to deliver important information to their users. Web push notifications were a nice added bonus — not a crucial part of a product‘s workflows.&lt;/p&gt;

&lt;p&gt;Provided that Apple implements web push notifications the right way (as in, following the &lt;a href="https://www.w3.org/TR/push-api/" rel="noopener noreferrer"&gt;W3 specs&lt;/a&gt;), this is about to change. You’ll be able to notify users on both Android and iOS with very little effort — and without having to list your apps on Google Play and the App Store.&lt;/p&gt;

&lt;p&gt;That being said, web developers have abused the &lt;a href="https://developer.mozilla.org/en-US/docs/Web/API/Push_API" rel="noopener noreferrer"&gt;Web Push API&lt;/a&gt; so much (e.g. news websites requesting access on your first visit) that people have grown to detest these prompts. As a result, in some cases, Chrome (and other browsers) &lt;a href="https://www.searchenginejournal.com/chrome-push-notification-blocker/342593/" rel="noopener noreferrer"&gt;auto-blocks push notification requests&lt;/a&gt; — making it harder for developers with legitimate use cases to request access to this feature.&lt;/p&gt;

&lt;p&gt;One of the top items on my personal wishlist would be for PWAs to be granted a &lt;em&gt;higher authority&lt;/em&gt; than a regular website, once installed (but not quite as much as a native app). Getting people to install your PWA is evidence that they sort of trust it — they haven’t just stumbled upon your site.&lt;/p&gt;

&lt;p&gt;A few examples of what &lt;em&gt;higher authority&lt;/em&gt; could translate into:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;Installed PWAs could automatically be granted access to the &lt;a href="https://developer.mozilla.org/en-US/docs/Web/API/Push_API" rel="noopener noreferrer"&gt;Push API&lt;/a&gt;.&lt;/li&gt;
&lt;li&gt;Restrict access to the &lt;a href="https://developer.mozilla.org/en-US/docs/Web/API/Push_API" rel="noopener noreferrer"&gt;Push API&lt;/a&gt; to installed PWAs. Regular websites wouldn’t be allowed to request access at all. Goodbye BS spam.&lt;/li&gt;
&lt;li&gt;Bundle requests to multiple browser APIs within the &lt;a href="https://web.dev/learn/pwa/installation-prompt/" rel="noopener noreferrer"&gt;installation prompt&lt;/a&gt;. For example, when installed, a PWA could request to get automatic access to the &lt;a href="https://developer.mozilla.org/en-US/docs/Web/API/Push_API" rel="noopener noreferrer"&gt;Push API&lt;/a&gt;, the &lt;a href="https://developer.mozilla.org/en-US/docs/Web/API/Geolocation_API" rel="noopener noreferrer"&gt;Geolocation API&lt;/a&gt;, or the &lt;a href="https://webaudioapi.com/samples/microphone/" rel="noopener noreferrer"&gt;Microphone API&lt;/a&gt; — with toggles for users to granularly allow or disallow each individually.&lt;/li&gt;
&lt;li&gt;Or more modestly, Chrome could simply not auto-block push notifications when requested from within a PWA.&lt;/li&gt;
&lt;/ul&gt;

&lt;h2&gt;
  
  
  Native install prompt on iOS
&lt;/h2&gt;

&lt;p&gt;&lt;a href="https://media.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Fic5ixpfby6kkm19d4kd0.png" class="article-body-image-wrapper"&gt;&lt;img src="https://media.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Fic5ixpfby6kkm19d4kd0.png" alt="Installing a PWA on iOS requires showing users custom instructions"&gt;&lt;/a&gt;&lt;br&gt;
Installing a PWA on iOS requires showing users custom instructions&lt;br&gt;
Installing an app on iOS currently requires opening the Share panel and clicking on the &lt;em&gt;Add to Home Screen&lt;/em&gt; button. This mostly does the job, but it isn’t as straightforward as installing a native iOS app.&lt;/p&gt;

&lt;p&gt;If Safari implemented support for the &lt;a href="https://developer.mozilla.org/en-US/docs/Web/API/BeforeInstallPromptEvent" rel="noopener noreferrer"&gt;beforeInstallPrompt event&lt;/a&gt;, the experience would be streamlined. Or at the very least, Apple could change the wording of Add to Home Screen to Install the app — Android has done exactly that a few years ago.&lt;/p&gt;

&lt;p&gt;Although the current flow is an okay-ish workaround, it does have some unintended consequences that are detrimental to the experience for both users and developers.&lt;/p&gt;

&lt;p&gt;For example, it’s &lt;a href="https://www.reddit.com/r/PWA/comments/wnvijd/why_does_sfsafariviewcontroller_have_no_add_to/" rel="noopener noreferrer"&gt;impossible to distinguish&lt;/a&gt; between actual Safari (which has an &lt;em&gt;Add to Home Screen&lt;/em&gt; button) and a &lt;a href="https://developer.apple.com/documentation/safariservices/sfsafariviewcontroller" rel="noopener noreferrer"&gt;SFSafariViewController&lt;/a&gt; view (which does not). For the record, the latter is used by a number of in-app browsers, e.g. the Twitter iOS app.&lt;/p&gt;

&lt;p&gt;As a result, you have no choice but show your custom instructions… even when the option isn’t there. A tad confusing for users who may not be into that sort of technical nitpicking, aka 99.99% of users.&lt;/p&gt;

&lt;p&gt;Similarly, I’m looking forward to the day when PWA developers no longer need to &lt;a href="https://progressier.com/pwa-icons-and-ios-splash-screen-generator" rel="noopener noreferrer"&gt;generate 25+ individual splash screen image files&lt;/a&gt; just to support every iPhone and iPad.&lt;/p&gt;
&lt;h2&gt;
  
  
  Better post-install manifest updates
&lt;/h2&gt;

&lt;p&gt;PWAs would also become a lot more competitive if the developer was able to update the key details of the manifest (&lt;code&gt;icon&lt;/code&gt;, &lt;code&gt;name&lt;/code&gt;, splash screens, etc) after installation.&lt;/p&gt;

&lt;p&gt;Google has a &lt;a href="https://web.dev/manifest-updates/" rel="noopener noreferrer"&gt;whole article about this&lt;/a&gt;, but I’ll give you the TL;DR: none of the properties you &lt;em&gt;actually&lt;/em&gt; want to change can be changed. So once it’s installed, you can’t update how it looks on the user’s home screen.&lt;/p&gt;

&lt;p&gt;Or at least, that was the case until very recently.&lt;/p&gt;

&lt;p&gt;Luckily, there’s been some interesting developments in that area. Now, desktop Chrome supports changing the &lt;code&gt;name&lt;/code&gt; of an app after it’s been installed. It even comes with a nice — albeit confusing — anti-phishing prompt so users can approve the change or uninstall the app.&lt;br&gt;
&lt;iframe class="tweet-embed" id="tweet-1558484746564509698-609" src="https://platform.twitter.com/embed/Tweet.html?id=1558484746564509698"&gt;
&lt;/iframe&gt;

  // Detect dark theme
  var iframe = document.getElementById('tweet-1558484746564509698-609');
  if (document.body.className.includes('dark-theme')) {
    iframe.src = "https://platform.twitter.com/embed/Tweet.html?id=1558484746564509698&amp;amp;theme=dark"
  }



&lt;br&gt;
&lt;iframe class="tweet-embed" id="tweet-1558432138067955713-135" src="https://platform.twitter.com/embed/Tweet.html?id=1558432138067955713"&gt;
&lt;/iframe&gt;

  // Detect dark theme
  var iframe = document.getElementById('tweet-1558432138067955713-135');
  if (document.body.className.includes('dark-theme')) {
    iframe.src = "https://platform.twitter.com/embed/Tweet.html?id=1558432138067955713&amp;amp;theme=dark"
  }



&lt;/p&gt;

&lt;h2&gt;
  
  
  Better scope management
&lt;/h2&gt;

&lt;p&gt;If there’s one area where PWAs really shine is &lt;a href="https://progressier.com/features/programmatic-pwa-creation?ref=m09" rel="noopener noreferrer"&gt;programmatic app creation&lt;/a&gt;.&lt;/p&gt;

&lt;p&gt;One of my customers at &lt;a href="https://progressier.com?ref=d09" rel="noopener noreferrer"&gt;Progressier&lt;/a&gt; is a company that commercializes software for photographers. Photographers use that software to create unique wedding galleries for their own customers.&lt;/p&gt;

&lt;p&gt;With &lt;a href="https://progressier.com?ref=d09" rel="noopener noreferrer"&gt;Progressier&lt;/a&gt;, each of these galleries is a unique app with its own name (the name of the newlyweds) and icon (a photo of the couple). With 10,000s of such galleries, this would simply be impossible to manage any other way.&lt;/p&gt;

&lt;p&gt;It doesn’t come without a few bottlenecks and gotchas, however.&lt;/p&gt;

&lt;p&gt;While it’s generally preferable to host each PWA on &lt;a href="https://web.dev/building-multiple-pwas-on-the-same-domain/" rel="noopener noreferrer"&gt;their own subdomain&lt;/a&gt; (e.g. &lt;code&gt;pwa1.example.com&lt;/code&gt; and &lt;code&gt;pwa2.example.com&lt;/code&gt;), it’s often not possible. In this case, the second best option is to host each in its own directory (e.g. &lt;code&gt;example.com/pwa1/&lt;/code&gt; and &lt;code&gt;example.com/pwa2/&lt;/code&gt;).&lt;/p&gt;

&lt;p&gt;Managing scopes is very counter-intuitive because of what I like to call the &lt;em&gt;trailing slash problem&lt;/em&gt;. In a nutshell, &lt;code&gt;example.com/pwa1/&lt;/code&gt; is a valid scope while example.com/pwa1 (notice the missing trailing slash) is not.&lt;/p&gt;

&lt;p&gt;If you use the latter, browsers will instead consider the scope to be &lt;code&gt;example.com/&lt;/code&gt; (the root domain) — the problem is that there is no error message or warning. It just silently fails.&lt;/p&gt;

&lt;p&gt;I wish browsers would be a tiny bit smarter and would automatically handle trailing slashes in the scope field. They could just auto-correct &lt;code&gt;example.com/pwa1&lt;/code&gt; to &lt;code&gt;example.com/pwa1/&lt;/code&gt;. I don’t see any use cases where the former is not an error where the dev actually meant the latter.&lt;/p&gt;

&lt;p&gt;Scopes could also be improved on iOS. Opening a link that's within the scope of a PWA from a third-party app on Android opens the installed PWA. However, on iOS, it opens Safari instead.&lt;/p&gt;

&lt;h2&gt;
  
  
  Desktop screenshots
&lt;/h2&gt;

&lt;p&gt;&lt;a href="https://media.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Fmjg4n83a4451j8yhoahf.jpeg" class="article-body-image-wrapper"&gt;&lt;img src="https://media.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Fmjg4n83a4451j8yhoahf.jpeg" alt="Old install prompt vs new install prompt"&gt;&lt;/a&gt;&lt;br&gt;
&lt;a href="https://developer.chrome.com/blog/richer-pwa-installation/" rel="noopener noreferrer"&gt;The Richer Installation&lt;/a&gt; UI has definitely helped bridge the gap between native apps and PWAs. By including screenshots in the installation prompt, developers can show their app in action — and it looks and feels like a standard app store interface.&lt;/p&gt;

&lt;p&gt;At &lt;a href="https://progressier.com?ref=d09" rel="noopener noreferrer"&gt;Progressier&lt;/a&gt;, I went a bit further by providing a &lt;a href="https://progressier.com/pwa-screenshots-generator" rel="noopener noreferrer"&gt;free tool for designing these screenshots&lt;/a&gt; in addition to integrating the tool into the product itself. So if you’re a &lt;a href="https://progressier.com?ref=d09" rel="noopener noreferrer"&gt;Progressier&lt;/a&gt; customer, you can design, manage, edit, localize, and upload your screenshots within one interface. Kinda like Photoshop meets Google Play.&lt;/p&gt;

&lt;p&gt;The &lt;code&gt;screenshots&lt;/code&gt; property of the app manifest doesn’t currently do anything on desktop — but there is an active proposal to also display these screenshots on desktop Chrome. You can already test this by using the &lt;code&gt;--enable-features=DesktopPWAsDetailedInstallDialog&lt;/code&gt; command line flag.&lt;br&gt;
&lt;iframe class="tweet-embed" id="tweet-1547467531060883456-639" src="https://platform.twitter.com/embed/Tweet.html?id=1547467531060883456"&gt;
&lt;/iframe&gt;

  // Detect dark theme
  var iframe = document.getElementById('tweet-1547467531060883456-639');
  if (document.body.className.includes('dark-theme')) {
    iframe.src = "https://platform.twitter.com/embed/Tweet.html?id=1547467531060883456&amp;amp;theme=dark"
  }



&lt;/p&gt;

&lt;h2&gt;
  
  
  What about native features?
&lt;/h2&gt;

&lt;p&gt;Should PWAs ever be able to &lt;a href="https://developer.android.com/training/contacts-provider/retrieve-names" rel="noopener noreferrer"&gt;access your contacts&lt;/a&gt;? &lt;a href="https://developer.android.com/training/contacts-provider/retrieve-names" rel="noopener noreferrer"&gt;view your calendar&lt;/a&gt;? &lt;a href="https://developer.android.com/reference/android/telephony/SmsManager" rel="noopener noreferrer"&gt;send SMS/MMS&lt;/a&gt;? &lt;a href="https://developer.android.com/training/scheduling/alarms" rel="noopener noreferrer"&gt;set alarms&lt;/a&gt;? Personally, I would argue that they shouldn’t — ever.&lt;/p&gt;

&lt;p&gt;The fact that PWAs are limited in their scope is the very reason why they are secure. Bypassing the browser limitations would set a dangerous precedent that would create an artificial need for a third-party approver (i.e. an app store) or a service like &lt;a href="https://progressier.com/alternative-to/pwa-builder" rel="noopener noreferrer"&gt;PWABuilder&lt;/a&gt;, hence invalidating the whole concept.&lt;/p&gt;

&lt;p&gt;Of course, some apps do need access to these features. And for those, native is — and hopefully always will be — the only way to go.&lt;/p&gt;

&lt;p&gt;But for the vast majority of apps that do not need access to these features, PWAs can not only be a great alternative —they’re increasingly becoming the best option.&lt;/p&gt;

&lt;p&gt;When you consider the fact that more apps get built with &lt;a href="https://bubble.io/" rel="noopener noreferrer"&gt;Bubble&lt;/a&gt;, &lt;a href="https://softr.io/" rel="noopener noreferrer"&gt;Softr&lt;/a&gt;, and other no-code platforms every day, it looks like this is the direction we’re headed.&lt;/p&gt;

&lt;p&gt;And personally, I think this is awesome news for developers, users, and the web, as a whole.&lt;/p&gt;

&lt;blockquote&gt;
&lt;p&gt;&lt;em&gt;Kevin Basset is the Founder at &lt;a href="https://progressier.com?ref=d09" rel="noopener noreferrer"&gt;Progressier&lt;/a&gt;, a software toolkit used by 5,000+ apps to emancipate themselves from the app stores.&lt;/em&gt;&lt;/p&gt;
&lt;/blockquote&gt;

</description>
      <category>webdev</category>
      <category>pwa</category>
      <category>javascript</category>
      <category>html</category>
    </item>
    <item>
      <title>Why a PWA app icon shouldn't have a purpose set to 'any maskable'</title>
      <dc:creator>Kevin Basset</dc:creator>
      <pubDate>Thu, 06 Jan 2022 04:54:38 +0000</pubDate>
      <link>https://forem.com/progressier/why-a-pwa-app-icon-shouldnt-have-a-purpose-set-to-any-maskable-4c78</link>
      <guid>https://forem.com/progressier/why-a-pwa-app-icon-shouldnt-have-a-purpose-set-to-any-maskable-4c78</guid>
      <description>&lt;p&gt;A Progressive Web App requires a &lt;a href="https://progressier.com/features/no-code-web-app-manifest" rel="noopener noreferrer"&gt;web app manifest&lt;/a&gt;, a JSON file that contains the details of your app (things like the name and icons of your PWA). &lt;/p&gt;

&lt;p&gt;An app manifest must have an &lt;a href="https://developer.mozilla.org/en-US/docs/Web/Manifest/icons" rel="noopener noreferrer"&gt;array of icons&lt;/a&gt;. Each of these icons has a purpose set to either &lt;code&gt;monochrome&lt;/code&gt;, &lt;code&gt;any&lt;/code&gt; or&lt;code&gt;maskable&lt;/code&gt; or a combination of these three values.&lt;/p&gt;

&lt;h2&gt;
  
  
  Why is "any maskable" discouraged by Chrome?
&lt;/h2&gt;

&lt;p&gt;Lately, I've noticed quite a few PWA app manifests displaying a warning that until mid-2021 didn't exist (those created with &lt;a href="https://progressier.com" rel="noopener noreferrer"&gt;Progressier&lt;/a&gt; always work great though!): &lt;/p&gt;

&lt;blockquote&gt;
&lt;p&gt;&lt;em&gt;Declaring an icon with purpose "any maskable" is discouraged. It is likely to look incorrect on some platforms due to too much or too little padding.&lt;/em&gt;&lt;/p&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;
&lt;/code&gt;&lt;/pre&gt;
&lt;/blockquote&gt;

&lt;p&gt;{&lt;br&gt;
  …&lt;br&gt;
  "icons": [&lt;br&gt;
    {&lt;br&gt;
      "src": "icon2.png",&lt;br&gt;
      "sizes": "512x512",&lt;br&gt;
      "type": "image/png",&lt;br&gt;
      "purpose": "any maskable" // &amp;lt;-- triggers the warning&lt;br&gt;
    }&lt;br&gt;
  ],&lt;br&gt;
  …&lt;br&gt;
}&lt;/p&gt;



&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;
In this article, I'll show you exactly what Chrome means with that warning, but first I must explain what `maskable icons` are and why they exist.

## What are `maskable` icons?
Until a few years ago, app icons on Android could have a transparent background and use any shape they wanted. And that frankly made your home screen quite messy. Look at that Samsung Galaxy Note 4 from 2014:

![Home screen of a Samsung Galaxy Note 4 from 2014](https://dev-to-uploads.s3.amazonaws.com/uploads/articles/tg3zekcw3ih33dni1fs3.jpg)

Since then, smartphone vendors — probably in an effort to emulate iOS — standardized app icons. On a given home screen, every app icon has the same size and shape.

![Samsung Galaxy Note S21 vs Google Pixel 6 have different icon styles](https://dev-to-uploads.s3.amazonaws.com/uploads/articles/ntlr11okhwkitahxeb4y.jpg)
_Samsung Galaxy Note S21+ (Square icons with rounded corners) vs Google Pixel 6 (circular icons)_

_Thankfully_, the W3C folks came up with the [maskable icon](https://www.w3.org/TR/appmanifest/#icon-masks) feature. A maskable icon is one that has a safe zone that is cropped so that the icon can be displayed within a variety of shapes and occupy the entire space available.

(I say "_thankfully_" because just imagine the mess it would have become if developers had to provide a different icon for each possible shape.)

## Difference between `any` and `maskable`

Here is how a Android home screen renders the same PNG image with the purpose set to `maskable` (left) and set to `any` (right)
![Purpose set to maskable vs any](https://dev-to-uploads.s3.amazonaws.com/uploads/articles/7hoimei1exupph3jyg85.jpg)

## So why no `any maskable` icons?
In short, it's because it's very difficult to create an app icon that can be used as-is and as a mask. You'll almost always either have too much or too little padding.

## The perfect solution
Use [Progressier](https://progressier.com) to have icons perfectly sized automatically. Else to do it manually, create two 512x512 icons, one with the purpose `maskable`, and the other with the purpose `any`. Make your `maskable` icon fit the entirety of the area. And give your `any` icon 40 pixels of padding and 20% of border-radius (so that it looks great on all macOS versions).

&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;{&lt;br&gt;
  …&lt;br&gt;
  "icons": [&lt;br&gt;
    {&lt;br&gt;
      "src": "icon2.png",&lt;br&gt;
      "sizes": "512x512",&lt;br&gt;
      "type": "image/png",&lt;br&gt;
      "purpose": "any"&lt;br&gt;
    },&lt;br&gt;
    {&lt;br&gt;
      "src": "icon2.png",&lt;br&gt;
      "sizes": "512x512",&lt;br&gt;
      "type": "image/png",&lt;br&gt;
      "purpose": "maskable"&lt;br&gt;
    },&lt;br&gt;
  ],&lt;br&gt;
  …&lt;br&gt;
}&lt;/p&gt;



&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;
## Where do my PWA icons show?
Different platforms will use either of the two icons.
- On **Android**, the **home screen** and **install prompt** use the icon with the purpose `maskable`
- The **splash screen** generated by recent **Android phones** (post 2020) uses the icon with the purpose `maskable`
- Older or low-end **Android phones** may have a **splash screen** generated from the icon with the purpose `any` instead.
- **Chrome OS** uses the icon with the purpose `maskable`
- **Windows** uses the icon with the purpose `any` with all browsers, as it does not enforce any particular icon shapes
- **macOS Ventura** or lower uses the icon with the purpose `any`
- **macOS Sonoma** or higher uses the icon with the purpose `maskable`
- **iOS** requires an extra set of icons set with the `apple-touch-icon` (home screen icon)  and `apple-touch-startup-image` meta tags (splash screen)
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;

</description>
      <category>pwa</category>
      <category>javascript</category>
      <category>webdev</category>
      <category>beginners</category>
    </item>
    <item>
      <title>Create a PWA from scratch with Vanilla JS</title>
      <dc:creator>Kevin Basset</dc:creator>
      <pubDate>Thu, 30 Dec 2021 08:23:17 +0000</pubDate>
      <link>https://forem.com/progressier/create-a-pwa-from-scratch-with-vanilla-js-36cm</link>
      <guid>https://forem.com/progressier/create-a-pwa-from-scratch-with-vanilla-js-36cm</guid>
      <description>&lt;p&gt;When COVID started spreading in China in January 2020, I took it upon myself to build &lt;a href="https://progressier.com/?force=true#story" rel="noopener noreferrer"&gt;The Coronavirus App&lt;/a&gt;. That simple PWA has since been used by more than 15 million individual users. &lt;/p&gt;

&lt;p&gt;The problems I encountered while building that app then inspired me to create &lt;a href="https://progressier.com" rel="noopener noreferrer"&gt;Progressier&lt;/a&gt;, a SaaS platform that makes it possible to add the entire block of functionality we call "Progressive Web App" to any domain without having to write any code.&lt;/p&gt;

&lt;p&gt;In this article, I'll share some tips and tricks about what I've learnt &lt;a href="https://progressier.com/quickstart/building-a-pwa-with-vanilla-javascript" rel="noopener noreferrer"&gt;developing PWAs from scratch with Vanilla JS&lt;/a&gt;.&lt;/p&gt;

&lt;h2&gt;
  
  
  Not so easy... but worth it
&lt;/h2&gt;

&lt;p&gt;A PWA offers a sexy promise: only one code base to manage and your app works on all platforms. In practice, that means that you have to make that one code base do a lot more things than if you developed several standalone native apps.&lt;/p&gt;

&lt;p&gt;The pros probably outweigh the cons most of the time. But it isn't black or white. Developers blindly promoting PWAs as a replacement for native apps simply haven't spent enough time developing PWAs. Native apps also have their place.&lt;/p&gt;

&lt;h2&gt;
  
  
  Don't go desktop-first... nor mobile-first
&lt;/h2&gt;

&lt;p&gt;Until a decade ago, most websites were first and foremost optimized for desktop use. Then came mobile phones, and we started making websites responsive. &lt;/p&gt;

&lt;p&gt;With PWAs, you can't think desktop-first. And you probably shouldn't think mobile-first either. A PWA created with Vanilla JS has to look and feel like a native app on mobile. But it also has to look like a proper desktop app on desktop. &lt;/p&gt;

&lt;p&gt;These are two completely different UI paradigms — it's not just about the size of elements. For example, mobile UIs tend to display only one interactive element at a time while desktop UIs usually have many of them displayed simultaneously. Here are some concrete examples:&lt;/p&gt;

&lt;p&gt;&lt;a href="https://media.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Fqf5fmvsailvp43t0lcvy.jpg" class="article-body-image-wrapper"&gt;&lt;img src="https://media.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Fqf5fmvsailvp43t0lcvy.jpg" alt="A standard dropdown menu becomes a drawer with an overlay"&gt;&lt;/a&gt;&lt;br&gt;
&lt;em&gt;A standard dropdown menu on desktop becomes a bottom drawer with an overlay on mobile&lt;/em&gt;&lt;br&gt;
&lt;a href="https://media.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Fojt81xqnylgq5s6uuzp8.jpg" class="article-body-image-wrapper"&gt;&lt;img src="https://media.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Fojt81xqnylgq5s6uuzp8.jpg" alt="An accordion item becomes a full screen menu"&gt;&lt;/a&gt;&lt;br&gt;
&lt;em&gt;Desktop accordion items become standalone full screen components on mobile&lt;/em&gt;&lt;br&gt;
&lt;a href="https://media.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Fqb2k17i7ihqxbzldikhz.jpg" class="article-body-image-wrapper"&gt;&lt;img src="https://media.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Fqb2k17i7ihqxbzldikhz.jpg" alt="A side panel searchable list becomes a search bar"&gt;&lt;/a&gt;&lt;br&gt;
&lt;em&gt;A side panel searchable list on desktop becomes a mobile search bar&lt;/em&gt;&lt;/p&gt;

&lt;p&gt;As a rule of thumb, create one single HTML element and use CSS to style it. Often that will mean changing the element &lt;code&gt;position&lt;/code&gt; from &lt;code&gt;relative&lt;/code&gt; to &lt;code&gt;fixed&lt;/code&gt; or &lt;code&gt;absolute&lt;/code&gt;. &lt;/p&gt;

&lt;p&gt;Sometimes, it isn't really possible. When developing a PWA with Vanilla JS, it's not uncommon to run into &lt;code&gt;z-index&lt;/code&gt; issues. An element has to appear on top of a sibling of their parent container when it's open on mobile, while the parent has to appear underneath the sibling when it's not. When that happens, you'll have to implement a few tricks to modify the &lt;code&gt;z-index&lt;/code&gt; of the parent dynamically with JavaScript.&lt;/p&gt;

&lt;p&gt;When you design components for a PWA, start with the functionality, then design their mobile and desktop versions concurrently. And only then figure out what the right HTML structure should be.&lt;/p&gt;
&lt;h2&gt;
  
  
  Abstract away
&lt;/h2&gt;

&lt;p&gt;Proponents of frameworks like React or Vue sometimes argue that Vanilla JS is too verbose and inefficient. They also argue that if you solve that by abstracting the browser APIs, you're essentially creating your own framework (aka "reinventing the wheel"). Below are two code snippets that do the exact same thing:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;let onClick = function(e){console.log(e.target.value);}
let parentNode = document.querySelector('#parent')

//PURE VANILLA JAVASCRIPT
let input = document.createElement('input');
input.classList.add('cool');
input.addEventListener('click', onClick);
parentNode.appendChild(input);

//MY HOMEMADE VANILLA JAVASCRIPT ABSTRACTION
utils.node('input', 'cool', {parent: parentNode, click: onClick});
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;The benefits of my homemade abstraction are pretty obvious. 61 characters instead of 139 means you save time typing code and the browser saves time getting it from your server. Each HTML element being one line, your code also becomes easier to read and organize.&lt;/p&gt;

&lt;p&gt;Yet both functions are semantically identical. They both create a &lt;a href="https://developer.mozilla.org/en-US/docs/Web/API/Node" rel="noopener noreferrer"&gt;DOM Node&lt;/a&gt;, add a class and an event listener to it, and append it to the DOM. Vanilla JS is about using the default browser APIs. Frameworks, on the other hand, are opiniated. They introduce their own preconceptions about how things should be done. Think how  &lt;a href="https://reactjs.org/docs/introducing-jsx.html" rel="noopener noreferrer"&gt;React uses JXS to create a hybrid HTML/JavaScript declarative style&lt;/a&gt; for example. Frameworks create different paradigms. Shortening the syntax of Vanilla JS doesn't fall into that category, in my humble opinion.&lt;/p&gt;

&lt;p&gt;Ten years ago, jQuery was popular because it made things more consistent across browsers. Nowadays, most browser APIs are so well-built and documented that you probably don't need anything else. Another good example is &lt;a href="https://momentjs.com/" rel="noopener noreferrer"&gt;Moment.js&lt;/a&gt; — dealing with dates and time used to be a pain in the ass. Now with the &lt;a href="https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/Date" rel="noopener noreferrer"&gt;Date()&lt;/a&gt; interface, it's easy. And it's available in JavaScript.&lt;/p&gt;

&lt;p&gt;So use Vanilla JS but build your own abstractions. Make it as simple as possible to write, understand, organize and modify your code. You're definitely going to need to be organized to make a PWA created from scratch with Vanilla JS work on all the platforms it has to support.&lt;/p&gt;

&lt;h2&gt;
  
  
  Design reusable components
&lt;/h2&gt;

&lt;p&gt;Without a framework structuring your code for you, you have to be extra careful not turning your project into spaghetti code. What has worked well for me is create semantic silos / components. One component is a JavaScript function that contains everything that pertains to that component: the HTML, DOM nodes, event listeners, CSS, logic are all in the same place. &lt;/p&gt;

&lt;p&gt;In addition to making your code more readable, it also makes it easier to iterate on your product. When you have to delete a component, you just remove the entire block of code. You can be sure it won't break anything else, and your code base never contains leftovers from previous iterations.&lt;/p&gt;

&lt;p&gt;You don't really need to use frameworks to build components. In fact, you'd be surprised how easy it really is with Vanilla JS. And you don't even need the fancy &lt;a href="https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Statements/class" rel="noopener noreferrer"&gt;class&lt;/a&gt; declaration either. Below is my basic structure for creating components.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;function ComponentName(parent){
  let that = this;
  this.parent = parent;
  this.componentId = "component-id";
  this.styling = `
    .`+that.componentId+`{position:relative;}
    .`+that.componentId+`-inner{width:100%;}
  `;

  this.fetch = async function(){
    //do whatever async operations I need to do
    //e.g. query data of the component from DB
  }
  this.stylize = function(){
   //create a &amp;lt;style&amp;gt; node and set its id to that.componentId
   //set the content of the &amp;lt;style&amp;gt; node to that.styling
   //then simply append it to the DOM 
   //(or overwrite the content of an existing &amp;lt;style&amp;gt; with the same ID)
  }
  this.create = function(){
   //create a new container for the component
   //append it to that.parent
   //store it as that.element
  }
  this.render = function(){
   //empty that.element and recreate its inner content
  }
  this.init = async function(){
    await that.fetch();
    that.stylize();
    that.create();
    that.render();
  }
  //this component self-initializes when created
  this.init();
}
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;I can't recall ever needing more than that. It really does everything you might want it to do: create custom CSS, create a container, lets you wait for data from the network if needed, lets you re-render the container when data changes. &lt;/p&gt;

&lt;p&gt;And because you're using Vanilla JS, you can structure each component slightly differently. For example, a different component may not be self-initializing like the one above. Or it may be invoked with completely different parameters, say data from another component.&lt;/p&gt;

&lt;p&gt;Of course, there's probably a thousand other ways you could go about this that would work equally well.&lt;/p&gt;

&lt;h2&gt;
  
  
  Use the right tools for the job
&lt;/h2&gt;

&lt;p&gt;&lt;strong&gt;Recommendation 1: Use libraries&lt;/strong&gt;&lt;br&gt;
Using Vanilla JS doesn't mean you can't utilize libraries that abstract some complex things that aren't available straight out of the box in the browser. The key is that these libraries should work in their own silo and not force you to rewrite your entire app around them. For example, don't build your own maps — use &lt;a href="https://leafletjs.com/" rel="noopener noreferrer"&gt;Leaflet.js&lt;/a&gt;. And don't build your own charts — instead, use &lt;a href="https://www.chartjs.org/" rel="noopener noreferrer"&gt;Charts.js&lt;/a&gt;.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Recommendation 2: &lt;a href="https://www.browserstack.com/" rel="noopener noreferrer"&gt;BrowserStack&lt;/a&gt;&lt;/strong&gt;&lt;br&gt;
You're going to have to spend a substantial amount of time testing your PWA in different browsers. Personally I'm a big fan of Browserstack. The platform allows you to test any web app or website on every browser/OS combination imaginable — even older devices. And these are real devices, not emulated devices. The $39 I pay every month for it are well worth it. I'm not affiliated with Browserstack in any way by the way. &lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Recommendation 3: &lt;a href="https://developer.mozilla.org/en-US/" rel="noopener noreferrer"&gt;MDN Web Docs&lt;/a&gt;&lt;/strong&gt;&lt;br&gt;
I absolutely love MDN. It's essentially a list of all APIs available to you in JavaScript. Each come with extremely comprehensive documentation. As a Vanilla JS developer, if you only allowed me access to a single site on the entire World Wide Web, that's the one I would pick.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Recommendation 4: &lt;a href="https://progressier.com" rel="noopener noreferrer"&gt;Progressier&lt;/a&gt;&lt;/strong&gt;&lt;br&gt;
I built it so I'm obviously biased but I can't end the article without recommending Progressier. When building The Coronavirus App, it hit me that the entire block of functionality we call PWA (caching strategies, installability, push notifications) was unnecessarily annoying to implement. So I decided to build an abstraction for it — which you can add to your own app with a single line of code.&lt;/p&gt;

&lt;h2&gt;
  
  
  That's all folks!
&lt;/h2&gt;

&lt;p&gt;Have you built a PWA with Vanilla Javascript yet? How was your experience with it? What other tools would you recommend using?&lt;/p&gt;

&lt;p&gt;If this article helped you in any way, please consider leaving a comment below 🙏&lt;/p&gt;

</description>
      <category>javascript</category>
      <category>webdev</category>
      <category>css</category>
      <category>pwa</category>
    </item>
    <item>
      <title>Create a PWA app manifest dynamically</title>
      <dc:creator>Kevin Basset</dc:creator>
      <pubDate>Tue, 28 Dec 2021 06:51:07 +0000</pubDate>
      <link>https://forem.com/progressier/create-a-pwa-app-manifest-dynamically-1b4b</link>
      <guid>https://forem.com/progressier/create-a-pwa-app-manifest-dynamically-1b4b</guid>
      <description>&lt;p&gt;Every Progressive Web App has an &lt;a href="https://developer.mozilla.org/en-US/docs/Web/Manifest" rel="noopener noreferrer"&gt;app manifest&lt;/a&gt;, a simple JSON file that contains the basic information of your app, i.e. name, icon, description, etc.&lt;/p&gt;

&lt;p&gt;If you just want to add PWA functionality to your existing web app, &lt;a href="https://progressier.com" rel="noopener noreferrer"&gt;Progressier&lt;/a&gt; has everything you need (including &lt;a href="https://progressier.com/features/programmatic-pwa-creation" rel="noopener noreferrer"&gt;dynamic app icons options&lt;/a&gt;). But if you want to create your own web app manifest dynamically, please read on.&lt;/p&gt;

&lt;h2&gt;
  
  
  How it's traditionally done
&lt;/h2&gt;

&lt;p&gt;When building your first PWA, the simplest way to proceed is adding a link to your app manifest directly in the &lt;code&gt;&amp;lt;head&amp;gt;&lt;/code&gt; section of your HTML template.&lt;/p&gt;

&lt;p&gt;&lt;code&gt;&amp;lt;link rel="manifest" href="/manifest.json"&amp;gt;&lt;/code&gt;&lt;/p&gt;

&lt;p&gt;Host &lt;code&gt;manifest.json&lt;/code&gt; anywhere on your site. The resulting JSON file should look like this:&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;

{
   "short_name":"Coronavirus",
   "name":"The Coronavirus App",
   "background_color":"#ffffff",
   "theme_color":"#ffffff",
   "display":"standalone",
   "orientation":"any",
   "start_url":"https://coronavirus.app",
   "scope":"https://coronavirus.app",
   "icons":[
      {"src":"/icon512.png","sizes":"512x512","type":"image/png"},
      {"src":"/icon192.png","sizes":"192x192","type":"image/png"},
      {"src":"/icon196.png","sizes":"196x196","type":"image/png"}
   ]
}


&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;
&lt;h2&gt;
  
  
  Why you might want to do it differently
&lt;/h2&gt;

&lt;p&gt;While the method above is a perfectly valid way of dealing with web app manifests, there are legitimate cases when a static file won't do and you'll want to generate it dynamically instead:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;You need completely different icons on desktop and mobile&lt;/li&gt;
&lt;li&gt;You want to host multiple distinct PWAs on the same domain&lt;/li&gt;
&lt;li&gt;The PWA should look different for each logged-in user&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;And there are probably many more valid examples. To generate your app manifest dynamically, you have two options: build it on your server when it's requested by users. Or automatically generate it right in the browser (our preferred method).&lt;/p&gt;
&lt;h2&gt;
  
  
  Server-side dynamic app manifest generation
&lt;/h2&gt;

&lt;p&gt;Rather than hosting the file as a static file on you site, make the path to &lt;code&gt;/manifest.json&lt;/code&gt; an API, which will allow you to generate the content of the JSON file programmatically based on whatever your needs are. Here is an example using Node/Express:&lt;/p&gt;
&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;

app.get('/manifest.json', async function(req, res){
    //for brevity, we're not including the isDesktop function here
    let iconUrl = isDesktop() ? '/desktop.png' : '/mobile.png';
    let manifest = { 
       name: "App name",
       icons: [{
         src: iconUrl, 
         sizes: "512x512", 
         type:"image/png"
       }]
    }
    res.header('content-type', 'application/json');
    return res.status(200).send(JSON.stringify(manifest));
});


&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;
&lt;h2&gt;
  
  
  Client-side dynamic app manifest creation
&lt;/h2&gt;

&lt;p&gt;Most people don't know that &lt;code&gt;/manifest.json&lt;/code&gt; doesn't have to be an actual file. In fact, it works just fine with a &lt;a href="https://developer.mozilla.org/en-US/docs/Web/HTTP/Basics_of_HTTP/Data_URIs" rel="noopener noreferrer"&gt;Data URL&lt;/a&gt;. &lt;/p&gt;

&lt;p&gt;I would even argue that a Data URL is a better way to go about this — even if you don't need to generate that particular asset dynamically. &lt;/p&gt;

&lt;p&gt;No additional file to download from your server means faster load times and reduced server costs. And since the Data URL will be different every time you modify the content of your web app manifest, you don't have to worry about the browser not updating its content accordingly.&lt;/p&gt;

&lt;p&gt;In your client-side code, here is how you can create it:&lt;/p&gt;
&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;

//for brevity, we're not including the isDesktop function here
let iconUrl = isDesktop() ? '/desktop.png' : '/mobile.png';
let manifest = { 
  name: "App name",
  icons: [{
    src: iconUrl, 
    sizes: "512x512", 
    type:"image/png"
  }]
};
let content = encodeURIComponent(JSON.stringify(manifest));
let url = "data:application/manifest+json,"+content;
let element = document.createElement('link');
element.setAttribute('rel', 'manifest');
element.setAttribute('href', url);
document.querySelector('head').appendChild(element);


&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;

&lt;p&gt;At &lt;a href="https://progressier.com" rel="noopener noreferrer"&gt;Progressier&lt;/a&gt;, we came across quite a few complex use cases of users needing completely different logos on their Android home screen, on their Android splash screen, on their iPhone splash screen and on their Windows/Mac installed PWA, so we've designed our dashboard accordingly. &lt;/p&gt;

&lt;p&gt;&lt;a href="https://media.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Fbja80wx246627auuhf4e.JPG" class="article-body-image-wrapper"&gt;&lt;img src="https://media.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Fbja80wx246627auuhf4e.JPG" alt="Progressier dynamic app manifest toggles and inputs"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;Behind the scenes, we use these user settings to generate their app manifest dynamically with the methods above.&lt;/p&gt;

&lt;p&gt;Questions or feedback? Leave a comment below!&lt;/p&gt;

</description>
      <category>pwa</category>
      <category>javascript</category>
      <category>beginners</category>
      <category>web</category>
    </item>
    <item>
      <title>Handling Opaque Responses in a Service Worker</title>
      <dc:creator>Kevin Basset</dc:creator>
      <pubDate>Mon, 27 Dec 2021 08:47:12 +0000</pubDate>
      <link>https://forem.com/progressier/handling-opaque-responses-in-a-service-worker-fgd</link>
      <guid>https://forem.com/progressier/handling-opaque-responses-in-a-service-worker-fgd</guid>
      <description>&lt;p&gt;&lt;a href="https://progressier.com"&gt;Progressier&lt;/a&gt; handles opaque responses automatically so that you don't have worry about them. But if you're interested in learning more about opaque responses, why they're a problem and how you should deal with them in your service worker, please continue reading.&lt;/p&gt;

&lt;h2&gt;
  
  
  What's an opaque response?
&lt;/h2&gt;

&lt;p&gt;When a website requests an asset, e.g. a JPG image or a JavaScript file, it sends a request to a server. This server then responds with the requested image or JavaScript contents.&lt;/p&gt;

&lt;p&gt;Problems start when the asset is hosted on a different domain than the requesting site. Browsers follow a mechanism called &lt;a href="https://developer.mozilla.org/en-US/docs/Web/HTTP/CORS"&gt;Cross-Origin Resource Sharing&lt;/a&gt; (or CORS). In a nutshell, it greatly limits what you can do with a resource located on a different domain.&lt;/p&gt;

&lt;p&gt;One way to prevent issues is for the owner of the resource to add a &lt;code&gt;access-control-allow-origin: *&lt;/code&gt; header to the response (also works with your specific domain instead of &lt;code&gt;*&lt;/code&gt;). It essentially tells the browser &lt;em&gt;hey Chrome, just let anyone freely use that resource on their site&lt;/em&gt;. &lt;/p&gt;

&lt;p&gt;Here is where it gets interesting — even without this header, &lt;code&gt;&amp;lt;script&amp;gt;&lt;/code&gt;, &lt;code&gt;&amp;lt;style&amp;gt;&lt;/code&gt; or &lt;code&gt;&amp;lt;img&amp;gt;&lt;/code&gt; tags will still be able to request and use these resources. But your Javascript code or service worker won't be allowed to read or otherwise modify them. Responses that the browser can use but that you as a developer cannot are called &lt;em&gt;opaque responses&lt;/em&gt;.&lt;/p&gt;

&lt;h2&gt;
  
  
  So... what's the problem with opaque responses, exactly?
&lt;/h2&gt;

&lt;p&gt;When a server sends a response to a browser, it also sends a HTTP &lt;a href="https://developer.mozilla.org/en-US/docs/Web/HTTP/Status"&gt;response status code&lt;/a&gt;, which tells the browser whether the request was successful or not.&lt;/p&gt;

&lt;p&gt;Anything starting with &lt;code&gt;2xx&lt;/code&gt; usually means success. &lt;code&gt;5xx&lt;/code&gt; means there was an error with the server. And &lt;code&gt;4xx&lt;/code&gt; an error with the request.&lt;/p&gt;

&lt;p&gt;For opaque responses, the response status code is always &lt;code&gt;0&lt;/code&gt;.&lt;/p&gt;

&lt;p&gt;Your service worker has no way to know whether a request was successful or whether it resulted in an error. And because the request is made completely opaque, it does not contain any other clues that can tell you which way it went. &lt;/p&gt;

&lt;blockquote&gt;
&lt;p&gt;&lt;em&gt;Editor's Note: I understand why opaque responses are necessary, but I honestly have no idea why 'opaque' also has to mean hiding the request status code. If anyone knows, I'm genuinely interested in learning more about the reasoning behind this choice.&lt;/em&gt;&lt;/p&gt;
&lt;/blockquote&gt;

&lt;p&gt;The problem is that most apps and websites do receive quite a few opaque responses. And if you choose not to cache them, these resources won't be accessible offline at all when the network fails to send a successful response.&lt;/p&gt;

&lt;h2&gt;
  
  
  Approach 1: The Last Resort Approach
&lt;/h2&gt;

&lt;p&gt;The approach we use at &lt;a href="https://progressier.com"&gt;Progressier&lt;/a&gt; is to cache resources that have opaque responses but only serve them as a last resort — when there is no other response available in cache and the network couldn't provide a response (because that server is down or the user is offline for example).&lt;/p&gt;

&lt;p&gt;In that case, the choice is between showing an error (100% probability of error) versus showing an opaque response that may or not have been a successful response (unknown probability of error but by definition less than 100%).&lt;/p&gt;

&lt;h2&gt;
  
  
  Approach 2: The No-Cache Approach
&lt;/h2&gt;

&lt;p&gt;The default behavior in &lt;a href="https://developers.google.com/web/tools/workbox"&gt;Workbox&lt;/a&gt; is to not cache opaque responses at all. This is another valid approach. It eliminates the uncertainty described above and it also ensures that the resource will NOT be available offline. &lt;/p&gt;

&lt;p&gt;In cases where your front-end actually need to know what the error is (not just know that there is an error), this approach provides consistency and may be a better alternative.&lt;/p&gt;

&lt;h2&gt;
  
  
  Recognizing and caching opaque responses
&lt;/h2&gt;

&lt;p&gt;An opaque response can be identified by the status code in the &lt;a href="https://developer.mozilla.org/en-US/docs/Web/API/Response"&gt;Response&lt;/a&gt; object. If it contains &lt;code&gt;response.status === 0&lt;/code&gt;, then you're dealing with an opaque response. &lt;/p&gt;

&lt;p&gt;Note that what you can do with opaque responses caching-wise is limited. You'll necessarily have to call &lt;a href="https://developer.mozilla.org/en-US/docs/Web/API/Response/clone"&gt;response.clone()&lt;/a&gt; before putting it in cache. If you don't, the body of the response will be consumed when you put in cache and it will fail when you also return it as a response to the &lt;a href="https://developer.mozilla.org/en-US/docs/Web/API/FetchEvent"&gt;fetch&lt;/a&gt; event.&lt;/p&gt;

&lt;p&gt;Response.clone() sometimes isn't enough, i.e. if you need to alter the response before putting in cache. That's why you can usually make a copy of the headers and body of the request, and recreate a response from scratch using the &lt;a href="https://developer.mozilla.org/en-US/docs/Web/API/Response/Response"&gt;Response&lt;/a&gt; constructor. &lt;/p&gt;

&lt;p&gt;Well, with opaque responses... you can't do that. The constructor simply doesn't allow to create opaque responses.&lt;/p&gt;

&lt;h2&gt;
  
  
  Conclusion
&lt;/h2&gt;

&lt;p&gt;When building &lt;a href="https://progressier.com"&gt;Progressier&lt;/a&gt;'s caching strategy builder, I had quite a bit of fun figuring out the inner workings of service workers. And opaque responses were definitely one of the highlights! Their quirks can be hard to grasp at first, but once you understand how they behave, dealing with them becomes a lot easier.&lt;/p&gt;

&lt;p&gt;If that article helped you understand anything about opaque responses, leave a comment. And if you have any questions, feel free to reach out to me 💪🎉😜&lt;/p&gt;

</description>
      <category>javascript</category>
      <category>pwa</category>
      <category>webdev</category>
      <category>programming</category>
    </item>
    <item>
      <title>How to force a PWA to refresh its content</title>
      <dc:creator>Kevin Basset</dc:creator>
      <pubDate>Fri, 17 Dec 2021 06:05:33 +0000</pubDate>
      <link>https://forem.com/progressier/how-to-force-a-pwa-to-refresh-its-content-1deb</link>
      <guid>https://forem.com/progressier/how-to-force-a-pwa-to-refresh-its-content-1deb</guid>
      <description>&lt;p&gt;&lt;a href="https://media.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Fgnxlsgshbgr76h1e93te.png" class="article-body-image-wrapper"&gt;&lt;img src="https://media.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Fgnxlsgshbgr76h1e93te.png" alt="An illustration of the concept of forcing a PWA to update its content"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;One of the most common questions we get asked at &lt;a href="https://progressier.com" rel="noopener noreferrer"&gt;Progressier&lt;/a&gt; pertains to client-side caching. How does one ensure a PWA always displays up-to-date data and assets while also making good use of caching?&lt;/p&gt;

&lt;p&gt;Although a PWA often looks and feels like a native app, from a technical perspective it really just works like any other website. When one opens a page, it loads assets (images, scripts, stylesheets…) and data (user data, product data…). These resources are fetched from the network and then used by the browser.&lt;/p&gt;

&lt;p&gt;Enter the &lt;a href="https://developer.mozilla.org/en-US/docs/Web/HTTP/Caching" rel="noopener noreferrer"&gt;HTTP cache&lt;/a&gt; mechanism. In order to make loading websites faster, browsers cache these resources. On the initial load, an image will come from your server. On the following load, it may come from the cache instead. So if you update it in the meantime, the browser may display a stale version of that image. How can you prevent that?&lt;/p&gt;

&lt;h2&gt;
  
  
  The Versioning Trick
&lt;/h2&gt;

&lt;p&gt;Versioning is probably the easiest way to force the browser to load a resource from your server. When you update a resource, add a parameter to the URL of the resource wherever your request in your code. For example, edit your client-side code to request &lt;code&gt;domain.com/data.json?version=2&lt;/code&gt; instead of &lt;code&gt;domain.com/data.json?version=1&lt;/code&gt;.&lt;/p&gt;

&lt;p&gt;A browser will see these two URLs and consider them completely different assets. So it won't use a cached version of the former when the page explicitly requests the latter. A good practice is to append a version number (or any other query strings, really) to key resources in your build process, so you don't have to do that manually every time you make a change.&lt;/p&gt;

&lt;h2&gt;
  
  
  The Cache-Control Header
&lt;/h2&gt;

&lt;p&gt;When a server responds to a HTTP request successfully, it returns the asset itself (a JavaScript file, an image, a CSV file…) but it also sends headers - parameters that tell the browser what they are allowed or not allowed to do with the resource.&lt;/p&gt;

&lt;p&gt;One of these headers is the &lt;a href="https://developer.mozilla.org/en-US/docs/Web/HTTP/Headers/Cache-Control" rel="noopener noreferrer"&gt;Cache-Control&lt;/a&gt; header. It exists specifically so you can tell the browser how a particular resource should be cached (or not) and revalidated (or not).&lt;/p&gt;

&lt;p&gt;If you control the server that responds to the request, you can set different &lt;code&gt;Cache-Control&lt;/code&gt; headers and tell the browser how it should treat each particular resource. For instance, set the value of the &lt;code&gt;Cache-Control&lt;/code&gt; header to&lt;code&gt;no-cache&lt;/code&gt; to forbid the browser to cache the resource at all.&lt;/p&gt;

&lt;p&gt;Of course, this method only works with resources that you own - not third-party scripts, CSS libraries, Google fonts or images hosted somewhere else.&lt;/p&gt;

&lt;h2&gt;
  
  
  The Network First Strategy
&lt;/h2&gt;

&lt;p&gt;Truth be told, the HTTP Cache mechanism is a bit antiquated. With it, all you can really tell the browser is whether or not a resource should be cached and until when.&lt;/p&gt;

&lt;p&gt;There is another caching mechanism called the &lt;a href="https://developer.mozilla.org/fr/docs/Web/API/Cache" rel="noopener noreferrer"&gt;Cache API&lt;/a&gt;. And it's available in &lt;a href="https://developer.mozilla.org/en-US/docs/Web/API/Service_Worker_API" rel="noopener noreferrer"&gt;service workers&lt;/a&gt;. &lt;a href="https://progressier.com" rel="noopener noreferrer"&gt;Progressier&lt;/a&gt; uses that API for its caching strategy maker. You can define very specific rules for each type of resources without having to write a single line of code.&lt;/p&gt;

&lt;p&gt;Use &lt;em&gt;Network First&lt;/em&gt; and target all resources to prevent caching altogether. With &lt;em&gt;the Network First&lt;/em&gt; strategy, resources will always be fetched from the network exclusively (so an error will be thrown if the network is somehow unavailable, e.g. if the user or the server go offline).&lt;/p&gt;

&lt;p&gt;Of course, most of the time, you'll want to be more specific and apply that strategy to resources that are mission critical and use more caching-friendly strategies for less essential resources (&lt;a href="https://intercom.help/progressier/en/articles/5703064-what-s-a-caching-strategy" rel="noopener noreferrer"&gt;Stale-While-Revalidate&lt;/a&gt; for example).&lt;/p&gt;

&lt;h2&gt;
  
  
  The Fake Reload Button
&lt;/h2&gt;

&lt;p&gt;With the first three methods, you're essentially telling the browser whether a particular resource should be retrieved from the cache or the network when the page is loaded.&lt;/p&gt;

&lt;p&gt;But once installed, opening a PWA may not always trigger a new page reload. You can launch a PWA from your home screen, do what you have to do with it, then launch another app, and go back to the PWA the next day.&lt;/p&gt;

&lt;p&gt;If you haven't closed the app or turned off your phone in the meantime, it will not reload the page - instead it will simply allow you to continue your session where you left off. If you, the app owner, updated resources in the meantime, then the user may still be using stale resources. How can you force the PWA to refresh its content then?&lt;/p&gt;

&lt;p&gt;When we launched &lt;a href="https://progressier.com/dashboard#story" rel="noopener noreferrer"&gt;The Coronavirus App&lt;/a&gt; in January 2020, users were very demanding when it came to data freshness. We updated data every 15 minutes automatically, so it was absolutely critical to not let users see stale data (or we would receive tons of angry emails!).&lt;/p&gt;

&lt;p&gt;But rather than wait for actual updates, we used a simple trick: when the user had spent more than half an hour on the page, we would present them with an &lt;a href="https://progressier.com/features/pwa-content-refresh-prompt" rel="noopener noreferrer"&gt;option to reload the page&lt;/a&gt; (which they had no other choice but click) and fetch resources again. It looked like this:&lt;/p&gt;

&lt;p&gt;&lt;a href="https://media.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2F5vrulyux0wz7rngcm95u.jpeg" class="article-body-image-wrapper"&gt;&lt;img src="https://media.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2F5vrulyux0wz7rngcm95u.jpeg" alt="A practical example of forcing a PWA to update its content in the Coronavirus App"&gt;&lt;/a&gt;&lt;br&gt;
Giving the illusion that something is happening is a powerful UX concept. In our case, the purpose was two-fold: make it look like the app was updated constantly (which it actually was - just not in a synchronized manner with that fake reload button) and avoid showing data.&lt;/p&gt;

&lt;h2&gt;
  
  
  Conclusion
&lt;/h2&gt;

&lt;p&gt;So there you go. Three different caching methods to ensure your PWA stays updated and one little UX trick.&lt;/p&gt;

&lt;p&gt;The versioning trick is probably the easiest way to go about this - and it works universally, whether you own the requested resources or not.&lt;/p&gt;

&lt;p&gt;And if that's not feasible easily in your build process, you can either use the Cache-Control header (for resources you own) or the Service Worker method to granularly define caching behaviors.&lt;/p&gt;

&lt;p&gt;What do you think? Do you use other methods to keep your PWA updated?&lt;/p&gt;

</description>
      <category>pwa</category>
      <category>javascript</category>
      <category>webdev</category>
      <category>caching</category>
    </item>
  </channel>
</rss>
