Understanding Progressive Web Apps: Delivering a Mobile Experience - HTML5 and JavaScript Service Workers in 2021
Tags: webdev, javascript, html, pwa
Published: 2021-08-15 08:05:19

## What is a PWA?


> A PWA (Progressive Web Application) is a type of app software delivered through the web. PWAs are built using common technologies such as HTML, CSS and JavaScript. They can be installed and function on any platform which uses a W3C compliant web browser on either desktop or mobile devices.
>It is important to note that the browser will allow your web application to become a PWA only over a `secure connection` (using SSL encryption technology) or on `localhost`.


###Who Can Install PWAs?

Support for PWAs are mostly focused around mobile browsers but there is limited support for some desktop browsers as well.

>####Support as of August 2021:
>
>#####Mobile: 
>Nearly every mobile browser supports PWAs, with the exception of KaiOS
>
>#####Desktop:
>Chrome 39 & UP
>Edge 79 & UP
>IOS Safari/Chrome 11.3 & UP - Partial Support
>Firefox - Deprecated as of January 2021


![Progressive Web App Browser Support Table](https://dev-to-uploads.s3.amazonaws.com/uploads/articles/xyysfqvzxebf6fjq5p6p.png)

<cite>Screenshot from [caniuse.com](https://caniuse.com/?search=PWA)</cite>


##Why Should I Develop PWAs?

>####Purpose:
>
> PWAs allow us to offer web applications that can be installed onto any device and act indistinguishably from a native app.
>By meeting certain criteria, your website or web application can easily be turned into a PWA as well.
>
>####Required Technologies:
>
> - HTML
> - CSS
> - JavaScript
> - NodeJS (optional package that we will be using here)
>
>####Benefits:
>
> - Application Store Registration
> - Offline Fallbacks
> - Network or Cache First Resource Fetching
> - Push Notifications
> - Background Sync
> - And More


##It doesn't take much to get started!


###Our File Structure

```
    -root/
      -index.html
      -manifest.json
      -service-worker.js
      -logo.[png, jpg, etc...]
```


##index.html


Development can start with a basic HTML5 boilerplate.


###HTML5 Boilerplate


```html
    <html lang="en">
      <head>
        <meta charset="UTF-8">
        <meta name="viewport" content="width=device-width, initial-scale=1.0">
        <title>TechSnack Simple PWA</title>
      </head>
      <body>
        <h1>Hello World!</h1>
      </body>
    <html>
```


##manifest.json

In order for the user's device to know what to do with our web application, we are going to need to provide it some details. These details are fairly self explanatory.


>We will leave the `icons` array empty for now. More on this shortly.


```json
    {
      "name": "TechSnack Simple PWA",
      "short_name": "TechSnack",
      "start_url": "/?home=true",
      "icons": [],
      "theme_color": "#000000",
      "background_color": "#FFFFFF",
      "display": "fullscreen",
      "orientation": "portrait"
    }
```


>With this information the user's device can:
>
> - Install our application
> - Apply a custom icon for launching the app
> - Display a custom splash screen on launch
> - Allow customization of the application window and behaviour
> - Mimic native application behaviour
> - Allow access to native features such as GPS and push notifications
> - Register our application with popular app stores


##Linking manifest.json

Use the `link` tag to connect `manifest.json` to our app

```html
    <head>

      ...

      <link rel="manifest" href="manifest.json">
    </head>
```

##Node Packages (1 - optional)

- [pwa-asset-generator](https://www.npmjs.com/package/pwa-asset-generator)

>As previously mentioned, the user's device will apply the custom icon for us. For it to do that we will need to supply at least one image for the device to reference.

###What about multiple screen sizes or resolutions?

There are countless different mobile devices being used around the world today. To optimize the display of visual assets, each device prefers logos of a certain dimension.


##pwa-asset-generator

You will need at least one image file of the following MIME types:

 - PNG
 - JPEG/JPG
 - SVG
 - WebP


>###Install
>
>```
>    $ npm install --global pwa-asset-generator
>```


We will now want to run the package in our `webroot` directory.
The following snippet will do for our purposes.

>`npx pwa-asset-generator [path/to/logo] [path/to/output/dir] -i [path/to/index.html] -m [path/to/manifest.json] -f`
>The `-f` flag generates favicon image/meta tag


>###Execution - From `webroot` directory
>
>```
>    $ npx pwa-asset-generator logo.jpg logos -i index.html -m manifest.json -f
>```

![Command line output | pwa-asset-generator | npm](https://dev-to-uploads.s3.amazonaws.com/uploads/articles/2sx52e4v6s2mn2yhjx4t.png)

>It is worth noting that if you execute `pwa-asset-generator` without using the `-i`, `-m` and `-f` flags the results will be output to your console instead.

>Copy and paste the results into the `icons` array within `manifest.json`

![Command line output | pwa-asset-generator | npm](https://dev-to-uploads.s3.amazonaws.com/uploads/articles/mnb02co2lbdp3ay0oltb.png)

>Copy and paste the output into the `head` tag of `index.html`

![Command line output | pwa-asset-generator | npm](https://dev-to-uploads.s3.amazonaws.com/uploads/articles/li3s906b1r3i36tzmlgl.png)



###New `icons/` directory

>Contains all of the generated images.


![Directory Structure for PWA Icon Images](https://dev-to-uploads.s3.amazonaws.com/uploads/articles/zhad7nlvodfcmmgh0q1p.png)


###Updated `index.html`

Our `index.html` file should look like this now:

```
    <!DOCTYPE html>
<html lang="en">
  <head>
    <meta charset="UTF-8">
    <meta name="viewport" content="width=device-width, initial-scale=1.0">
    <meta name="theme-color" content="#000000">
    <link rel="apple-touch-icon" href="icons/apple-icon-180.png">

    <meta name="apple-mobile-web-app-capable" content="yes">
    <link rel="apple-touch-startup-image" href="icons/apple-splash-2048-2732.jpg" media="(device-width: 1024px) and (device-height: 1366px) and (-webkit-device-pixel-ratio: 2) and (orientation: portrait)">
    <link rel="apple-touch-startup-image" href="icons/apple-splash-2732-2048.jpg" media="(device-width: 1024px) and (device-height: 1366px) and (-webkit-device-pixel-ratio: 2) and (orientation: landscape)">
    <link rel="apple-touch-startup-image" href="icons/apple-splash-1668-2388.jpg" media="(device-width: 834px) and (device-height: 1194px) and (-webkit-device-pixel-ratio: 2) and (orientation: portrait)">
    <link rel="apple-touch-startup-image" href="icons/apple-splash-2388-1668.jpg" media="(device-width: 834px) and (device-height: 1194px) and (-webkit-device-pixel-ratio: 2) and (orientation: landscape)">
    <link rel="apple-touch-startup-image" href="icons/apple-splash-1536-2048.jpg" media="(device-width: 768px) and (device-height: 1024px) and (-webkit-device-pixel-ratio: 2) and (orientation: portrait)">
    <link rel="apple-touch-startup-image" href="icons/apple-splash-2048-1536.jpg" media="(device-width: 768px) and (device-height: 1024px) and (-webkit-device-pixel-ratio: 2) and (orientation: landscape)">
    <link rel="apple-touch-startup-image" href="icons/apple-splash-1668-2224.jpg" media="(device-width: 834px) and (device-height: 1112px) and (-webkit-device-pixel-ratio: 2) and (orientation: portrait)">
    <link rel="apple-touch-startup-image" href="icons/apple-splash-2224-1668.jpg" media="(device-width: 834px) and (device-height: 1112px) and (-webkit-device-pixel-ratio: 2) and (orientation: landscape)">
    <link rel="apple-touch-startup-image" href="icons/apple-splash-1620-2160.jpg" media="(device-width: 810px) and (device-height: 1080px) and (-webkit-device-pixel-ratio: 2) and (orientation: portrait)">
    <link rel="apple-touch-startup-image" href="icons/apple-splash-2160-1620.jpg" media="(device-width: 810px) and (device-height: 1080px) and (-webkit-device-pixel-ratio: 2) and (orientation: landscape)">
    <link rel="apple-touch-startup-image" href="icons/apple-splash-1284-2778.jpg" media="(device-width: 428px) and (device-height: 926px) and (-webkit-device-pixel-ratio: 3) and (orientation: portrait)">
    <link rel="apple-touch-startup-image" href="icons/apple-splash-2778-1284.jpg" media="(device-width: 428px) and (device-height: 926px) and (-webkit-device-pixel-ratio: 3) and (orientation: landscape)">
    <link rel="apple-touch-startup-image" href="icons/apple-splash-1170-2532.jpg" media="(device-width: 390px) and (device-height: 844px) and (-webkit-device-pixel-ratio: 3) and (orientation: portrait)">
    <link rel="apple-touch-startup-image" href="icons/apple-splash-2532-1170.jpg" media="(device-width: 390px) and (device-height: 844px) and (-webkit-device-pixel-ratio: 3) and (orientation: landscape)">
    <link rel="apple-touch-startup-image" href="icons/apple-splash-1125-2436.jpg" media="(device-width: 375px) and (device-height: 812px) and (-webkit-device-pixel-ratio: 3) and (orientation: portrait)">
    <link rel="apple-touch-startup-image" href="icons/apple-splash-2436-1125.jpg" media="(device-width: 375px) and (device-height: 812px) and (-webkit-device-pixel-ratio: 3) and (orientation: landscape)">
    <link rel="apple-touch-startup-image" href="icons/apple-splash-1242-2688.jpg" media="(device-width: 414px) and (device-height: 896px) and (-webkit-device-pixel-ratio: 3) and (orientation: portrait)">
    <link rel="apple-touch-startup-image" href="icons/apple-splash-2688-1242.jpg" media="(device-width: 414px) and (device-height: 896px) and (-webkit-device-pixel-ratio: 3) and (orientation: landscape)">
    <link rel="apple-touch-startup-image" href="icons/apple-splash-828-1792.jpg" media="(device-width: 414px) and (device-height: 896px) and (-webkit-device-pixel-ratio: 2) and (orientation: portrait)">
    <link rel="apple-touch-startup-image" href="icons/apple-splash-1792-828.jpg" media="(device-width: 414px) and (device-height: 896px) and (-webkit-device-pixel-ratio: 2) and (orientation: landscape)">
    <link rel="apple-touch-startup-image" href="icons/apple-splash-1242-2208.jpg" media="(device-width: 414px) and (device-height: 736px) and (-webkit-device-pixel-ratio: 3) and (orientation: portrait)">
    <link rel="apple-touch-startup-image" href="icons/apple-splash-2208-1242.jpg" media="(device-width: 414px) and (device-height: 736px) and (-webkit-device-pixel-ratio: 3) and (orientation: landscape)">
    <link rel="apple-touch-startup-image" href="icons/apple-splash-750-1334.jpg" media="(device-width: 375px) and (device-height: 667px) and (-webkit-device-pixel-ratio: 2) and (orientation: portrait)">
    <link rel="apple-touch-startup-image" href="icons/apple-splash-1334-750.jpg" media="(device-width: 375px) and (device-height: 667px) and (-webkit-device-pixel-ratio: 2) and (orientation: landscape)">
    <link rel="apple-touch-startup-image" href="icons/apple-splash-640-1136.jpg" media="(device-width: 320px) and (device-height: 568px) and (-webkit-device-pixel-ratio: 2) and (orientation: portrait)">
    <link rel="apple-touch-startup-image" href="icons/apple-splash-1136-640.jpg" media="(device-width: 320px) and (device-height: 568px) and (-webkit-device-pixel-ratio: 2) and (orientation: landscape)">

    <title>TechSnack | Simple PWA</title>
    <link id="favicon" rel="sortcut icon" href="favicon.ico" type="image/x-icon">
    <link rel="manifest" href="manifest.json">
  </head>
  <body>
    <h1>Hello World!</h1>
  </body>
</html>
```

###Final `manifest.json`

Our `manifest.json` file should look like this now:

```json
    {
      "name": "TechSnack Simple PWA",
      "short_name": "TechSnack",
      "start_url": "/?home=true",
      "icons": [
        {
          "src": "icons/manifest-icon-192.png",
          "sizes": "192x192",
          "type": "image/png",
          "purpose": "maskable any"
        },
        {
          "src": "icons/manifest-icon-512.png",
          "sizes": "512x512",
          "type": "image/png",
          "purpose": "maskable any"
        }
      ],
      "theme_color": "#000000",
      "background_color": "#FFFFFF",
      "display": "fullscreen",
      "orientation": "portrait"
    }
```


##Service Worker

Before our PWA can do all of the fancy things previously mentioned we must first create a service worker.


>A service worker is a listener script the browser runs in the background. The service worker runs separately from the webpage allowing for the implementation of features that do not require interaction or calls from the webpage or user.
>Service Workers may support features such as `periodic sync` or `geofencing` in the future.
>NOTE: Although service workers are a JavaScript file, there are additional limitations imposed when coding. You may not have access to the DOM through a service worker.

###Lifecycle of a Service Worker

>There are multiple functions/features that you would normally have to build out around [A Service Workers Lifecycle](https://developers.google.com/web/fundamentals/primers/service-workers).
>We will be using an API called `workbox` to avoid worrying about configuring what is under the hood.


###`workbox` API


For our service worker, we are going to employ the use of an API called `workbox`. This API offers baked-in features that would require multiple articles to describe on its own. 
If you are interested in digging into the nitty gritty you can read about [A Service Workers Lifecycle](https://developers.google.com/web/fundamentals/primers/service-workers).


>####`ImportScript`
>
>Inside of `service-worker.js` we will `import` the `workbox API`
>
>```javascript
>    importScripts('https://storage.googleapis.com/workbox-cdn/releases/6.0.2/workbox-sw.js');
>```


####Registering Routes

A service worker can intercept network requests from a page. It may respond to the page with `cached content` or `generated content`

![Service Worker Routing| JavaScript](https://dev-to-uploads.s3.amazonaws.com/uploads/articles/kvm5ydu7z0s6c4u7z0d1.png)

<cite>Screenshot from [Google Dev](https://developers.google.com/web/tools/workbox/modules/workbox-routing)</cite>

>Note: (From Above)
>
>The `method` is `GET` by default.
>To change this it must be specified.
>
>The order of route registration is important when multiple workers are available to handle a request.
>Whichever worker has been created `first` will take priority in handling a given request.

##`service-worker.js`

We can now add the following code within `service-worker.js`:

```javascript

    ...

    workbox.routing.registerRoute(
      ({request}) => request.destination === 'image',
      new workbox.strategies.CacheFirst() //to search cache first
      //new workbox.strategies.NetworkFirst() //to search server first
);
```

>That's it! The above code will:
> - `RegisterRoute` with `workbox`
> - Intercept all `'image'` files at the page's `request`


Here we choose our `strategies`. Would we like to serve to our page from `CacheFirst` or `NetworkFirst`? This we will decide by whether the specific resource(s) we are interested in is `static` or `dynamic`.

 - If they are generally `static` to the page our user lands on then we will want to serve them from `cache`.

 - However, if they are `dynamically generated` by some sort of `back-end` then we would want to largely get that file from the `network`.


##Final `service-worker.js`

```javascript

    importScripts('https://storage.googleapis.com/workbox-cdn/releases/6.0.2/workbox-sw.js');

    workbox.routing.registerRoute(
      ({request}) => request.destination === 'image',
      new workbox.strategies.CacheFirst() //to search cache first
      //new workbox.strategies.NetworkFirst() //to search server first
);
```


###Linking `service-worker.js`


Now that we have our service worker in place and intercepting requests for image files, we can link our script within `index.html`

```html
    <body>

      ...

      <script>
        if('serviceWorker' in navigator){
        navigator.serviceWorker.register('/service-worker.js');
      }
      </script>
    </body>
```

Its that simple to register our service worker!


##Putting it all together

We can finally take a look at the final code base for our PWA project.

###Our File Structure:
![Progressive Web Application | File Structure](https://dev-to-uploads.s3.amazonaws.com/uploads/articles/7egcbtjwcwf6u89c5s25.png)

###`index.html`

```html
    <!DOCTYPE html>
    <html lang="en">
      <head>
        <meta charset="UTF-8">
        <meta name="viewport" content="width=device-width, initial-scale=1.0">
        <meta name="theme-color" content="#000000">
        <link rel="apple-touch-icon" href="icons/apple-icon-180.png">

        <meta name="apple-mobile-web-app-capable" content="yes">
        <link rel="apple-touch-startup-image" href="icons/apple-splash-2048-2732.jpg" media="(device-width: 1024px) and (device-height: 1366px) and (-webkit-device-pixel-ratio: 2) and (orientation: portrait)">
        <link rel="apple-touch-startup-image" href="icons/apple-splash-2732-2048.jpg" media="(device-width: 1024px) and (device-height: 1366px) and (-webkit-device-pixel-ratio: 2) and (orientation: landscape)">
        <link rel="apple-touch-startup-image" href="icons/apple-splash-1668-2388.jpg" media="(device-width: 834px) and (device-height: 1194px) and (-webkit-device-pixel-ratio: 2) and (orientation: portrait)">
        <link rel="apple-touch-startup-image" href="icons/apple-splash-2388-1668.jpg" media="(device-width: 834px) and (device-height: 1194px) and (-webkit-device-pixel-ratio: 2) and (orientation: landscape)">
        <link rel="apple-touch-startup-image" href="icons/apple-splash-1536-2048.jpg" media="(device-width: 768px) and (device-height: 1024px) and (-webkit-device-pixel-ratio: 2) and (orientation: portrait)">
        <link rel="apple-touch-startup-image" href="icons/apple-splash-2048-1536.jpg" media="(device-width: 768px) and (device-height: 1024px) and (-webkit-device-pixel-ratio: 2) and (orientation: landscape)">
        <link rel="apple-touch-startup-image" href="icons/apple-splash-1668-2224.jpg" media="(device-width: 834px) and (device-height: 1112px) and (-webkit-device-pixel-ratio: 2) and (orientation: portrait)">
        <link rel="apple-touch-startup-image" href="icons/apple-splash-2224-1668.jpg" media="(device-width: 834px) and (device-height: 1112px) and (-webkit-device-pixel-ratio: 2) and (orientation: landscape)">
        <link rel="apple-touch-startup-image" href="icons/apple-splash-1620-2160.jpg" media="(device-width: 810px) and (device-height: 1080px) and (-webkit-device-pixel-ratio: 2) and (orientation: portrait)">
        <link rel="apple-touch-startup-image" href="icons/apple-splash-2160-1620.jpg" media="(device-width: 810px) and (device-height: 1080px) and (-webkit-device-pixel-ratio: 2) and (orientation: landscape)">
        <link rel="apple-touch-startup-image" href="icons/apple-splash-1284-2778.jpg" media="(device-width: 428px) and (device-height: 926px) and (-webkit-device-pixel-ratio: 3) and (orientation: portrait)">
        <link rel="apple-touch-startup-image" href="icons/apple-splash-2778-1284.jpg" media="(device-width: 428px) and (device-height: 926px) and (-webkit-device-pixel-ratio: 3) and (orientation: landscape)">
        <link rel="apple-touch-startup-image" href="icons/apple-splash-1170-2532.jpg" media="(device-width: 390px) and (device-height: 844px) and (-webkit-device-pixel-ratio: 3) and (orientation: portrait)">
        <link rel="apple-touch-startup-image" href="icons/apple-splash-2532-1170.jpg" media="(device-width: 390px) and (device-height: 844px) and (-webkit-device-pixel-ratio: 3) and (orientation: landscape)">
        <link rel="apple-touch-startup-image" href="icons/apple-splash-1125-2436.jpg" media="(device-width: 375px) and (device-height: 812px) and (-webkit-device-pixel-ratio: 3) and (orientation: portrait)">
        <link rel="apple-touch-startup-image" href="icons/apple-splash-2436-1125.jpg" media="(device-width: 375px) and (device-height: 812px) and (-webkit-device-pixel-ratio: 3) and (orientation: landscape)">
        <link rel="apple-touch-startup-image" href="icons/apple-splash-1242-2688.jpg" media="(device-width: 414px) and (device-height: 896px) and (-webkit-device-pixel-ratio: 3) and (orientation: portrait)">
        <link rel="apple-touch-startup-image" href="icons/apple-splash-2688-1242.jpg" media="(device-width: 414px) and (device-height: 896px) and (-webkit-device-pixel-ratio: 3) and (orientation: landscape)">
        <link rel="apple-touch-startup-image" href="icons/apple-splash-828-1792.jpg" media="(device-width: 414px) and (device-height: 896px) and (-webkit-device-pixel-ratio: 2) and (orientation: portrait)">
        <link rel="apple-touch-startup-image" href="icons/apple-splash-1792-828.jpg" media="(device-width: 414px) and (device-height: 896px) and (-webkit-device-pixel-ratio: 2) and (orientation: landscape)">
        <link rel="apple-touch-startup-image" href="icons/apple-splash-1242-2208.jpg" media="(device-width: 414px) and (device-height: 736px) and (-webkit-device-pixel-ratio: 3) and (orientation: portrait)">
        <link rel="apple-touch-startup-image" href="icons/apple-splash-2208-1242.jpg" media="(device-width: 414px) and (device-height: 736px) and (-webkit-device-pixel-ratio: 3) and (orientation: landscape)">
        <link rel="apple-touch-startup-image" href="icons/apple-splash-750-1334.jpg" media="(device-width: 375px) and (device-height: 667px) and (-webkit-device-pixel-ratio: 2) and (orientation: portrait)">
        <link rel="apple-touch-startup-image" href="icons/apple-splash-1334-750.jpg" media="(device-width: 375px) and (device-height: 667px) and (-webkit-device-pixel-ratio: 2) and (orientation: landscape)">
        <link rel="apple-touch-startup-image" href="icons/apple-splash-640-1136.jpg" media="(device-width: 320px) and (device-height: 568px) and (-webkit-device-pixel-ratio: 2) and (orientation: portrait)">
        <link rel="apple-touch-startup-image" href="icons/apple-splash-1136-640.jpg" media="(device-width: 320px) and (device-height: 568px) and (-webkit-device-pixel-ratio: 2) and (orientation: landscape)">

        <title>TechSnack | Simple PWA</title>
        <link id="favicon" rel="sortcut icon" href="favicon.ico" type="image/x-icon">
        <link rel="manifest" href="manifest.json">
      </head>
      <body>
        <h1>Hello World!</h1>

        <script>
          if('serviceWorker' in navigator){
            navigator.serviceWorker.register('/service-worker.js');
          }
        </script>
      </body>
    </html>
```


##`manifest.json`

```json
    {
      "name": "TechSnack Simple PWA",
      "short_name": "TechSnack",
      "start_url": "/?home=true",
      "icons": [
        {
          "src": "icons/manifest-icon-192.png",
          "sizes": "192x192",
          "type": "image/png",
          "purpose": "maskable any"
        },
        {
          "src": "icons/manifest-icon-512.png",
          "sizes": "512x512",
          "type": "image/png",
          "purpose": "maskable any"
        }
      ],
      "theme_color": "#000000",
      "background_color": "#FFFFFF",
      "display": "fullscreen",
      "orientation": "portrait"
    }
```


###`service-worker.js`


```javascript
    importScripts('https://storage.googleapis.com/workbox-cdn/releases/6.0.2/workbox-sw.js');

    workbox.routing.registerRoute(
      ({request}) => request.destination === 'image',
      new workbox.strategies.CacheFirst() //to search cache first
    );
```


##Serving Our Page

We can now view our page in the browser.

>For `localhost` run `npx serve` in the `webroot` directory.
>Then you can visit your securely served `remote ip` or `domain`
>Please keep in mind the browser support for your device

![Progressive Web App In Browser Window](https://dev-to-uploads.s3.amazonaws.com/uploads/articles/qikvlp6v89pjuf2m7fu8.png)

>There is a new icon in our address bar!
![Progressive Web App | Install User Action Icon](https://dev-to-uploads.s3.amazonaws.com/uploads/articles/xcdhvcm35vzjsv21dixl.png)

>The browser requires a `user action` to install the PWA
>The User can click this icon to see the `prompt`
![Progressive Web App | Install User Action Prompt](https://dev-to-uploads.s3.amazonaws.com/uploads/articles/h2xzzmc5c26z8hon8wfl.png)

>Now you can click on the icon installed on the device's homepage
![PWA Application Running in Program Browser Window ](https://dev-to-uploads.s3.amazonaws.com/uploads/articles/29weqkudnzkfiiqzl9u9.png)

##Summarization of PWA Development

As you can see it is super easy to create a web application that can mimic the same functions as a native app. In future articles we will delve into each feature that we will now have access to with this powerful piece of technology.

###Help TechSnack Write Concise Content:

Leave us a comment with your thoughts on the article below. Whether you liked or disliked the article, all feedback will help me to know how to better create content that meets your needs, goals and aspirations.

Sharing the article on your social platforms would also be a great help!

[Follow TechSnack on Twitter](https://twitter.com/RomesbergJames)


Join the conversation at [r/TechSnack](https://www.reddit.com/r/TechSnack/)