<?xml version="1.0" encoding="UTF-8"?>
<rss xmlns:dc="http://purl.org/dc/elements/1.1/" xmlns:content="http://purl.org/rss/1.0/modules/content/"
    xmlns:atom="http://www.w3.org/2005/Atom" xmlns:media="http://search.yahoo.com/mrss/" version="2.0">
    <channel>
        
        <title>
            <![CDATA[ PWA - freeCodeCamp.org ]]>
        </title>
        <description>
            <![CDATA[ Browse thousands of programming tutorials written by experts. Learn Web Development, Data Science, DevOps, Security, and get developer career advice. ]]>
        </description>
        <link>https://www.freecodecamp.org/news/</link>
        <image>
            <url>https://cdn.freecodecamp.org/universal/favicons/favicon.png</url>
            <title>
                <![CDATA[ PWA - freeCodeCamp.org ]]>
            </title>
            <link>https://www.freecodecamp.org/news/</link>
        </image>
        <generator>Eleventy</generator>
        <lastBuildDate>Sat, 30 May 2026 16:31:29 +0000</lastBuildDate>
        <atom:link href="https://www.freecodecamp.org/news/tag/pwa/rss.xml" rel="self" type="application/rss+xml" />
        <ttl>60</ttl>
        
            <item>
                <title>
                    <![CDATA[ How to Convert Your Website into an Android App Using Bubblewrap ]]>
                </title>
                <description>
                    <![CDATA[ If you are a web developer who doesn’t know about App Development (like me!), then this article is for you. I’ll teach you how to turn your website into a native app, without new frameworks or languages. You’ll learn how to convert a website to a PWA... ]]>
                </description>
                <link>https://www.freecodecamp.org/news/how-to-convert-your-website-into-an-android-app-using-bubblewrap/</link>
                <guid isPermaLink="false">68a4b9d4f2bced8c3a658f5a</guid>
                
                    <category>
                        <![CDATA[ Android ]]>
                    </category>
                
                    <category>
                        <![CDATA[ PWA ]]>
                    </category>
                
                    <category>
                        <![CDATA[ Web Development ]]>
                    </category>
                
                <dc:creator>
                    <![CDATA[ Sanjay ]]>
                </dc:creator>
                <pubDate>Tue, 19 Aug 2025 17:52:20 +0000</pubDate>
                <media:content url="https://cdn.hashnode.com/res/hashnode/image/upload/v1755625913612/bfffd5f9-f4d6-4f8d-aae8-72f5730bd7e9.png" medium="image" />
                <content:encoded>
                    <![CDATA[ <p>If you are a web developer who doesn’t know about App Development (like me!), then this article is for you. I’ll teach you how to turn your website into a native app, without new frameworks or languages. You’ll learn how to convert a website to a PWA (Progressive Web App) that you can publish on the Play Store.</p>
<p>First, we’ll turn your website into a Progressive Web App (PWA). Then we'll use a free command-line tool from Google called <strong>Bubblewrap</strong> to package that PWA into an Android app. Let’s get started.</p>
<h2 id="heading-prerequisites">Prerequisites</h2>
<p>If you follow along with this tutorial, there are some prerequisites:</p>
<ul>
<li><p>Basic knowledge of web development</p>
</li>
<li><p>Your site should be live to the public, and you’ll need to have access to its source code.</p>
</li>
<li><p>We'll use npm to install the necessary tools, so make sure you have Node.js installed.</p>
</li>
</ul>
<p><strong>Note:</strong> This tutorial is based on a <strong>Vite</strong> project, but the final steps with Bubblewrap are the same for any web framework.</p>
<h2 id="heading-table-of-contents">Table of Contents</h2>
<ol>
<li><p><a class="post-section-overview" href="#heading-what-is-a-pwa">What is a PWA?</a></p>
</li>
<li><p><a class="post-section-overview" href="#heading-what-is-bubblewrap">What is Bubblewrap?</a></p>
<ul>
<li><p><a class="post-section-overview" href="#heading-what-is-a-twa-trusted-web-activity">What is a TWA (Trusted Web Activity)?</a></p>
</li>
<li><p><a class="post-section-overview" href="#heading-how-twa-verifies-trust">How TWA Verifies Trust</a></p>
</li>
</ul>
</li>
<li><p><a class="post-section-overview" href="#heading-step-1-configure-your-pwa-in-vite">Step 1 – Configure Your PWA in Vite</a></p>
<ul>
<li><p><a class="post-section-overview" href="#heading-create-your-app-icons">Create Your App Icons</a></p>
</li>
<li><p><a class="post-section-overview" href="#heading-install-the-vite-pwa-plugin">Install the Vite PWA plugin.</a></p>
</li>
<li><p><a class="post-section-overview" href="#heading-configure-the-plugin">Configure the Plugin</a></p>
</li>
</ul>
</li>
<li><p><a class="post-section-overview" href="#heading-step-2-create-the-android-app">Step 2 – Create the Android App</a></p>
<ul>
<li><p><a class="post-section-overview" href="#heading-create-a-build-folder">Create a Build Folder</a></p>
</li>
<li><p><a class="post-section-overview" href="#heading-install-the-bubblewrap-cli">Install the Bubblewrap CLI</a></p>
</li>
<li><p><a class="post-section-overview" href="#heading-initialize-the-project">Initialize the Project</a></p>
</li>
<li><p><a class="post-section-overview" href="#heading-lets-troubleshoot-the-init-command">Let’s troubleshoot the init command</a>.</p>
</li>
</ul>
</li>
<li><p><a class="post-section-overview" href="#heading-step-3-answer-bubblewrap-questions">Step 3 – Answer Bubblewrap Questions</a></p>
</li>
<li><p><a class="post-section-overview" href="#heading-step-4-build-the-app">Step 4 – Build the App</a></p>
</li>
<li><p><a class="post-section-overview" href="#heading-step-5-setting-up-twa-validation">Step 5 – Setting Up TWA Validation</a></p>
<ul>
<li><p><a class="post-section-overview" href="#heading-what-is-the-well-known-folder">What is the .well-known folder?</a></p>
</li>
<li><p><a class="post-section-overview" href="#heading-what-is-delegatepermissioncommonhandleallurls">What is delegate_permission/common.handle_all_urls?</a></p>
</li>
</ul>
</li>
<li><p><a class="post-section-overview" href="#heading-step-6-optional-customize-the-in-app-experience">Step 6 (Optional) – Customize the In-App Experience</a></p>
</li>
<li><p><a class="post-section-overview" href="#heading-wrapping-up">Wrapping Up</a></p>
</li>
</ol>
<h2 id="heading-what-is-a-pwa">What is a PWA?</h2>
<p>PWA stands for <strong>Progressive Web Application</strong>, and its goal is to make your website look and feel just like a native app. If you’ve visited a website in your browser and seen an install icon that lets you download it to your phone or laptop, you've used a PWA.</p>
<p>But it’s not just about the look and feel. A PWA also has app-like features, such as working offline, sending push notifications, and more.</p>
<p>There are two main components of a PWA.</p>
<ul>
<li><p>The manifest file describes your app, such as its name, icons, start URL, and so on.</p>
</li>
<li><p>A service worker is a background JavaScript file that acts as a proxy. The caching and push notifications are handled by a service file, which runs as a different thread apart from the main thread.</p>
</li>
</ul>
<p>Without these two components, browsers won’t let users download the app locally.</p>
<p>The manifest file and the service worker are like a checklist for the browser. When you visit a website, the browser looks for both of these components. If they are present and correctly configured, the browser knows it's a true PWA and will show the "install" icon, allowing users to download the app locally. Without them, the browser just sees a regular website, and the option to install won't be available.</p>
<h2 id="heading-what-is-bubblewrap">What is Bubblewrap?</h2>
<p>Bubblewrap is a command-line tool made by Google that takes your PWA and turns it into an Android App using a Trusted Web Activity (TWA).</p>
<p>Bubblewrap simplifies the process of creating a TWA, turning a PWA's manifest file into an Android app package (APK or AAB).</p>
<h3 id="heading-what-is-a-twa-trusted-web-activity">What is a TWA (Trusted Web Activity)?</h3>
<p>A Trusted Web Activity (TWA) is a modern Android feature that lets you display your live website full-screen inside an Android app. Basically, it runs the website on the browser, but it doesn’t show the browser address bar on the App. This helps it feel like a native app.</p>
<p>To unlock this full-screen feature, your app needs to be “Trusted“.</p>
<p>This is where the "secret handshake" comes in. Android needs to be sure that the person who built the app and the person who owns the website are the same. Without this proof of ownership, the TWA will run in a fallback mode and show the browser address bar at the top, ruining the native app feel.</p>
<h3 id="heading-how-twa-verifies-trust">How TWA Verifies Trust</h3>
<p>This trust is verified using a system called <strong>Digital Asset Links</strong>. You place a special file on your website (we'll do this in the implementation part) that contains your app's unique digital fingerprint. When a user opens your app, the Android OS checks this file. If the fingerprints match, it grants your app "trusted" status, removes the address bar, and enables other features like deep linking.</p>
<p>You can check this relationship yourself using Google's official testing tool: <a target="_blank" href="https://developers.google.com/digital-asset-links/tools/generator">Digital Asset Links Verifier.</a></p>
<p>Now that you understand the project and tools, let’s start building.</p>
<h2 id="heading-step-1-configure-your-pwa-in-vite">Step 1 – Configure Your PWA in Vite</h2>
<p>The first step is to add the two main components for a PWA: the manifest file and service worker. This is what will allow the browser to recognize it as "installable."</p>
<p>This guide is based on a project built with Vite, which makes this process easy with a special plugin. If you're using a different tool, the concepts are the same, but you'll need to look up different resources about the specific steps for your environment.</p>
<h3 id="heading-create-your-app-icons">Create Your App Icons</h3>
<p>Before we touch any code, we need the icons for our app. Android requires specific sizes for the app's launcher icon (what you see on your home screen) and the splash screen (what you see when the app starts).</p>
<p>You'll need two main sizes: <code>192x192</code> pixels and <code>512x512</code> pixels. You can use this <a target="_blank" href="https://realfavicongenerator.net/">Favicon Generator</a> to generate your logo in the respective sizes. You can upload your main logo, and it will generate all the necessary sizes for you.</p>
<p>Then just download the generated files and place the <code>192x192</code> and <code>512x512</code> files into the <code>public</code> folder of your project.</p>
<p><img src="https://cdn.hashnode.com/res/hashnode/image/upload/v1755067586673/f7e06fc2-4b55-4ec3-af05-b2e78bf19273.png" alt="f7e06fc2-4b55-4ec3-af05-b2e78bf19273" class="image--center mx-auto" width="289" height="340" loading="lazy"></p>
<h3 id="heading-install-the-vite-pwa-plugin">Install the Vite PWA plugin.</h3>
<p>A PWA requires a manifest file and a service worker. We can create these manually, but this plugin automates that entire process. It will automatically generate a <code>manifest.json</code> and <code>service-worker.js</code> for you every time you build your project.</p>
<pre><code class="lang-bash">npm install vite-plugin-pwa -D
</code></pre>
<h3 id="heading-configure-the-plugin">Configure the Plugin</h3>
<p>In this step, we’ll use this plugin and configure our app's manifest. Edit the <code>vite.config.ts</code> file. This configuration will tell the plugin what to name your app, which icons to use, and so on.</p>
<p>In <code>vite.config.ts</code>:</p>
<pre><code class="lang-typescript"><span class="hljs-keyword">export</span> <span class="hljs-keyword">default</span> defineConfig({
  plugins: [
    VitePWA({
      registerType: <span class="hljs-string">"autoUpdate"</span>,   
      manifest: {
        name: <span class="hljs-string">"your app name"</span>,
        short_name: <span class="hljs-string">"your app short name"</span>,
        description: <span class="hljs-string">"write any description"</span>,
        theme_color: <span class="hljs-string">"#0d1117"</span>,
        background_color: <span class="hljs-string">"#ffffff"</span>,
        display: <span class="hljs-string">"standalone"</span>,
        start_url: <span class="hljs-string">"/"</span>,
        icons: [
          {
            src: <span class="hljs-string">"/web-app-manifest-192x192.png"</span>,
            sizes: <span class="hljs-string">"192x192"</span>,
            <span class="hljs-keyword">type</span>: <span class="hljs-string">"image/png"</span>,
          },
          {
            src: <span class="hljs-string">"/web-app-manifest-512x512.png"</span>,
            sizes: <span class="hljs-string">"512x512"</span>,
            <span class="hljs-keyword">type</span>: <span class="hljs-string">"image/png"</span>,
          },
        ],
      },
    }),
  ]
</code></pre>
<p>Now, when you run <code>npm run build</code>, the plugin will automatically generate the manifest and service worker files for you. With that done, deploy the changes. Now your website is a PWA.</p>
<h2 id="heading-step-2-create-the-android-app">Step 2 – Create the Android App</h2>
<p>Now that your website is a PWA, let’s use Bubblewrap to package it into an Android app.</p>
<h3 id="heading-create-a-build-folder">Create a Build Folder</h3>
<p>Create a dedicated folder for your Android project files. In your project's root, create a new folder. I'll call mine <code>android</code>.</p>
<pre><code class="lang-plaintext">project/
├── client/
├── server/
└── android/
</code></pre>
<p>Now navigate to the new folder that you created.</p>
<h3 id="heading-install-the-bubblewrap-cli">Install the Bubblewrap CLI</h3>
<pre><code class="lang-bash">npm install -g @bubblewrap/cli
</code></pre>
<h3 id="heading-initialize-the-project">Initialize the Project</h3>
<p>Next, run the <code>init</code> command. Bubblewrap will connect to your live website, read the <code>manifest.webmanifest</code> file that Vite created, and use that information to generate a basic Android project.</p>
<pre><code class="lang-bash">bubblewrap init --manifest=https://your-website-domain/manifest.webmanifest
</code></pre>
<p>Run the command, replacing <code>your-website-domain</code> with your actual URL:</p>
<h3 id="heading-lets-troubleshoot-the-init-command">Let’s troubleshoot the <code>init</code> command</h3>
<p>As you run the <code>init</code> command, Bubblewrap will need two key software packages: the <strong>Java Development Kit (JDK)</strong> and the <strong>Android SDK</strong>. It will offer to install them for you.</p>
<h4 id="heading-jdk-setup">JDK setup:</h4>
<pre><code class="lang-bash">? Do you want Bubblewrap to install the JDK (recommended)?
  (Enter <span class="hljs-string">"No"</span> to use your own JDK 17 installation) (Y/n)
</code></pre>
<p>In my case, when I let Bubblewrap install the JDK, the process downloaded the files but then failed at the "decompressing" step. If you face this same problem, don't worry! The fix is to install it manually.</p>
<ul>
<li><p>Say <strong>No</strong> to the prompt.</p>
</li>
<li><p>Download the recommended version (usually JDK 17) from a source like <a target="_blank" href="https://adoptium.net/temurin/releases/?version=17">Adoptium</a>.</p>
</li>
<li><p>Install it and set up your system's environment variables to include the JDK's <code>bin</code> path. If you’re not sure how to set environment variables, you can check out this site: <a target="_blank" href="https://www.c-sharpcorner.com/article/how-to-addedit-path-environment-variable-in-windows-11/">Set Environment Variables</a>.</p>
</li>
<li><p>When Bubblewrap asks for the path, provide it directly, such as <code>C:\java\jdk-17.0.16.8-hotspot</code>.</p>
</li>
</ul>
<h4 id="heading-android-sdk-setup">Android SDK setup:</h4>
<p>Once the JDK is set up successfully, the next step is to configure the Android SDK.</p>
<pre><code class="lang-bash">? Do you want Bubblewrap to install the Android SDK (recommended)?
  (Enter <span class="hljs-string">"No"</span> to use your own Android SDK installation) (Y/n)
</code></pre>
<p>Since I didn't have the Android SDK, I let Bubblewrap handle this by selecting <strong>Yes</strong>. I didn't face any problems here.</p>
<p>If you face any problem in setting up on Android SDK, just set it up manually and give the path, just like the JDK setup.</p>
<h2 id="heading-step-3-answer-bubblewrap-questions">Step 3 – Answer Bubblewrap Questions</h2>
<p>After the SDK is set up, Bubblewrap will ask a bunch of questions to configure your app. This information is used to create the <code>twa-manifest.json</code> file, which is the blueprint for your App.</p>
<pre><code class="lang-plaintext">Domain: Press Enter (auto-filled from your manifest)

Application name: Your full app name

Application ID: (e.g, chat.yourapp.twa)

Display mode: standalone

Orientation: portrait

Status bar color: Press Enter (accepts default)

Splash screen color: Press Enter (accepts default)

Icon URL: Press Enter (accepts default)

Include support for Play Billing?: Type Y if your app uses Google Play in-app purchases. Otherwise, N

Request geolocation permission?: Type Y if your app needs location access. Otherwise, N
</code></pre>
<p>In these questions, the important part is the key store and the key.</p>
<pre><code class="lang-plaintext">First and Last names: Your full name

Organizational Unit: Developer or anything

Organization: Your organization name

Country (2-letter code): Your country code

Password for key store: Enter a new password

Password for key: Re-enter the same password
</code></pre>
<p><strong>Note:</strong> These passwords for both the key store and key should be the same, or else it will throw an error. <strong>Refer to this issue:</strong> <a target="_blank" href="https://github.com/GoogleChromeLabs/bubblewrap/issues/713">Bubblewrap Issue</a>.</p>
<h2 id="heading-step-4-build-the-app">Step 4 – Build the App</h2>
<pre><code class="lang-bash">bubblewrap build --universalApk
</code></pre>
<p>This command starts building your application. Here, the flag <code>universalApk</code> will produce the <code>.apk</code> and <code>.abb</code>. If you’re going to publish your application in the Play Store, upload the <code>.abb</code> file to the Play Store. For our testing, we need an APK file, so this flag <code>universalApk</code> will produce both files. If we didn't give this flag, it would only give us <code>.abb</code>.</p>
<h2 id="heading-step-5-setting-up-twa-validation">Step 5 – Setting Up TWA Validation</h2>
<p>Once the build is done, you’ll get the APK. Transfer it to your phone and test it. When you open the app, you’ll see the browser address bar. This is because we haven't set up the "trust" between your app and your website yet. Let's fix that now.</p>
<p>In your frontend project, go to the <code>public</code> folder, create a new folder called <code>.well-known</code>, and inside that, create a file called <code>assetlinks.json</code>.</p>
<pre><code class="lang-bash">frontend/
├── public/
    ├── .well-known/
        └── assetlinks.json
</code></pre>
<h3 id="heading-what-is-the-well-known-folder">What is the <code>.well-known</code> folder?</h3>
<p>A well-known folder is used to store files that define configurations for protocols, as it’s used for external sources to find the validation for your website. In our case, our app checks the well-known folder from our website and verifies the validation.</p>
<p>Paste the following into <code>assetlinks.json</code>:</p>
<pre><code class="lang-json">[
  {
    <span class="hljs-attr">"relation"</span>: [<span class="hljs-string">"delegate_permission/common.handle_all_urls"</span>],
    <span class="hljs-attr">"target"</span>: {
      <span class="hljs-attr">"namespace"</span>: <span class="hljs-string">"android_app"</span>,
      <span class="hljs-attr">"package_name"</span>: <span class="hljs-string">"chat.yourapp.twa"</span>,
      <span class="hljs-attr">"sha256_cert_fingerprints"</span>: [
       <span class="hljs-string">"your_sha256_fingerprint"</span>
      ]
    }
  }
]
</code></pre>
<h3 id="heading-what-is-delegatepermissioncommonhandleallurls">What is <code>delegate_permission/common.handle_all_urls</code>?</h3>
<p>This is a special flag that opens all the links from the app instead of the domain. Simply put, it acts as a deeplink. After you install the app, if you click your website link from WhatsApp or from somewhere, it will open your app instead of opening in a browser, acting as a deeplink.</p>
<p>The <code>package_name</code> field should be the <code>packageId</code>, which you can get from your Android build folder in <code>twa-manifest.json</code>.</p>
<p>Now, get your fingerprints. Run the following command to do so:</p>
<pre><code class="lang-bash">keytool -list -v -keystore android.keystore -<span class="hljs-built_in">alias</span> android
</code></pre>
<p>The alias name should be the value that you created. Once you enter this command, it’ll ask for the key store password. Enter that, and you’ll get your <code>SHA256</code> fingerprint. Copy that and paste it into the <code>assetslinks.json</code> file in the <code>sha256_cert_fingerprints</code> array. Now push these changes to production. You can verify the validation in <a target="_blank" href="https://developers.google.com/digital-asset-links/tools/generator">Digital Asset Links</a></p>
<p>That’s it! Now you can install the app and test it.</p>
<h2 id="heading-step-6-optional-customize-the-in-app-experience"><strong>Step 6 (Optional) – Customize the In-App Experience</strong></h2>
<p>Now, additionally, there will be some cases where we want to show different content to users on the website vs the mobile app. Can we do that? Yes!</p>
<p>In your Android build folder, in <code>twa-manifest.json</code>, there will be a field called <code>startUrl</code>. If not, add it and add the value  <code>"startUrl": "/?twa=true"</code>. The <code>startUrl</code> is the entry point. I have a query parameter of value <code>twa=true</code>.</p>
<p>Run the build again with <code>bubblewrap build --universalApk</code>.</p>
<p>Now, if you open your app, it will open the app with the entry URL as <code>yourwebsitedomain.com/?twa=true</code>.</p>
<p>In your frontend:</p>
<pre><code class="lang-typescript"><span class="hljs-keyword">const</span> twaParam = queryParams.get(<span class="hljs-string">"twa"</span>);

<span class="hljs-keyword">const</span> [isTwa, setIsTwa] = useState&lt;<span class="hljs-built_in">boolean</span>&gt;(<span class="hljs-function">() =&gt;</span> {
   <span class="hljs-keyword">return</span> <span class="hljs-built_in">localStorage</span>.getItem(<span class="hljs-string">"isTwa"</span>) === <span class="hljs-string">"true"</span>;
});

useEffect(<span class="hljs-function">() =&gt;</span> {
  <span class="hljs-keyword">if</span> (twaParam === <span class="hljs-string">"true"</span>) {
    <span class="hljs-built_in">localStorage</span>.setItem(<span class="hljs-string">"isTwa"</span>, <span class="hljs-string">"true"</span>); <span class="hljs-comment">// set the value to local storage</span>
    setIsTwa(<span class="hljs-literal">true</span>);
  }
}, [twaParam]);
</code></pre>
<pre><code class="lang-typescript"> {isTwa? (
    &lt;Link to=<span class="hljs-string">"/contact"</span> className=<span class="hljs-string">"underline hover:text-primary"</span>&gt;
       Contact
    &lt;/Link&gt; 
  ) : (
     &lt;Link to=<span class="hljs-string">"/download"</span> className=<span class="hljs-string">"underline hover:text-primary"</span>&gt;
       Download App
      &lt;/Link&gt;
  )}
</code></pre>
<p>In the code above, we check for the <code>twa=true</code> query parameter in the URL. If it's present, we save that information to local storage, and then we conditionally render the content for the user.</p>
<p>That's it. We have created an App.</p>
<p>If you want to change any name, colour, or splash screen, you can change it in <code>twa-manifest.json</code> and run the build again.</p>
<h2 id="heading-wrapping-up">Wrapping Up</h2>
<p>Bubblewrap is only for Android. If you want the app to support cross-platform, there are some other platforms, like Capacitor, which I’ll write about in another article.</p>
<p>By the way, you can check out the App that I made using Bubblewrap here: <a target="_blank" href="https://strangertalk.chat/download">Stranger Talk</a>.</p>
<p>If there are any mistakes or you have any questions, contact me on <a target="_blank" href="https://www.linkedin.com/in/sanjay-r-ab6064294/">LinkedIn</a> or <a target="_blank" href="https://www.instagram.com/heheheh_pet/profilecard/?igsh=eXh3MWw4ZzZ3NTRq">Instagram</a>.</p>
<p>Thank you for reading!</p>
 ]]>
                </content:encoded>
            </item>
        
            <item>
                <title>
                    <![CDATA[ How to Implement a Service Worker with WorkBox in a Progressive Web App ]]>
                </title>
                <description>
                    <![CDATA[ Imagine having a web app that looks and feels just like a native mobile app. It launches from your home screen, runs in full-screen mode, and responds smoothly to your interactions. But here’s the surprising part: it wasn’t downloaded from an app sto... ]]>
                </description>
                <link>https://www.freecodecamp.org/news/implement-a-service-worker-with-workbox-in-a-pwa/</link>
                <guid isPermaLink="false">68595d64f486735784954566</guid>
                
                    <category>
                        <![CDATA[ PWA ]]>
                    </category>
                
                    <category>
                        <![CDATA[ HTML5 ]]>
                    </category>
                
                    <category>
                        <![CDATA[ CSS ]]>
                    </category>
                
                    <category>
                        <![CDATA[ Service Workers ]]>
                    </category>
                
                    <category>
                        <![CDATA[ workbox ]]>
                    </category>
                
                    <category>
                        <![CDATA[ progressive web apps ]]>
                    </category>
                
                    <category>
                        <![CDATA[ JavaScript ]]>
                    </category>
                
                    <category>
                        <![CDATA[ manifest ]]>
                    </category>
                
                <dc:creator>
                    <![CDATA[ Damilola Oniyide ]]>
                </dc:creator>
                <pubDate>Mon, 23 Jun 2025 13:57:56 +0000</pubDate>
                <media:content url="https://cdn.hashnode.com/res/hashnode/image/upload/v1750687028879/b12e57cb-290a-4562-8584-95eb5713a871.png" medium="image" />
                <content:encoded>
                    <![CDATA[ <p>Imagine having a web app that looks and feels just like a native mobile app. It launches from your home screen, runs in full-screen mode, and responds smoothly to your interactions. But here’s the surprising part: it wasn’t downloaded from an app store. It’s a Progressive Web App (PWA).</p>
<p>PWAs bring the power of the web to your fingertips with the experience of a mobile app. Even better? If you lose internet connection while on the go, the app can still function, showing your previously loaded data and getting updates once you’re back online.</p>
<p>In this tutorial, you’ll learn how to implement a service worker with WorkBox in a weather app using HTML, CSS, and JavaScript. We’ll start by understanding what a PWA is, the core components behind the scenes, especially service workers, and how to use Workbox to supercharge your app with offline capabilities.</p>
<h2 id="heading-table-of-contents">Table of Contents</h2>
<ul>
<li><p><a class="post-section-overview" href="#heading-what-well-cover">What We’ll Cover</a></p>
</li>
<li><p><a class="post-section-overview" href="#heading-what-is-a-progressive-web-app-pwa">What is a Progressive Web App (PWA)?</a></p>
</li>
<li><p><a class="post-section-overview" href="#heading-what-makes-a-web-app-progressive">What Makes a Web App “Progressive”?</a></p>
</li>
<li><p><a class="post-section-overview" href="#heading-components-of-a-pwa">Components of a PWA</a></p>
</li>
<li><p><a class="post-section-overview" href="#heading-what-is-a-service-worker-in-pwa">What is a Service Worker in PWA?</a></p>
</li>
<li><p><a class="post-section-overview" href="#heading-why-use-workbox-instead-of-manual-service-workers">Why Use Workbox Instead of Manual Service Workers?</a></p>
</li>
<li><p><a class="post-section-overview" href="#heading-introduction-to-workbox">Introduction to WorkBox</a></p>
</li>
<li><p><a class="post-section-overview" href="#heading-project-setup">Project Setup</a></p>
</li>
<li><p><a class="post-section-overview" href="#heading-creating-the-offline-html-structure">Creating the Offline HTML Structure</a></p>
</li>
<li><p><a class="post-section-overview" href="#heading-styling-with-css">Styling with CSS</a></p>
</li>
<li><p><a class="post-section-overview" href="#heading-how-to-set-up-appjs-and-configjs">How to Set Up app.js and config.js</a></p>
</li>
<li><p><a class="post-section-overview" href="#heading-how-to-create-a-manifest-file">How to Create a Manifest File</a></p>
</li>
<li><p><a class="post-section-overview" href="#heading-how-to-add-workbox-to-your-service-workerjs-file">How to Add WorkBox to Your service-worker.js File</a></p>
</li>
<li><p><a class="post-section-overview" href="#heading-how-to-create-your-service-worker-in-the-service-workerjs-file">How to Create your Service Worker in the service-worker.js File</a></p>
</li>
<li><p><a class="post-section-overview" href="#heading-how-to-set-up-app-installation">How to Set Up App Installation</a></p>
</li>
<li><p><a class="post-section-overview" href="#heading-how-to-install-the-weather-app">How to Install the Weather App</a></p>
</li>
<li><p><a class="post-section-overview" href="#heading-conclusion">Conclusion</a></p>
</li>
</ul>
<h2 id="heading-what-well-cover">What We’ll Cover</h2>
<ul>
<li><p><strong>Setting Up the Project:</strong> We'll build a simple weather app using HTML, CSS, and JavaScript. This approach is perfect for this tutorial because it keeps things simple and accessible while focusing on core PWA concepts without the added complexity of frameworks like React or Vue.</p>
</li>
<li><p><strong>Turning the App into a PWA:</strong> Next, we’ll walk through the concept of a Progressive Web App, covering the key features and best practices of PWAs.</p>
</li>
<li><p><strong>Implementing Service Worker via WorkBox:</strong> Finally, we’ll dive deeper into how service workers function and explore why using Workbox simplifies the process.</p>
</li>
</ul>
<p>Here’s what the final application will look like:</p>
<p><img src="https://cdn.hashnode.com/res/hashnode/image/upload/v1747272664555/8ec876bc-0881-4a63-8010-02136de91db3.png" alt="Weatherly app interface showing Tokyo weather with 24°C temperature, overcast clouds, city search functionality, and location services button" class="image--center mx-auto" width="600" height="400" loading="lazy"></p>
<h3 id="heading-audience"><strong>Audience</strong></h3>
<p>This tutorial is for web developers of all levels. Whether you're new to Progressive Web Apps (PWAs) or just starting to explore service workers, this guide will walk you through the core concepts and demonstrate why using a Google-backed library like Workbox to implement service workers can be more efficient than manual implementation.</p>
<h3 id="heading-prerequisites"><strong>Prerequisites</strong></h3>
<p>Before you begin</p>
<ol>
<li><p>Get a free API key from the <a target="_blank" href="https://openweathermap.org/">OpenWeatherAPI</a> website</p>
</li>
<li><p>Make sure you’re familiar with HTML, CSS, and JavaScript.</p>
</li>
<li><p>If you’re new to PWAs, you might want to read some introductory articles to get a quick overview.</p>
<ul>
<li><p><a target="_blank" href="https://developer.mozilla.org/en-US/docs/Web/Progressive_web_apps">Progressive web apps</a></p>
</li>
<li><p><a target="_blank" href="https://web.dev/articles/workbox">Workbox</a></p>
</li>
</ul>
</li>
</ol>
<h2 id="heading-what-is-a-progressive-web-app-pwa">What is a Progressive Web App (PWA)?</h2>
<p>A PWA is a web application that combines the best of web and mobile apps. It’s built using standard web technologies like HTML, CSS, and JavaScript, but it behaves and feels like a native mobile app on your phone or tablet.</p>
<p>Think of apps like Instagram Web, Twitter Lite, or Spotify Web Player. Even though you’re not using a native app from an app store:</p>
<ul>
<li><p>You can still scroll your feed, view media, and send messages.</p>
</li>
<li><p>It works even on slow or unstable networks.</p>
</li>
<li><p>You can “install” it on your home screen and launch it like a regular app.</p>
</li>
<li><p>You even get push notifications just like a mobile app!</p>
</li>
</ul>
<p>With PWAs, you get the reach of the web and the feel of an app without the heavy storage or installation process.</p>
<h2 id="heading-what-makes-a-web-app-progressive">What Makes a Web App “Progressive”?</h2>
<p>A PWA is not just any website. It’s built to progressively enhance the user experience, depending on their device and browser capabilities. Here are the core characteristics that define a PWA:</p>
<ul>
<li><p><strong>Responsive</strong>: Works on all screen sizes, that is, phones, tablets, and desktops.</p>
</li>
<li><p><strong>Reliable</strong>: Loads instantly, even when offline or on poor networks.</p>
</li>
<li><p><strong>Installable</strong>: Can be added to the home screen without needing an app store.</p>
</li>
<li><p><strong>Engaging</strong>: Supports features like push notifications and background sync.</p>
</li>
</ul>
<h2 id="heading-components-of-a-pwa"><strong>Compone</strong>nts of a PWA</h2>
<p>Before your web app can be considered a PWA, it must include the following:</p>
<h3 id="heading-a-web-application-manifest">A Web Application Manifest</h3>
<p>The web app manifest is a JSON file that tells the browser about your web app, how it should appear, and behave when installed on a user's device.</p>
<p>Think of it like your app’s business card. It includes details like:</p>
<ul>
<li><p><strong>App name and short name</strong> – How your app is labeled on the home screen or app list.</p>
</li>
<li><p><strong>Icons</strong> – Images used for app icons on different screen sizes and resolutions.</p>
</li>
<li><p><strong>Theme color and background color</strong> – Defines the look of your app’s UI and loading screen.</p>
</li>
<li><p><strong>Start URL</strong> – The page that opens when the app is launched.</p>
</li>
<li><p><strong>Display mode</strong> – Controls whether the app opens in a browser tab, fullscreen, or a native-like window.</p>
</li>
<li><p><strong>Screenshots</strong> – Optional preview images that show how your app looks on different devices in app stores or installation prompts.</p>
</li>
</ul>
<h3 id="heading-a-service-worker">A Service Worker</h3>
<p>This is a script that runs in the background. It handles offline behaviour, caching, background sync, and push notifications needed to make your PWA function.</p>
<p>More details about the service worker will be discussed later in this article.</p>
<h3 id="heading-https">HTTPS</h3>
<p>PWAs must be served over HTTPS. This is not optional. Here’s why:</p>
<ul>
<li><p>It protects users by ensuring secure data transfer.</p>
</li>
<li><p>It enables important features like service workers and push notifications.</p>
</li>
<li><p>Browsers won’t allow service workers to register on non-secure origins.</p>
</li>
</ul>
<p>If you're testing locally, you can use <a target="_blank" href="http://localhost"><code>localhost</code></a> (which is treated as secure), But for production, your site must have an SSL certificate.</p>
<h2 id="heading-what-is-a-service-worker-in-pwa">What is a Service Worker in PWA?</h2>
<p>In PWAs, a service worker is a JavaScript file that runs in the background, separate from your main app, and acts like a network proxy. It can:</p>
<ul>
<li><p>Cache resources and serve them offline</p>
</li>
<li><p>Intercept network requests and apply caching strategies</p>
</li>
<li><p>Handle background syncs</p>
</li>
<li><p>Manage push notifications</p>
</li>
</ul>
<p>Think of it as your app’s behind-the-scenes assistant—makes it load fast, works offline, and stays updated, even when you're not looking.</p>
<h2 id="heading-why-use-workbox-instead-of-manual-service-workers">Why Use Workbox Instead of Manual Service Workers?</h2>
<p>Service workers are essential in creating a PWA, but getting started with them can be challenging. Writing service worker code from scratch can often be tedious and prone to errors. For example, you'd need to:</p>
<ul>
<li><p>Manually configure caching strategies</p>
</li>
<li><p>Handle service worker updates</p>
</li>
<li><p>Write and maintain a lot of repetitive boilerplate code</p>
</li>
</ul>
<p>Workbox, a library from Google, makes things easier by letting developers focus on what matters, without worrying about the complicated parts of service workers.</p>
<p>However, it’s still important to understand how service workers function, since they handle some complex tasks under the hood.</p>
<p>Here are key things a service worker (with or without Workbox) does:</p>
<ul>
<li><p><strong>Install event</strong>: Set up cache</p>
</li>
<li><p><strong>Activate event</strong>: Clean up old caches</p>
</li>
<li><p><strong>Fetch event</strong>: Intercept network requests and serve from cache</p>
</li>
</ul>
<p>With Workbox, these are wrapped in easy-to-use functions.</p>
<h2 id="heading-introduction-to-workbox">Introduction to WorkBox</h2>
<p>Workbox is a collection of libraries that helps developers build efficient service workers quickly, with best practices built right in. It supports strategies like:</p>
<ul>
<li><p><code>CacheFirst</code>: Load from cache, fall back to network</p>
</li>
<li><p><code>NetworkFirst</code> : Try network, fall back to cache</p>
</li>
<li><p><code>StaleWhileRevalidate</code>: Serve from cache and update in the background</p>
</li>
</ul>
<h3 id="heading-understanding-workbox-modules">Understanding Workbox Modules</h3>
<p>Workbox is more than just a tool. It is a collection of powerful modules, each designed to simplify different parts of working with service workers. These modules are flexible and can be used in three key contexts:</p>
<ul>
<li><p><strong>Service Worker Context</strong> – Inside your service worker file, where you handle caching, routing, and other background tasks.</p>
</li>
<li><p><strong>Window Context</strong> – Inside your main application (the client-side JS), where you register and communicate with the service worker.</p>
</li>
<li><p><strong>Build Tools Integration</strong> – Tools like Webpack use Workbox to generate service worker files and precache manifests during your build process.</p>
</li>
</ul>
<p>Let’s break down some of the most popular and essential modules Workbox offers:</p>
<ol>
<li><strong>workbox-routing</strong></li>
</ol>
<p>This module handles routing network requests within your service worker. Think of it like a traffic director that listens for <code>fetch</code> events and decides what to do with them.</p>
<p><strong>Use case:</strong> Route API requests to the network while routing static asset requests to the cache.</p>
<ol start="2">
<li><strong>workbox-strategies</strong></li>
</ol>
<p>This is where caching strategies like <code>CacheFirst</code>, <code>NetworkFirst</code>, and <code>StaleWhileRevalidate</code> are used. It provides a clean and consistent API for handling how your app responds to different requests.</p>
<p><strong>Use case:</strong> Apply different caching behaviours for images, fonts, or dynamic data with minimal code.</p>
<ol start="3">
<li><strong>workbox-precaching</strong></li>
</ol>
<p>This module handles precaching by storing static assets during the service worker’s install phase. It makes it easy to cache files ahead of time and ensures that updates are managed efficiently.</p>
<p><strong>Use case:</strong> Preload essential assets (like HTML, CSS, and logo images) so your app loads instantly, even offline.</p>
<ol start="4">
<li><strong>workbox-expiration</strong></li>
</ol>
<p>It is used as a plugin alongside caching strategies. This module adds smart cache expiration. You can automatically remove old or excessive items from the cache based on how long they've been stored or how many items exist.</p>
<p><strong>Use case:</strong> Keep your cache size under control without manually tracking and deleting outdated files.</p>
<p><strong>workbox-window</strong></p>
<p>This module is designed for the browser (window) side of your app. It simplifies service worker registration and allows you to communicate with the service worker from your page easily.</p>
<p><strong>Use case:</strong> Detect when a new service worker is available and prompt the user to refresh the app to update.</p>
<p>You can use WorkBox via:</p>
<ul>
<li><p>npm</p>
</li>
<li><p>CDN (which we'll use here for simplicity)</p>
</li>
</ul>
<h2 id="heading-project-setup">Project Setup</h2>
<p>Let's start by creating our project structure:</p>
<pre><code class="lang-plaintext">weather-pwa/
├── index.html
├── style.css
├── js/
│   ├── app.js
│   └── install.js
├── service-worker.js
├── images/
│   └── [your image files and folders here]
├── manifest.json
├── config.js  
└── offline.html
</code></pre>
<h3 id="heading-the-html-structure">The HTML Structure</h3>
<p>First, let's build our <code>index.html</code> file:</p>
<pre><code class="lang-xml">
<span class="hljs-meta">&lt;!DOCTYPE <span class="hljs-meta-keyword">html</span>&gt;</span>
<span class="hljs-tag">&lt;<span class="hljs-name">html</span> <span class="hljs-attr">lang</span>=<span class="hljs-string">"en"</span>&gt;</span>
    <span class="hljs-meta">&lt;!DOCTYPE <span class="hljs-meta-keyword">html</span>&gt;</span>
    <span class="hljs-tag">&lt;<span class="hljs-name">html</span> <span class="hljs-attr">lang</span>=<span class="hljs-string">"en"</span>&gt;</span>
    <span class="hljs-tag">&lt;<span class="hljs-name">head</span>&gt;</span>
        <span class="hljs-tag">&lt;<span class="hljs-name">meta</span> <span class="hljs-attr">charset</span>=<span class="hljs-string">"UTF-8"</span> /&gt;</span>
        <span class="hljs-tag">&lt;<span class="hljs-name">meta</span> <span class="hljs-attr">name</span>=<span class="hljs-string">"viewport"</span> <span class="hljs-attr">content</span>=<span class="hljs-string">"width=device-width, initial-scale=1.0"</span> /&gt;</span>
        <span class="hljs-tag">&lt;<span class="hljs-name">link</span> <span class="hljs-attr">rel</span>=<span class="hljs-string">"icon"</span> <span class="hljs-attr">href</span>=<span class="hljs-string">"/images/logo.png"</span> <span class="hljs-attr">type</span>=<span class="hljs-string">"image/png"</span>&gt;</span>
        <span class="hljs-tag">&lt;<span class="hljs-name">meta</span> <span class="hljs-attr">name</span>=<span class="hljs-string">"description"</span> <span class="hljs-attr">content</span>=<span class="hljs-string">"Simple Weather Progressive Web App"</span> /&gt;</span>
        <span class="hljs-tag">&lt;<span class="hljs-name">link</span> <span class="hljs-attr">rel</span>=<span class="hljs-string">"stylesheet"</span> <span class="hljs-attr">href</span>=<span class="hljs-string">"/styles.css"</span> /&gt;</span>
        <span class="hljs-tag">&lt;<span class="hljs-name">title</span>&gt;</span>Weatherly<span class="hljs-tag">&lt;/<span class="hljs-name">title</span>&gt;</span>
    <span class="hljs-tag">&lt;/<span class="hljs-name">head</span>&gt;</span>


<span class="hljs-tag">&lt;<span class="hljs-name">body</span>&gt;</span>
    <span class="hljs-tag">&lt;<span class="hljs-name">header</span> <span class="hljs-attr">class</span>=<span class="hljs-string">"header"</span>&gt;</span>
        <span class="hljs-tag">&lt;<span class="hljs-name">img</span> <span class="hljs-attr">loading</span>=<span class="hljs-string">"lazy"</span> <span class="hljs-attr">class</span>=<span class="hljs-string">"logo"</span> <span class="hljs-attr">src</span>=<span class="hljs-string">"images/logo.png"</span> <span class="hljs-attr">alt</span>=<span class="hljs-string">"Weatherly Logo"</span>&gt;</span>
        <span class="hljs-tag">&lt;<span class="hljs-name">h1</span>&gt;</span>Weatherly<span class="hljs-tag">&lt;/<span class="hljs-name">h1</span>&gt;</span>
    <span class="hljs-tag">&lt;/<span class="hljs-name">header</span>&gt;</span>

    <span class="hljs-tag">&lt;<span class="hljs-name">main</span> <span class="hljs-attr">class</span>=<span class="hljs-string">"main"</span>&gt;</span>
        <span class="hljs-tag">&lt;<span class="hljs-name">div</span> <span class="hljs-attr">class</span>=<span class="hljs-string">"weather-card"</span>&gt;</span>
            <span class="hljs-tag">&lt;<span class="hljs-name">div</span> <span class="hljs-attr">class</span>=<span class="hljs-string">"location-container"</span>&gt;</span>
                <span class="hljs-tag">&lt;<span class="hljs-name">input</span> <span class="hljs-attr">type</span>=<span class="hljs-string">"text"</span> <span class="hljs-attr">id</span>=<span class="hljs-string">"location-input"</span> <span class="hljs-attr">placeholder</span>=<span class="hljs-string">"Enter city name"</span>&gt;</span>
                <span class="hljs-tag">&lt;<span class="hljs-name">button</span> <span class="hljs-attr">id</span>=<span class="hljs-string">"search-btn"</span>&gt;</span>Search<span class="hljs-tag">&lt;/<span class="hljs-name">button</span>&gt;</span>
                <span class="hljs-tag">&lt;<span class="hljs-name">button</span> <span class="hljs-attr">id</span>=<span class="hljs-string">"locationBtn"</span>&gt;</span>📍 Use My Location<span class="hljs-tag">&lt;/<span class="hljs-name">button</span>&gt;</span>
                <span class="hljs-tag">&lt;<span class="hljs-name">button</span> <span class="hljs-attr">id</span>=<span class="hljs-string">"installBtn"</span> <span class="hljs-attr">style</span>=<span class="hljs-string">"display: none;"</span>&gt;</span>Install App<span class="hljs-tag">&lt;/<span class="hljs-name">button</span>&gt;</span>
            <span class="hljs-tag">&lt;/<span class="hljs-name">div</span>&gt;</span>

            <span class="hljs-tag">&lt;<span class="hljs-name">div</span> <span class="hljs-attr">id</span>=<span class="hljs-string">"offline-message"</span> <span class="hljs-attr">class</span>=<span class="hljs-string">"offline-message"</span>&gt;</span>
                You are currently offline. Weather data may not be up-to-date.
            <span class="hljs-tag">&lt;/<span class="hljs-name">div</span>&gt;</span>


            <span class="hljs-tag">&lt;<span class="hljs-name">div</span> <span class="hljs-attr">class</span>=<span class="hljs-string">"error"</span>&gt;</span>
                <span class="hljs-tag">&lt;<span class="hljs-name">p</span> <span class="hljs-attr">id</span>=<span class="hljs-string">"error-message"</span>&gt;</span><span class="hljs-tag">&lt;/<span class="hljs-name">p</span>&gt;</span>
            <span class="hljs-tag">&lt;/<span class="hljs-name">div</span>&gt;</span>

            <span class="hljs-tag">&lt;<span class="hljs-name">div</span> <span class="hljs-attr">id</span>=<span class="hljs-string">"weather-container"</span> <span class="hljs-attr">class</span>=<span class="hljs-string">"weather-container"</span>&gt;</span>
                <span class="hljs-tag">&lt;<span class="hljs-name">h3</span>&gt;</span>Your last searched location weather:<span class="hljs-tag">&lt;/<span class="hljs-name">h3</span>&gt;</span>
                <span class="hljs-tag">&lt;<span class="hljs-name">div</span> <span class="hljs-attr">class</span>=<span class="hljs-string">"location-info"</span>&gt;</span>
                    <span class="hljs-tag">&lt;<span class="hljs-name">h2</span> <span class="hljs-attr">id</span>=<span class="hljs-string">"city"</span>&gt;</span><span class="hljs-tag">&lt;/<span class="hljs-name">h2</span>&gt;</span>
                    <span class="hljs-tag">&lt;<span class="hljs-name">p</span> <span class="hljs-attr">id</span>=<span class="hljs-string">"date"</span>&gt;</span><span class="hljs-tag">&lt;/<span class="hljs-name">p</span>&gt;</span>
                <span class="hljs-tag">&lt;/<span class="hljs-name">div</span>&gt;</span>

                <span class="hljs-tag">&lt;<span class="hljs-name">div</span> <span class="hljs-attr">class</span>=<span class="hljs-string">"current-weather"</span>&gt;</span>
                    <span class="hljs-tag">&lt;<span class="hljs-name">img</span> <span class="hljs-attr">loading</span>=<span class="hljs-string">"lazy"</span> <span class="hljs-attr">id</span>=<span class="hljs-string">"weather-icon"</span> <span class="hljs-attr">src</span>=<span class="hljs-string">""</span> <span class="hljs-attr">alt</span>=<span class="hljs-string">"Weather icon"</span>&gt;</span>
                    <span class="hljs-tag">&lt;<span class="hljs-name">div</span> <span class="hljs-attr">class</span>=<span class="hljs-string">"temperature-container"</span>&gt;</span>
                        <span class="hljs-tag">&lt;<span class="hljs-name">h3</span> <span class="hljs-attr">id</span>=<span class="hljs-string">"temperature"</span>&gt;</span><span class="hljs-tag">&lt;/<span class="hljs-name">h3</span>&gt;</span>
                        <span class="hljs-tag">&lt;<span class="hljs-name">p</span> <span class="hljs-attr">id</span>=<span class="hljs-string">"weather-description"</span>&gt;</span><span class="hljs-tag">&lt;/<span class="hljs-name">p</span>&gt;</span>
                    <span class="hljs-tag">&lt;/<span class="hljs-name">div</span>&gt;</span>
                <span class="hljs-tag">&lt;/<span class="hljs-name">div</span>&gt;</span>

                <span class="hljs-tag">&lt;<span class="hljs-name">div</span> <span class="hljs-attr">class</span>=<span class="hljs-string">"weather-details"</span>&gt;</span>
                    <span class="hljs-tag">&lt;<span class="hljs-name">div</span> <span class="hljs-attr">class</span>=<span class="hljs-string">"detail"</span>&gt;</span>
                        <span class="hljs-tag">&lt;<span class="hljs-name">img</span> <span class="hljs-attr">loading</span>=<span class="hljs-string">"lazy"</span> <span class="hljs-attr">id</span>=<span class="hljs-string">"humidity-icon"</span> <span class="hljs-attr">src</span>=<span class="hljs-string">"/images/humidity.png"</span> <span class="hljs-attr">alt</span>=<span class="hljs-string">"Humidity icon"</span>&gt;</span>
                        <span class="hljs-tag">&lt;<span class="hljs-name">span</span> <span class="hljs-attr">class</span>=<span class="hljs-string">"label"</span>&gt;</span>Humidity<span class="hljs-tag">&lt;/<span class="hljs-name">span</span>&gt;</span>
                        <span class="hljs-tag">&lt;<span class="hljs-name">span</span> <span class="hljs-attr">id</span>=<span class="hljs-string">"humidity"</span> <span class="hljs-attr">class</span>=<span class="hljs-string">"value"</span>&gt;</span><span class="hljs-tag">&lt;/<span class="hljs-name">span</span>&gt;</span>
                    <span class="hljs-tag">&lt;/<span class="hljs-name">div</span>&gt;</span>
                    <span class="hljs-tag">&lt;<span class="hljs-name">div</span> <span class="hljs-attr">class</span>=<span class="hljs-string">"detail"</span>&gt;</span>
                        <span class="hljs-tag">&lt;<span class="hljs-name">img</span> <span class="hljs-attr">loading</span>=<span class="hljs-string">"lazy"</span> <span class="hljs-attr">id</span>=<span class="hljs-string">"wind-icon"</span> <span class="hljs-attr">src</span>=<span class="hljs-string">"/images/wind.png"</span> <span class="hljs-attr">alt</span>=<span class="hljs-string">"Wind icon"</span>&gt;</span>
                        <span class="hljs-tag">&lt;<span class="hljs-name">span</span> <span class="hljs-attr">class</span>=<span class="hljs-string">"label"</span>&gt;</span>Wind<span class="hljs-tag">&lt;/<span class="hljs-name">span</span>&gt;</span>
                        <span class="hljs-tag">&lt;<span class="hljs-name">span</span> <span class="hljs-attr">id</span>=<span class="hljs-string">"wind"</span> <span class="hljs-attr">class</span>=<span class="hljs-string">"value"</span>&gt;</span><span class="hljs-tag">&lt;/<span class="hljs-name">span</span>&gt;</span>
                    <span class="hljs-tag">&lt;/<span class="hljs-name">div</span>&gt;</span>
                <span class="hljs-tag">&lt;/<span class="hljs-name">div</span>&gt;</span>
            <span class="hljs-tag">&lt;/<span class="hljs-name">div</span>&gt;</span>

            <span class="hljs-comment">&lt;!-- Your location weather --&gt;</span>
            <span class="hljs-tag">&lt;<span class="hljs-name">div</span> <span class="hljs-attr">class</span>=<span class="hljs-string">"location-weather"</span>&gt;</span>
                <span class="hljs-tag">&lt;<span class="hljs-name">h3</span>&gt;</span>Your location's weather:<span class="hljs-tag">&lt;/<span class="hljs-name">h3</span>&gt;</span>
                <span class="hljs-tag">&lt;<span class="hljs-name">div</span> <span class="hljs-attr">class</span>=<span class="hljs-string">"weather-info"</span> <span class="hljs-attr">id</span>=<span class="hljs-string">"weatherInfo"</span>&gt;</span>

                <span class="hljs-tag">&lt;/<span class="hljs-name">div</span>&gt;</span>
            <span class="hljs-tag">&lt;/<span class="hljs-name">div</span>&gt;</span>
        <span class="hljs-tag">&lt;/<span class="hljs-name">div</span>&gt;</span>
    <span class="hljs-tag">&lt;/<span class="hljs-name">main</span>&gt;</span>

    <span class="hljs-tag">&lt;<span class="hljs-name">footer</span>&gt;</span>
        <span class="hljs-tag">&lt;<span class="hljs-name">p</span>&gt;</span>Made with ❤️ by <span class="hljs-tag">&lt;<span class="hljs-name">a</span> <span class="hljs-attr">href</span>=<span class="hljs-string">"www.linkedin.com/in/damilola-oniyide"</span>&gt;</span>Damilola Oniyide<span class="hljs-tag">&lt;/<span class="hljs-name">a</span>&gt;</span>
    <span class="hljs-tag">&lt;/<span class="hljs-name">footer</span>&gt;</span>
    <span class="hljs-tag">&lt;<span class="hljs-name">script</span> <span class="hljs-attr">type</span>=<span class="hljs-string">"module"</span> <span class="hljs-attr">src</span>=<span class="hljs-string">"/js/app.js"</span> <span class="hljs-attr">defer</span>&gt;</span><span class="hljs-tag">&lt;/<span class="hljs-name">script</span>&gt;</span>
<span class="hljs-tag">&lt;/<span class="hljs-name">body</span>&gt;</span>
<span class="hljs-tag">&lt;/<span class="hljs-name">html</span>&gt;</span>
</code></pre>
<h2 id="heading-creating-the-offline-html-structure">Creating the Offline HTML Structure</h2>
<p>The <code>offline.html</code> is the page that users will see when they lose network connection and try to navigate to a page that isn’t cached.</p>
<pre><code class="lang-xml"><span class="hljs-meta">&lt;!DOCTYPE <span class="hljs-meta-keyword">html</span>&gt;</span>
<span class="hljs-tag">&lt;<span class="hljs-name">html</span> <span class="hljs-attr">lang</span>=<span class="hljs-string">"en"</span>&gt;</span>
<span class="hljs-tag">&lt;<span class="hljs-name">head</span>&gt;</span>
  <span class="hljs-tag">&lt;<span class="hljs-name">meta</span> <span class="hljs-attr">charset</span>=<span class="hljs-string">"UTF-8"</span>&gt;</span>
  <span class="hljs-tag">&lt;<span class="hljs-name">meta</span> <span class="hljs-attr">name</span>=<span class="hljs-string">"viewport"</span> <span class="hljs-attr">content</span>=<span class="hljs-string">"width=device-width, initial-scale=1.0"</span>&gt;</span>
  <span class="hljs-tag">&lt;<span class="hljs-name">meta</span> <span class="hljs-attr">name</span>=<span class="hljs-string">"theme-color"</span> <span class="hljs-attr">content</span>=<span class="hljs-string">"#2196f3"</span>&gt;</span>
  <span class="hljs-tag">&lt;<span class="hljs-name">title</span>&gt;</span>Weatherly - Offline<span class="hljs-tag">&lt;/<span class="hljs-name">title</span>&gt;</span>
  <span class="hljs-tag">&lt;<span class="hljs-name">link</span> <span class="hljs-attr">rel</span>=<span class="hljs-string">"stylesheet"</span> <span class="hljs-attr">href</span>=<span class="hljs-string">"/styles.css"</span>&gt;</span>
  <span class="hljs-tag">&lt;<span class="hljs-name">style</span>&gt;</span><span class="css">
    <span class="hljs-selector-class">.offline-icon</span> {
      <span class="hljs-attribute">font-size</span>: <span class="hljs-number">5rem</span>;
      <span class="hljs-attribute">margin-bottom</span>: <span class="hljs-number">1.5rem</span>;
      <span class="hljs-attribute">color</span>: <span class="hljs-number">#2196f3</span>;
    }

    <span class="hljs-selector-class">.offline-message</span> {
      <span class="hljs-attribute">font-size</span>: <span class="hljs-number">1.5rem</span>;
      <span class="hljs-attribute">margin-bottom</span>: <span class="hljs-number">1.5rem</span>;
    }

    <span class="hljs-selector-class">.offline-subtext</span> {
      <span class="hljs-attribute">font-size</span>: <span class="hljs-number">1rem</span>;
      <span class="hljs-attribute">margin-bottom</span>: <span class="hljs-number">2rem</span>;
      <span class="hljs-attribute">color</span>: <span class="hljs-number">#666</span>;
    }

    <span class="hljs-selector-class">.retry-button</span> {
      <span class="hljs-attribute">padding</span>: <span class="hljs-number">0.75rem</span> <span class="hljs-number">1.5rem</span>;
      <span class="hljs-attribute">background-color</span>: <span class="hljs-number">#2196f3</span>;
      <span class="hljs-attribute">color</span>: white;
      <span class="hljs-attribute">border</span>: none;
      <span class="hljs-attribute">border-radius</span>: <span class="hljs-number">12px</span>;
      <span class="hljs-attribute">font-size</span>: <span class="hljs-number">1rem</span>;
      <span class="hljs-attribute">cursor</span>: pointer;
      <span class="hljs-attribute">transition</span>: background-color <span class="hljs-number">0.3s</span>;
    }

    <span class="hljs-selector-class">.retry-button</span><span class="hljs-selector-pseudo">:hover</span> {
      <span class="hljs-attribute">background-color</span>: <span class="hljs-number">#2980b9</span>;
    }
  </span><span class="hljs-tag">&lt;/<span class="hljs-name">style</span>&gt;</span>
<span class="hljs-tag">&lt;/<span class="hljs-name">head</span>&gt;</span>
<span class="hljs-tag">&lt;<span class="hljs-name">body</span>&gt;</span>
  <span class="hljs-tag">&lt;<span class="hljs-name">header</span>&gt;</span>
    <span class="hljs-tag">&lt;<span class="hljs-name">h1</span>&gt;</span>Weatherly<span class="hljs-tag">&lt;/<span class="hljs-name">h1</span>&gt;</span>
  <span class="hljs-tag">&lt;/<span class="hljs-name">header</span>&gt;</span>

  <span class="hljs-tag">&lt;<span class="hljs-name">main</span>&gt;</span>
    <span class="hljs-tag">&lt;<span class="hljs-name">div</span> <span class="hljs-attr">class</span>=<span class="hljs-string">"app-container"</span>&gt;</span>
      <span class="hljs-tag">&lt;<span class="hljs-name">div</span> <span class="hljs-attr">class</span>=<span class="hljs-string">"weather-card"</span>&gt;</span>
        <span class="hljs-tag">&lt;<span class="hljs-name">div</span> <span class="hljs-attr">class</span>=<span class="hljs-string">"offline-container"</span>&gt;</span>
          <span class="hljs-tag">&lt;<span class="hljs-name">div</span> <span class="hljs-attr">class</span>=<span class="hljs-string">"offline-icon"</span>&gt;</span>
            <span class="hljs-tag">&lt;<span class="hljs-name">svg</span> <span class="hljs-attr">xmlns</span>=<span class="hljs-string">"http://www.w3.org/2000/svg"</span> <span class="hljs-attr">width</span>=<span class="hljs-string">"1em"</span> <span class="hljs-attr">height</span>=<span class="hljs-string">"1em"</span> <span class="hljs-attr">fill</span>=<span class="hljs-string">"currentColor"</span> <span class="hljs-attr">viewBox</span>=<span class="hljs-string">"0 0 16 16"</span>&gt;</span>
              <span class="hljs-tag">&lt;<span class="hljs-name">path</span> <span class="hljs-attr">d</span>=<span class="hljs-string">"M8 15A7 7 0 1 1 8 1a7 7 0 0 1 0 14zm0 1A8 8 0 1 0 8 0a8 8 0 0 0 0 16z"</span>/&gt;</span>
              <span class="hljs-tag">&lt;<span class="hljs-name">path</span> <span class="hljs-attr">d</span>=<span class="hljs-string">"M7 6.5C7 7.328 6.552 8 6 8s-1-.672-1-1.5S5.448 5 6 5s1 .672 1 1.5zm-2.715 5.933a.5.5 0 0 1-.183-.683A4.498 4.498 0 0 1 8 9.5a4.5 4.5 0 0 1 3.898 2.25.5.5 0 0 1-.866.5A3.498 3.498 0 0 0 8 10.5a3.498 3.498 0 0 0-3.032 1.75.5.5 0 0 1-.683.183zM10 8c-.552 0-1-.672-1-1.5S9.448 5 10 5s1 .672 1 1.5S10.552 8 10 8z"</span>/&gt;</span>
            <span class="hljs-tag">&lt;/<span class="hljs-name">svg</span>&gt;</span>
          <span class="hljs-tag">&lt;/<span class="hljs-name">div</span>&gt;</span>
          <span class="hljs-tag">&lt;<span class="hljs-name">h2</span> <span class="hljs-attr">class</span>=<span class="hljs-string">"offline-message"</span>&gt;</span>You're offline<span class="hljs-tag">&lt;/<span class="hljs-name">h2</span>&gt;</span>
          <span class="hljs-tag">&lt;<span class="hljs-name">p</span> <span class="hljs-attr">class</span>=<span class="hljs-string">"offline-subtext"</span>&gt;</span>Please check your internet connection and try again.<span class="hljs-tag">&lt;/<span class="hljs-name">p</span>&gt;</span>
          <span class="hljs-tag">&lt;<span class="hljs-name">button</span> <span class="hljs-attr">class</span>=<span class="hljs-string">"retry-button"</span> <span class="hljs-attr">onclick</span>=<span class="hljs-string">"window.location.href='/'"</span>&gt;</span>Retry<span class="hljs-tag">&lt;/<span class="hljs-name">button</span>&gt;</span>
        <span class="hljs-tag">&lt;/<span class="hljs-name">div</span>&gt;</span>
      <span class="hljs-tag">&lt;/<span class="hljs-name">div</span>&gt;</span>
    <span class="hljs-tag">&lt;/<span class="hljs-name">div</span>&gt;</span>
  <span class="hljs-tag">&lt;/<span class="hljs-name">main</span>&gt;</span>

  <span class="hljs-tag">&lt;<span class="hljs-name">footer</span>&gt;</span>
    <span class="hljs-tag">&lt;<span class="hljs-name">p</span>&gt;</span>Made with ❤️ by Damilola Oniyide<span class="hljs-tag">&lt;/<span class="hljs-name">p</span>&gt;</span>
  <span class="hljs-tag">&lt;/<span class="hljs-name">footer</span>&gt;</span>
<span class="hljs-tag">&lt;/<span class="hljs-name">body</span>&gt;</span>
<span class="hljs-tag">&lt;/<span class="hljs-name">html</span>&gt;</span>
</code></pre>
<h2 id="heading-styling-with-css">Styling with CSS</h2>
<p>Now, let's create our <code>style.css</code> file for a responsive and user-friendly design:</p>
<pre><code class="lang-css">* {
    <span class="hljs-attribute">margin</span>: <span class="hljs-number">0</span>;
    <span class="hljs-attribute">padding</span>: <span class="hljs-number">0</span>;
    <span class="hljs-attribute">box-sizing</span>: border-box;
}

<span class="hljs-selector-tag">body</span> {
    <span class="hljs-attribute">font-family</span>: <span class="hljs-string">'Segoe UI'</span>, Tahoma, Geneva, Verdana, sans-serif;
    <span class="hljs-attribute">background-color</span>: <span class="hljs-number">#f5f5f5</span>;
    <span class="hljs-attribute">color</span>: <span class="hljs-number">#333</span>;
    <span class="hljs-attribute">line-height</span>: <span class="hljs-number">1.6</span>;
}

<span class="hljs-selector-class">.header</span> {
    <span class="hljs-attribute">background-color</span>: <span class="hljs-number">#2196f3</span>;
    <span class="hljs-attribute">color</span>: white;
    <span class="hljs-attribute">padding</span>: <span class="hljs-number">1rem</span>;
    <span class="hljs-attribute">display</span>: flex;
    <span class="hljs-attribute">justify-content</span>: center;
    <span class="hljs-attribute">align-items</span>: center;
    <span class="hljs-attribute">box-shadow</span>: <span class="hljs-number">0</span> <span class="hljs-number">2px</span> <span class="hljs-number">5px</span> <span class="hljs-built_in">rgba</span>(<span class="hljs-number">0</span>, <span class="hljs-number">0</span>, <span class="hljs-number">0</span>, <span class="hljs-number">0.1</span>);
}

<span class="hljs-selector-class">.header</span> <span class="hljs-selector-tag">h1</span> {
    <span class="hljs-attribute">font-size</span>: <span class="hljs-number">1.5rem</span>;
}


<span class="hljs-selector-class">.header</span> <span class="hljs-selector-tag">img</span> {
    <span class="hljs-attribute">width</span>: <span class="hljs-number">55px</span>;
    <span class="hljs-attribute">height</span>: <span class="hljs-number">55px</span>;
    <span class="hljs-attribute">border</span>: <span class="hljs-number">#ffff</span> <span class="hljs-number">1px</span> solid;
    <span class="hljs-attribute">margin-right</span>: <span class="hljs-number">4px</span>;
    <span class="hljs-attribute">border-radius</span>: <span class="hljs-number">10%</span>;
}


<span class="hljs-selector-class">.main</span> {
    <span class="hljs-attribute">padding</span>: <span class="hljs-number">1rem</span>;
    <span class="hljs-attribute">max-width</span>: auto;
    <span class="hljs-attribute">margin</span>: <span class="hljs-number">0</span> auto;
}

<span class="hljs-selector-class">.weather-card</span> {
    <span class="hljs-attribute">background-color</span>: white;
    <span class="hljs-attribute">border-radius</span>: <span class="hljs-number">8px</span>;
    <span class="hljs-attribute">box-shadow</span>: <span class="hljs-number">0</span> <span class="hljs-number">2px</span> <span class="hljs-number">10px</span> <span class="hljs-built_in">rgba</span>(<span class="hljs-number">0</span>, <span class="hljs-number">0</span>, <span class="hljs-number">0</span>, <span class="hljs-number">0.1</span>);
    <span class="hljs-attribute">padding</span>: <span class="hljs-number">1.5rem</span> <span class="hljs-number">3rem</span>;
    <span class="hljs-attribute">margin-top</span>: <span class="hljs-number">1rem</span>;
}

<span class="hljs-comment">/* Location input styles */</span>
<span class="hljs-selector-class">.location-container</span> {
    <span class="hljs-attribute">display</span>: flex;
    <span class="hljs-attribute">margin-bottom</span>: <span class="hljs-number">1.5rem</span>;
    <span class="hljs-attribute">justify-content</span>: center;
}

<span class="hljs-selector-id">#location-input</span> {
    <span class="hljs-attribute">flex</span>: <span class="hljs-number">1</span>;
    <span class="hljs-attribute">padding</span>: <span class="hljs-number">0.75rem</span>;
    <span class="hljs-attribute">border</span>: <span class="hljs-number">1px</span> solid <span class="hljs-number">#ddd</span>;
    <span class="hljs-attribute">border-radius</span>: <span class="hljs-number">4px</span> <span class="hljs-number">0</span> <span class="hljs-number">0</span> <span class="hljs-number">4px</span>;
    <span class="hljs-attribute">font-size</span>: <span class="hljs-number">1rem</span>;
    <span class="hljs-attribute">max-width</span>: <span class="hljs-number">240px</span>;
}

<span class="hljs-selector-id">#location-input</span><span class="hljs-selector-pseudo">:focus</span> {
    <span class="hljs-attribute">outline</span>: none;
    <span class="hljs-attribute">border-color</span>: <span class="hljs-number">#2196f3</span>;
}
<span class="hljs-selector-id">#location-input</span><span class="hljs-selector-pseudo">::placeholder</span> {
    <span class="hljs-attribute">color</span>: <span class="hljs-number">#999</span>;
}   

<span class="hljs-selector-id">#search-btn</span>, <span class="hljs-selector-id">#locationBtn</span> {
    <span class="hljs-attribute">background-color</span>: <span class="hljs-number">#2196f3</span>;
    <span class="hljs-attribute">color</span>: white;
    <span class="hljs-attribute">border</span>: none;
    <span class="hljs-attribute">padding</span>: <span class="hljs-number">0.75rem</span> <span class="hljs-number">1rem</span>;
    <span class="hljs-attribute">border-radius</span>: <span class="hljs-number">0</span> <span class="hljs-number">4px</span> <span class="hljs-number">4px</span> <span class="hljs-number">0</span>;
    <span class="hljs-attribute">cursor</span>: pointer;
    <span class="hljs-attribute">font-size</span>: <span class="hljs-number">1rem</span>;
    <span class="hljs-attribute">margin-right</span>: <span class="hljs-number">2.5px</span>;
}


<span class="hljs-selector-id">#installBtn</span> {
    <span class="hljs-attribute">background-color</span>: <span class="hljs-number">#2196f3</span>;
    <span class="hljs-attribute">color</span>: white;
    <span class="hljs-attribute">border</span>: none;
    <span class="hljs-attribute">padding</span>: <span class="hljs-number">0.75rem</span> <span class="hljs-number">1rem</span>;
    <span class="hljs-attribute">border-radius</span>: <span class="hljs-number">4px</span>;
    <span class="hljs-attribute">cursor</span>: pointer;
    <span class="hljs-attribute">font-size</span>: <span class="hljs-number">1rem</span>;

}

<span class="hljs-selector-id">#search-btn</span><span class="hljs-selector-pseudo">:focus</span>, <span class="hljs-selector-id">#locationBtn</span><span class="hljs-selector-pseudo">:focus</span>, <span class="hljs-selector-id">#installBtn</span><span class="hljs-selector-pseudo">:focus</span> {
    <span class="hljs-attribute">outline</span>: none;
    <span class="hljs-attribute">box-shadow</span>: <span class="hljs-number">0</span> <span class="hljs-number">0</span> <span class="hljs-number">5px</span> <span class="hljs-built_in">rgba</span>(<span class="hljs-number">33</span>, <span class="hljs-number">150</span>, <span class="hljs-number">243</span>, <span class="hljs-number">0.5</span>);
}
<span class="hljs-selector-id">#search-btn</span><span class="hljs-selector-pseudo">:hover</span>, <span class="hljs-selector-id">#locationBtn</span><span class="hljs-selector-pseudo">:hover</span>, <span class="hljs-selector-id">#installBtn</span><span class="hljs-selector-pseudo">:hover</span> {
    <span class="hljs-attribute">background-color</span>: <span class="hljs-number">#1976d2</span>;
}

<span class="hljs-selector-class">.error</span>, <span class="hljs-selector-class">.loading</span> {
    <span class="hljs-attribute">text-align</span>: center;
    <span class="hljs-attribute">font-weight</span>: bold;
    <span class="hljs-attribute">font-size</span>: <span class="hljs-number">14px</span>;
    <span class="hljs-attribute">margin-top</span>: <span class="hljs-number">10px</span>;
    <span class="hljs-attribute">display</span>: none;;
}

<span class="hljs-selector-class">.error-message</span> {
    <span class="hljs-attribute">color</span>: <span class="hljs-number">#d32f2f</span>;

}
<span class="hljs-comment">/* Weather display styles */</span>
<span class="hljs-selector-class">.weather-container</span> {
    <span class="hljs-attribute">display</span>: none 
}

<span class="hljs-selector-id">#weather-icon</span> {
    <span class="hljs-attribute">width</span>: <span class="hljs-number">1000px</span>; 
    <span class="hljs-attribute">height</span>: <span class="hljs-number">100px</span>;
  }

<span class="hljs-selector-class">.current-weather</span>{
    <span class="hljs-attribute">margin-bottom</span>: <span class="hljs-number">2rem</span>;
    <span class="hljs-attribute">display</span>: flex;
    <span class="hljs-attribute">justify-content</span>: center;
}

<span class="hljs-selector-class">.location-weather</span>{
    <span class="hljs-attribute">margin-top</span>: <span class="hljs-number">2rem</span>;
    <span class="hljs-attribute">display</span>: flex;
    <span class="hljs-attribute">justify-content</span>: center;
    <span class="hljs-attribute">flex-direction</span>: column;
}


<span class="hljs-selector-id">#weather-icon</span> {
    <span class="hljs-attribute">width</span>: <span class="hljs-number">80px</span>;
    <span class="hljs-attribute">height</span>: <span class="hljs-number">80px</span>;
    <span class="hljs-attribute">margin-right</span>: <span class="hljs-number">1rem</span>;
}

<span class="hljs-selector-class">.location-info</span> {
    <span class="hljs-attribute">margin-bottom</span>: <span class="hljs-number">1rem</span>;
    <span class="hljs-attribute">display</span>: flex;
    <span class="hljs-attribute">flex-direction</span>: column;
    <span class="hljs-attribute">justify-content</span>: center;
    <span class="hljs-attribute">align-items</span>: center;
}

<span class="hljs-selector-class">.location-info</span> <span class="hljs-selector-tag">h2</span>,  <span class="hljs-selector-class">.current-weather</span> <span class="hljs-selector-tag">h3</span>, <span class="hljs-selector-class">.weather-container</span> <span class="hljs-selector-tag">h3</span>, <span class="hljs-selector-class">.location-weather</span> <span class="hljs-selector-tag">h3</span> {
    <span class="hljs-attribute">font-size</span>: <span class="hljs-number">1.8rem</span>;
    <span class="hljs-attribute">margin-bottom</span>: <span class="hljs-number">0.25rem</span>;
}



<span class="hljs-selector-class">.location-info</span> <span class="hljs-selector-tag">p</span>, <span class="hljs-selector-class">.current-weather</span> <span class="hljs-selector-tag">p</span> {
    <span class="hljs-attribute">color</span>: <span class="hljs-number">#666</span>;
    <span class="hljs-attribute">font-size</span>: <span class="hljs-number">1.4rem</span>;
}

<span class="hljs-selector-class">.temperature-container</span> {
    <span class="hljs-attribute">display</span>: flex;
    <span class="hljs-attribute">flex-direction</span>: column;
    <span class="hljs-attribute">justify-content</span>: center;
    <span class="hljs-attribute">align-items</span>: center;
    <span class="hljs-attribute">margin-bottom</span>: <span class="hljs-number">1rem</span>;
}

<span class="hljs-selector-class">.temperature-container</span> <span class="hljs-selector-tag">h3</span> {
    <span class="hljs-attribute">font-size</span>: <span class="hljs-number">2.5rem</span>;
    <span class="hljs-attribute">margin-bottom</span>: <span class="hljs-number">0.25rem</span>;
}

<span class="hljs-selector-class">.temperature-container</span> <span class="hljs-selector-tag">p</span> {
    <span class="hljs-attribute">color</span>: <span class="hljs-number">#666</span>;
    <span class="hljs-attribute">text-transform</span>: capitalize;
}

<span class="hljs-selector-class">.weather-details</span> {
    <span class="hljs-attribute">display</span>: flex;
    <span class="hljs-attribute">justify-content</span>: center;
    <span class="hljs-attribute">background-color</span>: <span class="hljs-number">#f9f9f9</span>;
    <span class="hljs-attribute">border-radius</span>: <span class="hljs-number">4px</span>;
    <span class="hljs-attribute">padding</span>: <span class="hljs-number">1rem</span>;
}

<span class="hljs-selector-id">#humidity-icon</span>, <span class="hljs-selector-id">#wind-icon</span>{
    <span class="hljs-attribute">width</span>: <span class="hljs-number">40px</span>;
    <span class="hljs-attribute">height</span>: <span class="hljs-number">40px</span>;
}

<span class="hljs-selector-class">.detail</span> {
    <span class="hljs-attribute">display</span>: flex;
    <span class="hljs-attribute">flex-direction</span>: column;
    <span class="hljs-attribute">align-items</span>: center;
    <span class="hljs-attribute">margin</span>: <span class="hljs-number">0</span> <span class="hljs-number">1rem</span>;
    <span class="hljs-attribute">text-align</span>: center;
}

<span class="hljs-selector-class">.label</span> {
    <span class="hljs-attribute">font-size</span>: <span class="hljs-number">0.9rem</span>;
    <span class="hljs-attribute">color</span>: <span class="hljs-number">#666</span>;
    <span class="hljs-attribute">margin-bottom</span>: <span class="hljs-number">0.25rem</span>;
}

<span class="hljs-selector-class">.value</span> {
    <span class="hljs-attribute">font-size</span>: <span class="hljs-number">1.2rem</span>;
    <span class="hljs-attribute">font-weight</span>: <span class="hljs-number">500</span>;
}

<span class="hljs-comment">/* Error and offline message styles */</span>
<span class="hljs-selector-class">.error-message</span> {
    <span class="hljs-attribute">color</span>: <span class="hljs-number">#d32f2f</span>;
    <span class="hljs-attribute">text-align</span>: center;
    <span class="hljs-attribute">margin-top</span>: <span class="hljs-number">1rem</span>;
    <span class="hljs-attribute">display</span>: none;
} 

<span class="hljs-selector-class">.offline-message</span> {
    <span class="hljs-attribute">background-color</span>: <span class="hljs-number">#ffab91</span>;
    <span class="hljs-attribute">color</span>: <span class="hljs-number">#7f0000</span>;
    <span class="hljs-attribute">padding</span>: <span class="hljs-number">0.75rem</span>;
    <span class="hljs-attribute">text-align</span>: center;
    <span class="hljs-attribute">margin-top</span>: <span class="hljs-number">1rem</span>;
    <span class="hljs-attribute">border-radius</span>: <span class="hljs-number">4px</span>;
    <span class="hljs-attribute">display</span>: none;
}


<span class="hljs-comment">/* 5 days forecast weather */</span>
<span class="hljs-selector-class">.forecast-container</span> {
    <span class="hljs-attribute">display</span>: flex;
    <span class="hljs-attribute">justify-content</span>: space-around;
    <span class="hljs-attribute">gap</span>: <span class="hljs-number">1rem</span>;
}

<span class="hljs-selector-class">.forecast-item</span> {
    <span class="hljs-attribute">background-color</span>: white;
    <span class="hljs-attribute">border-radius</span>: <span class="hljs-number">8px</span>;
    <span class="hljs-attribute">box-shadow</span>: <span class="hljs-number">0</span> <span class="hljs-number">2px</span> <span class="hljs-number">10px</span> <span class="hljs-built_in">rgba</span>(<span class="hljs-number">0</span>, <span class="hljs-number">0</span>, <span class="hljs-number">0</span>, <span class="hljs-number">0.1</span>);
    <span class="hljs-attribute">padding</span>: <span class="hljs-number">1rem</span> <span class="hljs-number">4rem</span>;
    <span class="hljs-attribute">text-align</span>: center;
}



<span class="hljs-selector-tag">footer</span> {
    <span class="hljs-attribute">background-color</span>: <span class="hljs-number">#2196f3</span>;
    <span class="hljs-attribute">display</span>: flex;
    <span class="hljs-attribute">align-items</span>: center;
    <span class="hljs-attribute">justify-content</span>: center;
    <span class="hljs-attribute">padding</span>: .<span class="hljs-number">7rem</span> <span class="hljs-number">0</span>;
}

<span class="hljs-selector-tag">footer</span> <span class="hljs-selector-tag">p</span>, <span class="hljs-selector-tag">footer</span> <span class="hljs-selector-tag">a</span> {
    <span class="hljs-attribute">color</span>: <span class="hljs-number">#f9f9f9</span>;
    <span class="hljs-attribute">font-weight</span>: <span class="hljs-number">500</span>;
}
<span class="hljs-comment">/* Responsive styles */</span>
<span class="hljs-keyword">@media</span> (<span class="hljs-attribute">max-width:</span> <span class="hljs-number">480px</span>) {
    <span class="hljs-selector-class">.header</span> <span class="hljs-selector-tag">h1</span> {
        <span class="hljs-attribute">font-size</span>: <span class="hljs-number">1.2rem</span>;
    }

    <span class="hljs-selector-class">.location-container</span> {
        <span class="hljs-attribute">flex-direction</span>: column;
        <span class="hljs-attribute">align-items</span>: center;
        <span class="hljs-attribute">gap</span>: .<span class="hljs-number">6rem</span>
    }


    <span class="hljs-selector-class">.current-weather</span> {
        <span class="hljs-attribute">flex-direction</span>: column;
        <span class="hljs-attribute">justify-content</span>: center;
        <span class="hljs-attribute">align-items</span>: center;
    }

    <span class="hljs-selector-class">.weather-container</span> <span class="hljs-selector-tag">h3</span>,  <span class="hljs-selector-class">.location-weather</span> <span class="hljs-selector-tag">h3</span>, <span class="hljs-selector-class">.forecast</span> <span class="hljs-selector-tag">h3</span> {
        <span class="hljs-attribute">font-size</span>: <span class="hljs-number">1.5rem</span>;
    }

    <span class="hljs-selector-id">#weather-icon</span> {
        <span class="hljs-attribute">margin-right</span>: <span class="hljs-number">0</span>;
        <span class="hljs-attribute">margin-bottom</span>: <span class="hljs-number">1rem</span>;
    }

    <span class="hljs-selector-class">.forecast-container</span> {
        <span class="hljs-attribute">flex-direction</span>: column;
        <span class="hljs-attribute">align-items</span>: center;
    }
}
</code></pre>
<h2 id="heading-how-to-set-up-appjs-and-configjs">How to Set Up <code>app.js</code> and <code>config.js</code></h2>
<p>Now, let's create our <code>app.js</code> file to add functionality to the weather app. Before proceeding, ensure you’ve obtained your <strong>API key</strong> from <a target="_blank" href="https://openweathermap.org/">OpenWeather</a>. For best practice, store your API key in a separate file like <code>config.js</code> to keep things organized and avoid hardcoding sensitive data.</p>
<p>Here's what your <code>config.js</code> should look like:</p>
<pre><code class="lang-javascript"><span class="hljs-keyword">export</span> <span class="hljs-keyword">const</span> CONFIG = {
    <span class="hljs-attr">WEATHER_API_KEY</span>: <span class="hljs-string">"WRITE-YOUR-API-KEY-HERE"</span>,
};
</code></pre>
<p>Ensure you add the <code>config.js</code> file to <code>.gitignore</code> to avoid leaking sensitive information on a public platform like GitHub.</p>
<p>Now let’s move to <code>app.js</code>. This is where the main logic of your weather app will live. You can now reference your API key using <code>Weather_API_KEY</code> from the <code>config.js</code> file.</p>
<p>Below is the structure of your <code>app.js</code> file:</p>
<pre><code class="lang-javascript"><span class="hljs-keyword">import</span> { CONFIG } <span class="hljs-keyword">from</span> <span class="hljs-string">'./config.js'</span>;
<span class="hljs-keyword">const</span> BASE_URL = <span class="hljs-string">`https://api.openweathermap.org/data/2.5/weather?&amp;appid=<span class="hljs-subst">${CONFIG.WEATHER_API_KEY}</span>&amp;units=metric&amp;q=`</span>;

<span class="hljs-keyword">const</span> cityName = <span class="hljs-built_in">document</span>.getElementById(<span class="hljs-string">'location-input'</span>);
<span class="hljs-keyword">const</span> searchButton = <span class="hljs-built_in">document</span>.getElementById(<span class="hljs-string">'search-btn'</span>);
<span class="hljs-keyword">const</span> weatherIcon = <span class="hljs-built_in">document</span>.getElementById(<span class="hljs-string">'weather-icon'</span>);
<span class="hljs-keyword">const</span> locationBtn = <span class="hljs-built_in">document</span>.getElementById(<span class="hljs-string">'locationBtn'</span>);
<span class="hljs-keyword">const</span> weatherInfo = <span class="hljs-built_in">document</span>.getElementById(<span class="hljs-string">'weatherInfo'</span>);


<span class="hljs-function"><span class="hljs-keyword">function</span> <span class="hljs-title">getWeatherIcon</span>(<span class="hljs-params">condition</span>) </span>{
  <span class="hljs-keyword">switch</span> (condition) {
    <span class="hljs-keyword">case</span> <span class="hljs-string">"Clear"</span>:
      <span class="hljs-keyword">return</span> <span class="hljs-string">"images/weather-icons/clear.png"</span>;
    <span class="hljs-keyword">case</span> <span class="hljs-string">"Clouds"</span>:
      <span class="hljs-keyword">return</span> <span class="hljs-string">"images/weather-icons/clouds.png"</span>;
    <span class="hljs-keyword">case</span> <span class="hljs-string">"Drizzle"</span>:
      <span class="hljs-keyword">return</span> <span class="hljs-string">"images/weather-icons/drizzle.png"</span>;
    <span class="hljs-keyword">case</span> <span class="hljs-string">"Rain"</span>:
      <span class="hljs-keyword">return</span> <span class="hljs-string">"images/weather-icons/drizzle.png"</span>;
    <span class="hljs-keyword">case</span> <span class="hljs-string">"Mist"</span>:
      <span class="hljs-keyword">return</span> <span class="hljs-string">"images/weather-icons/mist.png"</span>;
    <span class="hljs-keyword">case</span> <span class="hljs-string">"Snow"</span>:
      <span class="hljs-keyword">return</span> <span class="hljs-string">"images/weather-icons/snow.png"</span>;
    <span class="hljs-keyword">default</span>:
      <span class="hljs-keyword">return</span> <span class="hljs-string">"images/weather-icons/default.png"</span>;
  }
}
<span class="hljs-comment">//Search for weather by city name</span>
<span class="hljs-keyword">async</span> <span class="hljs-function"><span class="hljs-keyword">function</span> <span class="hljs-title">checkWeatherBySearch</span>(<span class="hljs-params">city</span>)</span>{
    <span class="hljs-keyword">if</span>(city.length == <span class="hljs-number">0</span>) {
        <span class="hljs-built_in">document</span>.getElementsByClassName(<span class="hljs-string">'error'</span>)[<span class="hljs-number">0</span>].style.display = <span class="hljs-string">'block'</span>;
        <span class="hljs-built_in">document</span>.getElementsByClassName(<span class="hljs-string">'error'</span>)[<span class="hljs-number">0</span>].innerHTML = <span class="hljs-string">"Please enter a city name!"</span>;
        <span class="hljs-built_in">document</span>.getElementsByClassName(<span class="hljs-string">'error'</span>)[<span class="hljs-number">0</span>].style.color = <span class="hljs-string">'red'</span>;
        <span class="hljs-built_in">document</span>.getElementById(<span class="hljs-string">'weather-container'</span>).style.display = <span class="hljs-string">'none'</span>; 
        <span class="hljs-keyword">return</span>;
    }
    <span class="hljs-keyword">const</span> response = <span class="hljs-keyword">await</span> fetch(BASE_URL + city);
    <span class="hljs-built_in">document</span>.getElementsByClassName(<span class="hljs-string">'error'</span>)[<span class="hljs-number">0</span>].style.display = <span class="hljs-string">'block'</span>;
    <span class="hljs-built_in">document</span>.getElementsByClassName(<span class="hljs-string">'error'</span>)[<span class="hljs-number">0</span>].innerHTML = <span class="hljs-string">"Wait a sec, your location's data will be displayed soon!"</span>;

    <span class="hljs-keyword">if</span> (response.status == <span class="hljs-number">404</span>) {
        <span class="hljs-built_in">document</span>.getElementsByClassName(<span class="hljs-string">'error'</span>)[<span class="hljs-number">0</span>].style.display = <span class="hljs-string">'block'</span>;
        <span class="hljs-built_in">document</span>.getElementsByClassName(<span class="hljs-string">'error'</span>)[<span class="hljs-number">0</span>].innerHTML = <span class="hljs-string">"City not found! Please enter a valid city name."</span>;
        <span class="hljs-built_in">document</span>.getElementsByClassName(<span class="hljs-string">'error'</span>)[<span class="hljs-number">0</span>].style.color = <span class="hljs-string">'red'</span>;
        <span class="hljs-built_in">document</span>.getElementById(<span class="hljs-string">'weather-container'</span>).style.display = <span class="hljs-string">'none'</span>;       
    } <span class="hljs-keyword">else</span> {
      <span class="hljs-keyword">const</span> data = <span class="hljs-keyword">await</span> response.json();
      <span class="hljs-built_in">document</span>.getElementById(<span class="hljs-string">'weather-container'</span>).style.display = <span class="hljs-string">'block'</span>;
      <span class="hljs-built_in">document</span>.getElementsByClassName(<span class="hljs-string">'error'</span>)[<span class="hljs-number">0</span>].style.display = <span class="hljs-string">'none'</span>;
      <span class="hljs-built_in">localStorage</span>.setItem(<span class="hljs-string">'lastCity'</span>, city);
      <span class="hljs-built_in">document</span>.getElementById(<span class="hljs-string">'city'</span>).innerHTML = data.name;
      <span class="hljs-built_in">document</span>.getElementById(<span class="hljs-string">'date'</span>).innerHTML = <span class="hljs-keyword">new</span> <span class="hljs-built_in">Date</span>(data.dt * <span class="hljs-number">1000</span>).toLocaleDateString();
      <span class="hljs-built_in">document</span>.getElementById(<span class="hljs-string">"temperature"</span>).innerHTML = <span class="hljs-built_in">Math</span>.round(data.main.temp) + <span class="hljs-string">"°C"</span>;
      <span class="hljs-built_in">document</span>.getElementById(<span class="hljs-string">"humidity"</span>).innerHTML = data.main.humidity + <span class="hljs-string">"%"</span>;
      <span class="hljs-built_in">document</span>.getElementById(<span class="hljs-string">"wind"</span>).innerHTML = data.wind.speed + <span class="hljs-string">"m/s"</span>;
      <span class="hljs-built_in">document</span>.getElementById(<span class="hljs-string">'weather-description'</span>).innerHTML = data.weather[<span class="hljs-number">0</span>].description;
      <span class="hljs-keyword">const</span> weatherCondition = data.weather[<span class="hljs-number">0</span>].main;
      weatherIcon.src = getWeatherIcon(weatherCondition);
    }
}

 <span class="hljs-comment">// display next 5-day forecast by coordinates</span>
<span class="hljs-function"><span class="hljs-keyword">function</span> <span class="hljs-title">display5DaysForecast</span>(<span class="hljs-params">forecast</span>) </span>{
   <span class="hljs-keyword">const</span> fragment = <span class="hljs-built_in">document</span>.createDocumentFragment(); 
    <span class="hljs-keyword">const</span> forecastWrapper = <span class="hljs-built_in">document</span>.createElement(<span class="hljs-string">'div'</span>);
    forecastWrapper.className = <span class="hljs-string">'forecast'</span>;

    <span class="hljs-keyword">const</span> heading = <span class="hljs-built_in">document</span>.createElement(<span class="hljs-string">'h3'</span>);
    heading.innerHTML = <span class="hljs-string">"Your location's next 5 days forecast:"</span>;

    <span class="hljs-keyword">const</span> container = <span class="hljs-built_in">document</span>.createElement(<span class="hljs-string">'div'</span>);
    container.className = <span class="hljs-string">'forecast-container'</span>;

    <span class="hljs-keyword">const</span> addedDates = <span class="hljs-keyword">new</span> <span class="hljs-built_in">Set</span>();
    <span class="hljs-keyword">const</span> today = <span class="hljs-keyword">new</span> <span class="hljs-built_in">Date</span>().toDateString();

    forecast.forEach(<span class="hljs-function">(<span class="hljs-params">entry</span>) =&gt;</span> {
      <span class="hljs-keyword">const</span> entryDateObj = <span class="hljs-keyword">new</span> <span class="hljs-built_in">Date</span>(entry.dt * <span class="hljs-number">1000</span>);
      <span class="hljs-keyword">const</span> entryDateStr = entryDateObj.toDateString();

      <span class="hljs-keyword">if</span> (entryDateStr !== today &amp;&amp; !addedDates.has(entryDateStr)) {
        addedDates.add(entryDateStr);
        <span class="hljs-keyword">if</span> (addedDates.size &gt; <span class="hljs-number">6</span>) <span class="hljs-keyword">return</span>;


        <span class="hljs-keyword">const</span> condition = entry.weather[<span class="hljs-number">0</span>].main;
        <span class="hljs-keyword">const</span> iconSrc = getWeatherIcon(condition);

        <span class="hljs-keyword">const</span> forecastItem = <span class="hljs-built_in">document</span>.createElement(<span class="hljs-string">'div'</span>);
        forecastItem.className = <span class="hljs-string">'forecast-item'</span>;

        <span class="hljs-keyword">const</span> date = <span class="hljs-built_in">document</span>.createElement(<span class="hljs-string">'p'</span>);
        date.id = <span class="hljs-string">'date'</span>;
        date.innerHTML = <span class="hljs-string">`&lt;strong&gt;<span class="hljs-subst">${<span class="hljs-keyword">new</span> <span class="hljs-built_in">Date</span>(entry.dt * <span class="hljs-number">1000</span>).toLocaleDateString()}</span>&lt;/strong&gt;`</span>;

        <span class="hljs-keyword">const</span> icon = <span class="hljs-built_in">document</span>.createElement(<span class="hljs-string">'img'</span>);
        icon.loading = <span class="hljs-string">'lazy'</span>;
        icon.id = <span class="hljs-string">'weather-icon'</span>;
        icon.src = iconSrc;
        icon.alt = <span class="hljs-string">`<span class="hljs-subst">${condition}</span> icon`</span>;

        <span class="hljs-keyword">const</span> tempContainer = <span class="hljs-built_in">document</span>.createElement(<span class="hljs-string">'div'</span>);
        tempContainer.className = <span class="hljs-string">'temperature-container'</span>;

        <span class="hljs-keyword">const</span> temp = <span class="hljs-built_in">document</span>.createElement(<span class="hljs-string">'h3'</span>);
        temp.id = <span class="hljs-string">'temperature'</span>;
        temp.innerHTML = <span class="hljs-string">`<span class="hljs-subst">${<span class="hljs-built_in">Math</span>.round(entry.main.temp)}</span> °C`</span>;

        <span class="hljs-keyword">const</span> description = <span class="hljs-built_in">document</span>.createElement(<span class="hljs-string">'p'</span>);
        description.id = <span class="hljs-string">'weather-description'</span>;
        description.innerHTML = <span class="hljs-string">`<span class="hljs-subst">${entry.weather[<span class="hljs-number">0</span>].description}</span>`</span>;

        tempContainer.appendChild(temp);
        tempContainer.appendChild(description);
        forecastItem.appendChild(date);
        forecastItem.appendChild(icon);
        forecastItem.appendChild(tempContainer);
        container.appendChild(forecastItem);
      }
    });

    forecastWrapper.appendChild(heading);
    forecastWrapper.appendChild(container);
    fragment.appendChild(forecastWrapper);
    weatherInfo.appendChild(fragment); 
}

<span class="hljs-comment">// Fetch next 5-day forecast by coordinates</span>
<span class="hljs-function"><span class="hljs-keyword">function</span> <span class="hljs-title">get5DaysForecast</span>(<span class="hljs-params">lat, lon</span>) </span>{
    fetch(
      <span class="hljs-string">`https://api.openweathermap.org/data/2.5/forecast?lat=<span class="hljs-subst">${lat}</span>&amp;lon=<span class="hljs-subst">${lon}</span>&amp;appid=<span class="hljs-subst">${CONFIG.WEATHER_API_KEY}</span>&amp;units=metric`</span>
    )
      .then(<span class="hljs-function"><span class="hljs-params">res</span> =&gt;</span> res.json())
      .then(<span class="hljs-function"><span class="hljs-params">data</span> =&gt;</span> {
        requestIdleCallback(<span class="hljs-function">() =&gt;</span> {
          <span class="hljs-built_in">setTimeout</span>(<span class="hljs-function">() =&gt;</span> display5DaysForecast(data.list), <span class="hljs-number">0</span>);
        });        
      })
      .catch(<span class="hljs-function">() =&gt;</span> {
        weatherInfo.innerHTML = <span class="hljs-string">'Error fetching forecast data.'</span>;
    });
}

 <span class="hljs-comment">// Display current weather data</span>
<span class="hljs-function"><span class="hljs-keyword">function</span> <span class="hljs-title">displayUserWeather</span>(<span class="hljs-params">data</span>) </span>{
    <span class="hljs-keyword">const</span> weatherCondition = data.weather[<span class="hljs-number">0</span>].main;
    <span class="hljs-keyword">const</span> iconSrc = getWeatherIcon(weatherCondition);

    weatherInfo.innerHTML = <span class="hljs-string">`
      &lt;h2 id="city"&gt;<span class="hljs-subst">${data.name}</span>, <span class="hljs-subst">${data.sys.country}</span>&lt;/h2&gt;

      &lt;div class="current-weather"&gt;
        &lt;img loading="lazy" id="weather-icon" src="<span class="hljs-subst">${iconSrc}</span>" alt="Weather icon"&gt;
        &lt;div class="temperature-container"&gt;
          &lt;h3 id="temperature"&gt; <span class="hljs-subst">${<span class="hljs-built_in">Math</span>.round(data.main.temp)}</span> °C&lt;/h3&gt;
          &lt;p id="weather-description"&gt;<span class="hljs-subst">${data.weather[<span class="hljs-number">0</span>].description}</span>&lt;/p&gt;
        &lt;/div&gt;
      &lt;/div&gt;

      &lt;div class="weather-details"&gt;
        &lt;div class="detail"&gt;
          &lt;img loading="lazy" id="humidity-icon" src="/images/humidity.png" alt="Humidity icon"&gt;
          &lt;span class="label"&gt;Humidity&lt;/span&gt;
          &lt;span id="humidity" class="value"&gt; <span class="hljs-subst">${data.main.humidity}</span>%&lt;/span&gt;
        &lt;/div&gt;
        &lt;div class="detail"&gt;
          &lt;img loading="lazy" id="wind-icon" src="/images/wind.png" alt="Wind icon"&gt;
          &lt;span class="label"&gt;Wind&lt;/span&gt;
          &lt;span id="wind" class="value"&gt; <span class="hljs-subst">${data.wind.speed}</span> m/s&lt;/span&gt;
        &lt;/div&gt;
      &lt;/div&gt;
    `</span>;
  }

<span class="hljs-comment">// Fetch weather by coordinates</span>
<span class="hljs-function"><span class="hljs-keyword">function</span> <span class="hljs-title">getWeatherByCoords</span>(<span class="hljs-params">lat, lon</span>) </span>{
    fetch(
      <span class="hljs-string">`https://api.openweathermap.org/data/2.5/weather?lat=<span class="hljs-subst">${lat}</span>&amp;lon=<span class="hljs-subst">${lon}</span>&amp;appid=<span class="hljs-subst">${CONFIG.WEATHER_API_KEY}</span>&amp;units=metric`</span>
    )
      .then(<span class="hljs-function"><span class="hljs-params">res</span> =&gt;</span> res.json())
      .then(<span class="hljs-function"><span class="hljs-params">data</span> =&gt;</span> {
        displayUserWeather(data);
        get5DaysForecast(lat, lon);
      })
      .catch(<span class="hljs-function">() =&gt;</span> {
        weatherInfo.innerHTML = <span class="hljs-string">'Please turn on your device&amp;apos;s location to get weather data.'</span>;;
      });
  }

<span class="hljs-comment">// Event listeners for search button and input field</span>
cityName.addEventListener(<span class="hljs-string">'keypress'</span>, <span class="hljs-function">(<span class="hljs-params">e</span>) =&gt;</span> {
    <span class="hljs-keyword">if</span> (e.key === <span class="hljs-string">'Enter'</span>) checkWeatherBySearch(cityName.value);
});

  <span class="hljs-comment">// Search button click event</span>
searchButton.addEventListener(<span class="hljs-string">'click'</span>, <span class="hljs-function">()=&gt;</span>{
    checkWeatherBySearch(cityName.value);
});

<span class="hljs-comment">// Geolocation button</span>
locationBtn.addEventListener(<span class="hljs-string">'click'</span>, <span class="hljs-function">() =&gt;</span> {
    <span class="hljs-keyword">if</span> (navigator.geolocation) {
      navigator.geolocation.getCurrentPosition(
        <span class="hljs-function"><span class="hljs-params">pos</span> =&gt;</span> {
          <span class="hljs-keyword">const</span> { latitude, longitude } = pos.coords;
          getWeatherByCoords(latitude, longitude);
        },
        <span class="hljs-function">() =&gt;</span> {
          weatherInfo.innerHTML = <span class="hljs-string">'Unable to retrieve location.'</span>;
        }
      );
    } <span class="hljs-keyword">else</span> {
      weatherInfo.innerHTML = <span class="hljs-string">'Geolocation not supported.'</span>;
    }
});


<span class="hljs-comment">// Load last searched city</span>
<span class="hljs-built_in">window</span>.onload = <span class="hljs-function">() =&gt;</span> {
    <span class="hljs-keyword">const</span> lastCity = <span class="hljs-built_in">localStorage</span>.getItem(<span class="hljs-string">'lastCity'</span>);
    <span class="hljs-keyword">if</span> (lastCity) {
        checkWeatherBySearch(lastCity);
    }

    <span class="hljs-keyword">if</span> (navigator.geolocation) {
        navigator.geolocation.getCurrentPosition(
          <span class="hljs-function"><span class="hljs-params">pos</span> =&gt;</span> {
            <span class="hljs-keyword">const</span> { latitude, longitude } = pos.coords;
            getWeatherByCoords(latitude, longitude);
          },
          <span class="hljs-function">() =&gt;</span> {
            weatherInfo.innerHTML = <span class="hljs-string">'Unable to retrieve location.'</span>;
          }
        );
      } <span class="hljs-keyword">else</span> {
        weatherInfo.innerHTML = <span class="hljs-string">'Geolocation not supported.'</span>;
      }
};
</code></pre>
<p>Now that we have our weather app. Let’s go further to make it a progressive web app.</p>
<h2 id="heading-how-to-create-a-manifest-file">How to Create a Manifest File</h2>
<p>We need to create a <code>manifest.json</code> file, a critical part of making your app a PWA. We’ll also use <a target="_blank" href="https://www.npmjs.com/package/pwa-asset-generator"><strong>pwa-asset-generator</strong></a>, a CLI tool that helps you to generate all the necessary icons and splash screens from a single image (like your logo). This tool also updates your <code>manifest.json</code> and optionally injects relevant <code>&lt;link&gt;</code> tags into <code>index.html</code>.</p>
<p>Below is the <code>manifest.json</code> file containing key properties that define how the Progressive Web App behaves and appears when installed.</p>
<pre><code class="lang-json">{
  <span class="hljs-attr">"name"</span>: <span class="hljs-string">"Weatherly"</span>,                      <span class="hljs-comment">// The full name of your app that may be shown to users.</span>
  <span class="hljs-attr">"short_name"</span>: <span class="hljs-string">"Weatherly"</span>,               <span class="hljs-comment">// A shorter name used when space is limited, like on the home screen.</span>
  <span class="hljs-attr">"description"</span>: <span class="hljs-string">"A simple weather Progressive Web App"</span>, <span class="hljs-comment">// A short description of what your app does.</span>
  <span class="hljs-attr">"start_url"</span>: <span class="hljs-string">"/index.html"</span>,              <span class="hljs-comment">// The page that opens when the app is launched from the home screen.</span>
  <span class="hljs-attr">"display"</span>: <span class="hljs-string">"standalone"</span>,                 <span class="hljs-comment">// Makes the app look like a native app without browser UI (like address bar).</span>
  <span class="hljs-attr">"background_color"</span>: <span class="hljs-string">"#ffffff"</span>,           <span class="hljs-comment">// The background color used when the app is loading.</span>
  <span class="hljs-attr">"theme_color"</span>: <span class="hljs-string">"#2196f3"</span>,                <span class="hljs-comment">// The main color of the app’s UI, like the status bar.</span>
  <span class="hljs-attr">"orientation"</span>: <span class="hljs-string">"portrait"</span>,                <span class="hljs-comment">// Locks the screen orientation to portrait mode.</span>
   <span class="hljs-attr">"screenshots"</span>: [                         <span class="hljs-comment">//helps show users a preview of your app before installing it — especially in places like the "Add to Home screen" prompt on Android or in app stores that support PWAs.</span>
        {
          <span class="hljs-attr">"src"</span>: <span class="hljs-string">"images/screenshots/desktop-screenshot.png"</span>,
          <span class="hljs-attr">"sizes"</span>: <span class="hljs-string">"1337x645"</span>,
          <span class="hljs-attr">"type"</span>: <span class="hljs-string">"image/png"</span>,
          <span class="hljs-attr">"form_factor"</span>: <span class="hljs-string">"wide"</span>
        },
        {
          <span class="hljs-attr">"src"</span>: <span class="hljs-string">"images/screenshots/mobile-screenshot.png"</span>,
          <span class="hljs-attr">"sizes"</span>: <span class="hljs-string">"720x1417"</span>,
          <span class="hljs-attr">"type"</span>: <span class="hljs-string">"image/png"</span>,
          <span class="hljs-attr">"form_factor"</span>: <span class="hljs-string">"narrow"</span>
        }
      ]
}
</code></pre>
<h3 id="heading-how-to-generate-icons-and-splash-screens">How to Generate Icons and Splash Screens</h3>
<p>Inside your <code>images</code> folder, create a new folder called <code>assets</code>. This will store all the generated icons and splash screens. When your app is launched from the home screen, these splash screens will help improve the user experience on iOS devices.</p>
<p>Run the following command to generate PWA assets, update the <code>manifest.json</code>, and inject <code>&lt;link&gt;</code> tags into <code>index.html</code></p>
<pre><code class="lang-powershell">npx pwa<span class="hljs-literal">-asset</span><span class="hljs-literal">-generator</span> logo.png ./images/assets <span class="hljs-literal">-m</span> manifest.json <span class="hljs-literal">-i</span> index.html
</code></pre>
<h3 id="heading-injected-link-tags-in-indexhtml">Injected Link Tags in <code>index.html</code></h3>
<p>Once the command runs successfully, a series of <code>&lt;link&gt;</code> and <code>&lt;meta&gt;</code> Tags will be automatically added to your <code>index.html</code> <code>&lt;head&gt;</code>. These tags ensure support for splash screens and icons across various Apple devices:</p>
<pre><code class="lang-xml"><span class="hljs-tag">&lt;<span class="hljs-name">head</span>&gt;</span>
  <span class="hljs-tag">&lt;<span class="hljs-name">meta</span> <span class="hljs-attr">charset</span>=<span class="hljs-string">"UTF-8"</span> /&gt;</span>
  <span class="hljs-tag">&lt;<span class="hljs-name">meta</span> <span class="hljs-attr">name</span>=<span class="hljs-string">"viewport"</span> <span class="hljs-attr">content</span>=<span class="hljs-string">"width=device-width, initial-scale=1.0"</span> /&gt;</span>
  <span class="hljs-comment">&lt;!-- Other meta/link tags --&gt;</span>

  <span class="hljs-tag">&lt;<span class="hljs-name">link</span> <span class="hljs-attr">rel</span>=<span class="hljs-string">"apple-touch-icon"</span> <span class="hljs-attr">href</span>=<span class="hljs-string">"images/assets/apple-icon-180.png"</span>&gt;</span>
  <span class="hljs-tag">&lt;<span class="hljs-name">meta</span> <span class="hljs-attr">name</span>=<span class="hljs-string">"mobile-web-app-capable"</span> <span class="hljs-attr">content</span>=<span class="hljs-string">"yes"</span>&gt;</span>

  <span class="hljs-tag">&lt;<span class="hljs-name">link</span> <span class="hljs-attr">rel</span>=<span class="hljs-string">"apple-touch-startup-image"</span> <span class="hljs-attr">href</span>=<span class="hljs-string">"images/assets/apple-splash-2048-2732.jpg"</span> <span class="hljs-attr">media</span>=<span class="hljs-string">"(device-width: 1024px) and (device-height: 1366px) and (orientation: portrait)"</span>&gt;</span>
  <span class="hljs-tag">&lt;<span class="hljs-name">link</span> <span class="hljs-attr">rel</span>=<span class="hljs-string">"apple-touch-startup-image"</span> <span class="hljs-attr">href</span>=<span class="hljs-string">"images/assets/apple-splash-2732-2048.jpg"</span> <span class="hljs-attr">media</span>=<span class="hljs-string">"(device-width: 1024px) and (device-height: 1366px) and (orientation: landscape)"</span>&gt;</span>
  <span class="hljs-comment">&lt;!-- ...more splash screen tags for various devices... --&gt;</span>
<span class="hljs-tag">&lt;/<span class="hljs-name">head</span>&gt;</span>
</code></pre>
<p>Here’s how the <code>manifest.json</code> file should look like now:</p>
<pre><code class="lang-json">{
    <span class="hljs-attr">"name"</span>: <span class="hljs-string">"Weatherly"</span>,
    <span class="hljs-attr">"short_name"</span>: <span class="hljs-string">"Weatherly"</span>,
    <span class="hljs-attr">"description"</span>: <span class="hljs-string">"A simple weather Progressive Web App"</span>,
    <span class="hljs-attr">"start_url"</span>: <span class="hljs-string">"/index.html"</span>,
    <span class="hljs-attr">"display"</span>: <span class="hljs-string">"standalone"</span>,
    <span class="hljs-attr">"background_color"</span>: <span class="hljs-string">"#ffffff"</span>,
    <span class="hljs-attr">"theme_color"</span>: <span class="hljs-string">"#2196f3"</span>,
    <span class="hljs-attr">"orientation"</span>: <span class="hljs-string">"portrait"</span>,
    <span class="hljs-attr">"icons"</span>: [
        [
            {
              <span class="hljs-attr">"src"</span>: <span class="hljs-string">"images/assets/manifest-icon-192.maskable.png"</span>,
              <span class="hljs-attr">"sizes"</span>: <span class="hljs-string">"192x192"</span>,
              <span class="hljs-attr">"type"</span>: <span class="hljs-string">"image/png"</span>,
              <span class="hljs-attr">"purpose"</span>: <span class="hljs-string">"any"</span>
            },
            {
              <span class="hljs-attr">"src"</span>: <span class="hljs-string">"images/assets/manifest-icon-192.maskable.png"</span>,
              <span class="hljs-attr">"sizes"</span>: <span class="hljs-string">"192x192"</span>,
              <span class="hljs-attr">"type"</span>: <span class="hljs-string">"image/png"</span>,
              <span class="hljs-attr">"purpose"</span>: <span class="hljs-string">"maskable"</span>
            },
            {
              <span class="hljs-attr">"src"</span>: <span class="hljs-string">"images/assets/manifest-icon-512.maskable.png"</span>,
              <span class="hljs-attr">"sizes"</span>: <span class="hljs-string">"512x512"</span>,
              <span class="hljs-attr">"type"</span>: <span class="hljs-string">"image/png"</span>,
              <span class="hljs-attr">"purpose"</span>: <span class="hljs-string">"any"</span>
            },
            {
              <span class="hljs-attr">"src"</span>: <span class="hljs-string">"images/assets/manifest-icon-512.maskable.png"</span>,
              <span class="hljs-attr">"sizes"</span>: <span class="hljs-string">"512x512"</span>,
              <span class="hljs-attr">"type"</span>: <span class="hljs-string">"image/png"</span>,
              <span class="hljs-attr">"purpose"</span>: <span class="hljs-string">"maskable"</span>
            }
          ]
        ],
    <span class="hljs-attr">"screenshots"</span>: [
        {
          <span class="hljs-attr">"src"</span>: <span class="hljs-string">"images/screenshots/desktop-screenshot.png"</span>,
          <span class="hljs-attr">"sizes"</span>: <span class="hljs-string">"1337x645"</span>,
          <span class="hljs-attr">"type"</span>: <span class="hljs-string">"image/png"</span>,
          <span class="hljs-attr">"form_factor"</span>: <span class="hljs-string">"wide"</span>
        },
        {
          <span class="hljs-attr">"src"</span>: <span class="hljs-string">"images/screenshots/mobile-screenshot.png"</span>,
          <span class="hljs-attr">"sizes"</span>: <span class="hljs-string">"720x1417"</span>,
          <span class="hljs-attr">"type"</span>: <span class="hljs-string">"image/png"</span>,
          <span class="hljs-attr">"form_factor"</span>: <span class="hljs-string">"narrow"</span>
        }
      ]
    }
</code></pre>
<p>You can then link your manifest file to your HTML file:</p>
<pre><code class="lang-json">&lt;link rel=<span class="hljs-string">"manifest"</span> href=<span class="hljs-string">"manifest.json"</span> /&gt;
</code></pre>
<h2 id="heading-how-to-add-workbox-to-your-service-workerjs-file">How to Add WorkBox to Your <code>service-worker.js</code> File</h2>
<p>In this tutorial, WorkBox will be added to <code>index.html</code> via CDN. You can copy the import code below or visit WorkBox to get the link. You can then add it to the <code>index.html</code> file by placing the URL inside a <code>&lt;script&gt;</code> tag. You can copy the import code below or visit the WorkBox website for the latest link.</p>
<pre><code class="lang-javascript">importScripts(<span class="hljs-string">'https://storage.googleapis.com/workbox-cdn/releases/6.5.4/workbox-sw.js'</span>);
</code></pre>
<h2 id="heading-how-to-create-your-service-worker-in-the-service-workerjs-file">How to Create your Service Worker in the <code>service-worker.js</code> File</h2>
<p>Here, we’ll implement the necessary functionalities needed to make the weather app a PWA</p>
<h3 id="heading-step-1-activate-the-new-service-worker-immediately"><strong>Step 1:</strong> Activate the New Service Worker Immediately</h3>
<p>Add <code>workbox.core.skipWaiting()</code> to make the newly installed service worker activate right away instead of waiting for the old one to be removed in the <code>service-worker.js</code> file.</p>
<pre><code class="lang-javascript">workbox.core.skipWaiting();
</code></pre>
<h3 id="heading-step-2-take-control-of-open-tabs"><strong>Step 2:</strong> Take Control of Open Tabs</h3>
<p>Add <code>workbox.core.clientsClaim()</code> to ensure that the activated service worker takes control of all currently open pages, so the latest version of your app works immediately across all tabs after it becomes active.</p>
<pre><code class="lang-javascript">workbox.core.clientsClaim();
</code></pre>
<h3 id="heading-step-3-check-if-workbox-is-loaded">Step 3: Check if Workbox is Loaded</h3>
<p>Before using Workbox, make sure it has loaded properly.</p>
<pre><code class="lang-js"><span class="hljs-keyword">if</span> (workbox) {
  <span class="hljs-built_in">console</span>.log(<span class="hljs-string">'Workbox loaded successfully'</span>);
} <span class="hljs-keyword">else</span> {
  <span class="hljs-built_in">console</span>.log(<span class="hljs-string">'Workbox failed to load'</span>);
}
</code></pre>
<p>This confirms that the <code>workbox</code> object is available and ready to use. If not, the fallback message in the <code>else</code> block will be shown.</p>
<p>We then proceed to create the functions inside the <code>if</code> block</p>
<h3 id="heading-step-4-pre-cache-core-files">Step 4: Pre-cache Core Files</h3>
<p>Pre-cache essential files enable your app to work offline. This caches your app shell (HTML, CSS, JS), so it loads even without a network connection.</p>
<pre><code class="lang-js">workbox.precaching.precacheAndRoute([
    { <span class="hljs-attr">url</span>: <span class="hljs-string">'/index.html'</span>, <span class="hljs-attr">revision</span>: <span class="hljs-string">'3'</span> },
    { <span class="hljs-attr">url</span>: <span class="hljs-string">'/style.css'</span>, <span class="hljs-attr">revision</span>: <span class="hljs-string">'11'</span> },
    { <span class="hljs-attr">url</span>: <span class="hljs-string">'/app.js'</span>, <span class="hljs-attr">revision</span>: <span class="hljs-string">'7'</span> },
    { <span class="hljs-attr">url</span>: <span class="hljs-string">'/images/logo.png'</span>, <span class="hljs-attr">revision</span>: <span class="hljs-string">'3'</span> },
    { <span class="hljs-attr">url</span>: <span class="hljs-string">'/manifest.json'</span>, <span class="hljs-attr">revision</span>: <span class="hljs-string">'5'</span> },
    { <span class="hljs-attr">url</span>: <span class="hljs-string">'/offline.html'</span>, <span class="hljs-attr">revision</span>: <span class="hljs-string">'1'</span> },
  ]);
</code></pre>
<p>The <code>revision</code> helps with updating cached files when changes are made.</p>
<h3 id="heading-step-5-cache-api-responses-dynamically">Step 5: Cache API Responses Dynamically</h3>
<p>Set up a route to cache data from your weather API using the <code>NetworkFirst</code> caching strategy. This tells Workbox to try fetching fresh data from the network first. If the network fails, it serves the cached version instead.</p>
<pre><code class="lang-js"> <span class="hljs-comment">// Cache API requests </span>
  workbox.routing.registerRoute(
    <span class="hljs-function">(<span class="hljs-params">{ url }</span>) =&gt;</span> url.origin === <span class="hljs-string">'https://api.openweathermap.org'</span>,
    <span class="hljs-keyword">new</span> workbox.strategies.NetworkFirst({
      <span class="hljs-attr">cacheName</span>: <span class="hljs-string">'weather-api-cache'</span>,
      <span class="hljs-attr">plugins</span>: [
        <span class="hljs-keyword">new</span> workbox.expiration.ExpirationPlugin({
          <span class="hljs-attr">maxAgeSeconds</span>: <span class="hljs-number">24</span> * <span class="hljs-number">60</span> * <span class="hljs-number">60</span>,
          <span class="hljs-attr">maxEntries</span>: <span class="hljs-number">10</span>,
        }),
      ],
    })
  );
</code></pre>
<h3 id="heading-step-6-dynamic-image-caching">Step 6: Dynamic Image Caching</h3>
<p>This function enables dynamic caching for images using the <code>StaleWhileRevalidate</code> strategy. When a user requests an image, Workbox first serves it from the cache (if available) for faster load times, while simultaneously fetching an updated version from the network to refresh the cache. This ensures users get a quick response without missing out on updated content. It’s a smart way to handle images by balancing speed and freshness.</p>
<pre><code class="lang-javascript"><span class="hljs-comment">// Cache images</span>
  workbox.routing.registerRoute(
    <span class="hljs-function">(<span class="hljs-params">{ request }</span>) =&gt;</span> request.destination === <span class="hljs-string">'image'</span>,
    <span class="hljs-keyword">new</span> workbox.strategies.StaleWhileRevalidate({
      <span class="hljs-attr">cacheName</span>: <span class="hljs-string">'image-cache'</span>,
    })
  );
</code></pre>
<h3 id="heading-step-7-serve-cached-resources">Step 7: Serve Cached Resources</h3>
<p>The commonly used static files (like HTML, CSS, JS, fonts, and so on) are served quickly from the cache. It uses the <code>CacheFirst</code> strategy, meaning that the service worker will look in the cache first and only fetch from the network if the file isn’t already stored. The cache is named <code>"static-cache"</code> and it’s set to automatically remove items older than seven days using the <code>expiration</code> plugin. This helps keep the cache fresh and avoids taking up too much space.</p>
<pre><code class="lang-javascript">  <span class="hljs-comment">// Serve Cached Resources </span>
  workbox.routing.registerRoute(
    <span class="hljs-function">(<span class="hljs-params">{url}</span>) =&gt;</span> url.origin === self.location.origin,  
    <span class="hljs-keyword">new</span> workbox.strategies.CacheFirst({
      <span class="hljs-attr">cacheName</span>: <span class="hljs-string">'static-cache'</span>,  
      <span class="hljs-attr">plugins</span>: [
        <span class="hljs-keyword">new</span> workbox.expiration.ExpirationPlugin({
          <span class="hljs-attr">maxAgeSeconds</span>: <span class="hljs-number">7</span> * <span class="hljs-number">24</span> * <span class="hljs-number">60</span> * <span class="hljs-number">60</span>,  <span class="hljs-comment">// Cache static resources for 7 days</span>
        }),
      ],
    })
  );
</code></pre>
<h3 id="heading-step-8-cache-html-pages-with-offline-support"><strong>Step 8: Cache</strong> HTML Pages with Offline Support</h3>
<p>The <code>index.html</code> page will be handled using the NetworkFirst strategy. This means that the service worker tries to fetch the latest version from the network first. If the user is offline or the network fails, it falls back to the cached version. The cache is named <code>"pages-cache"</code> and the offline fallback page (<code>offline.html</code>) is returned when the requested page isn’t available. This ensures that users can still navigate the app even without an internet connection.</p>
<pre><code class="lang-javascript"><span class="hljs-comment">// Serve HTML pages with Network First and offline fallback</span>
workbox.routing.registerRoute(
  <span class="hljs-function">(<span class="hljs-params">{ request }</span>) =&gt;</span> request.mode === <span class="hljs-string">'navigate'</span>,
  <span class="hljs-keyword">async</span> ({ event }) =&gt; {
    <span class="hljs-keyword">try</span> {
      <span class="hljs-keyword">const</span> response = <span class="hljs-keyword">await</span> workbox.strategies.networkFirst({
        <span class="hljs-attr">cacheName</span>: <span class="hljs-string">'pages-cache'</span>,
        <span class="hljs-attr">plugins</span>: [
          <span class="hljs-keyword">new</span> workbox.expiration.ExpirationPlugin({
            <span class="hljs-attr">maxEntries</span>: <span class="hljs-number">50</span>,
          }),
        ],
      }).handle({ event });
      <span class="hljs-keyword">return</span> response || <span class="hljs-keyword">await</span> caches.match(<span class="hljs-string">'/offline.html'</span>);
    } <span class="hljs-keyword">catch</span> (error) {
      <span class="hljs-keyword">return</span> <span class="hljs-keyword">await</span> caches.match(<span class="hljs-string">'/offline.html'</span>);
    }
  }
);
</code></pre>
<h3 id="heading-step-9-handle-when-workbox-doesnt-load">Step 9: Handle When Workbox Doesn’t Load</h3>
<p>You should always provide a fallback in case something goes wrong. The <code>if</code> block will have an <code>else</code> block to catch issues during development and debugging.</p>
<pre><code class="lang-js"><span class="hljs-keyword">else</span> {
     <span class="hljs-built_in">console</span>.log(<span class="hljs-string">'Workbox failed to load'</span>);
}
</code></pre>
<p>Once the service worker finishes handling the different conditions in the <code>if-else</code> block, we add a general cleanup step to remove any outdated or unused caches.</p>
<h3 id="heading-step-10-clean-up-outdated-caches"><strong>Step 10:</strong> Clean Up Outdated Caches</h3>
<p>During the service worker's activation phase, old or unused caches are removed. It compares all existing cache names with a list of current ones (<code>precache</code>, <code>weather-api-cache</code>, <code>image-cache</code>, <code>pages-cache</code>, and <code>static-resources</code>). If a cache doesn’t match the current list, it gets deleted. This helps keep the app lightweight and ensures that outdated data doesn't persist.</p>
<pre><code class="lang-javascript"><span class="hljs-comment">// Clean up old/unused caches during activation</span>
self.addEventListener(<span class="hljs-string">'activate'</span>, <span class="hljs-function"><span class="hljs-params">event</span> =&gt;</span> {
  <span class="hljs-keyword">const</span> currentCaches = [
    workbox.core.cacheNames.precache,
    <span class="hljs-string">'weather-api-cache'</span>,
    <span class="hljs-string">'image-cache'</span>,
    <span class="hljs-string">'pages-cache'</span>,
    <span class="hljs-string">'static-cache'</span>
  ];

  event.waitUntil(
    caches.keys().then(<span class="hljs-function"><span class="hljs-params">cacheNames</span> =&gt;</span> {
      <span class="hljs-keyword">return</span> <span class="hljs-built_in">Promise</span>.all(
        cacheNames.map(<span class="hljs-function"><span class="hljs-params">cacheName</span> =&gt;</span> {
          <span class="hljs-keyword">if</span> (!currentCaches.includes(cacheName)) {
            <span class="hljs-keyword">return</span> caches.delete(cacheName);
          }
        })
      );
    })
  );
});
</code></pre>
<p>This is what your <code>service-worker.js</code> file should look like:</p>
<pre><code class="lang-javascript">importScripts(<span class="hljs-string">'https://storage.googleapis.com/workbox-cdn/releases/6.5.4/workbox-sw.js'</span>);

<span class="hljs-comment">// Force waiting service worker to become active</span>
workbox.core.skipWaiting();
workbox.core.clientsClaim();

<span class="hljs-keyword">if</span> (workbox) {
  <span class="hljs-built_in">console</span>.log(<span class="hljs-string">'Workbox loaded successfully'</span>);

  <span class="hljs-comment">// Precache critical files with revisions (update revisions when files change)</span>
  workbox.precaching.precacheAndRoute([
    { <span class="hljs-attr">url</span>: <span class="hljs-string">'/index.html'</span>, <span class="hljs-attr">revision</span>: <span class="hljs-string">'3'</span> },
    { <span class="hljs-attr">url</span>: <span class="hljs-string">'/style.css'</span>, <span class="hljs-attr">revision</span>: <span class="hljs-string">'11'</span> },
    { <span class="hljs-attr">url</span>: <span class="hljs-string">'/app.js'</span>, <span class="hljs-attr">revision</span>: <span class="hljs-string">'7'</span> },
    { <span class="hljs-attr">url</span>: <span class="hljs-string">'/images/logo.png'</span>, <span class="hljs-attr">revision</span>: <span class="hljs-string">'3'</span> },
    { <span class="hljs-attr">url</span>: <span class="hljs-string">'/manifest.json'</span>, <span class="hljs-attr">revision</span>: <span class="hljs-string">'5'</span> },
    { <span class="hljs-attr">url</span>: <span class="hljs-string">'/offline.html'</span>, <span class="hljs-attr">revision</span>: <span class="hljs-string">'1'</span> },
  ]);

  <span class="hljs-comment">// Cache API requests </span>
  workbox.routing.registerRoute(
    <span class="hljs-function">(<span class="hljs-params">{ url }</span>) =&gt;</span> url.origin === <span class="hljs-string">'https://api.openweathermap.org'</span>,
    <span class="hljs-keyword">new</span> workbox.strategies.NetworkFirst({
      <span class="hljs-attr">cacheName</span>: <span class="hljs-string">'weather-api-cache'</span>,
      <span class="hljs-attr">plugins</span>: [
        <span class="hljs-keyword">new</span> workbox.expiration.ExpirationPlugin({
          <span class="hljs-attr">maxAgeSeconds</span>: <span class="hljs-number">24</span> * <span class="hljs-number">60</span> * <span class="hljs-number">60</span>,
          <span class="hljs-attr">maxEntries</span>: <span class="hljs-number">10</span>,
        }),
      ],
    })
  );

  <span class="hljs-comment">// Cache images</span>
  workbox.routing.registerRoute(
    <span class="hljs-function">(<span class="hljs-params">{ request }</span>) =&gt;</span> request.destination === <span class="hljs-string">'image'</span>,
    <span class="hljs-keyword">new</span> workbox.strategies.StaleWhileRevalidate({
      <span class="hljs-attr">cacheName</span>: <span class="hljs-string">'image-cache'</span>,
    })
  );

    <span class="hljs-comment">// Serve Cached Resources </span>
  workbox.routing.registerRoute(
    <span class="hljs-function">(<span class="hljs-params">{url}</span>) =&gt;</span> url.origin === self.location.origin,  
    <span class="hljs-keyword">new</span> workbox.strategies.CacheFirst({
      <span class="hljs-attr">cacheName</span>: <span class="hljs-string">'static-cache'</span>,  
      <span class="hljs-attr">plugins</span>: [
        <span class="hljs-keyword">new</span> workbox.expiration.ExpirationPlugin({
          <span class="hljs-attr">maxAgeSeconds</span>: <span class="hljs-number">7</span> * <span class="hljs-number">24</span> * <span class="hljs-number">60</span> * <span class="hljs-number">60</span>,  <span class="hljs-comment">// Cache static resources for 7 days</span>
        }),
      ],
    })
  );

  <span class="hljs-comment">// Serve HTML pages with Network First and offline fallback</span>
workbox.routing.registerRoute(
  <span class="hljs-function">(<span class="hljs-params">{ request }</span>) =&gt;</span> request.mode === <span class="hljs-string">'navigate'</span>,
  <span class="hljs-keyword">async</span> ({ event }) =&gt; {
    <span class="hljs-keyword">try</span> {
      <span class="hljs-keyword">const</span> response = <span class="hljs-keyword">await</span> workbox.strategies.networkFirst({
        <span class="hljs-attr">cacheName</span>: <span class="hljs-string">'pages-cache'</span>,
        <span class="hljs-attr">plugins</span>: [
          <span class="hljs-keyword">new</span> workbox.expiration.ExpirationPlugin({
            <span class="hljs-attr">maxEntries</span>: <span class="hljs-number">50</span>,
          }),
        ],
      }).handle({ event });
      <span class="hljs-keyword">return</span> response || <span class="hljs-keyword">await</span> caches.match(<span class="hljs-string">'/offline.html'</span>);
    } <span class="hljs-keyword">catch</span> (error) {
      <span class="hljs-keyword">return</span> <span class="hljs-keyword">await</span> caches.match(<span class="hljs-string">'/offline.html'</span>);
    }
  }
);
} <span class="hljs-keyword">else</span> {
  <span class="hljs-built_in">console</span>.log(<span class="hljs-string">'Workbox failed to load'</span>);
}

<span class="hljs-comment">// Clean up old/unused caches during activation</span>
self.addEventListener(<span class="hljs-string">'activate'</span>, <span class="hljs-function"><span class="hljs-params">event</span> =&gt;</span> {
  <span class="hljs-keyword">const</span> currentCaches = [
    workbox.core.cacheNames.precache,
    <span class="hljs-string">'weather-api-cache'</span>,
    <span class="hljs-string">'image-cache'</span>,
    <span class="hljs-string">'pages-cache'</span>,
    <span class="hljs-string">'static-cache'</span>
  ];

  event.waitUntil(
    caches.keys().then(<span class="hljs-function"><span class="hljs-params">cacheNames</span> =&gt;</span> {
      <span class="hljs-keyword">return</span> <span class="hljs-built_in">Promise</span>.all(
        cacheNames.map(<span class="hljs-function"><span class="hljs-params">cacheName</span> =&gt;</span> {
          <span class="hljs-keyword">if</span> (!currentCaches.includes(cacheName)) {
            <span class="hljs-keyword">return</span> caches.delete(cacheName);
          }
        })
      );
    })
  );
});
</code></pre>
<h2 id="heading-how-to-set-up-app-installation">How to Set Up App Installation</h2>
<p>The code to install the app will be written in <code>install.js</code> following the steps below:</p>
<h3 id="heading-step-1-register-the-service-worker"><strong>Step 1:</strong> Register the Service Worker</h3>
<p>Register the service worker to activate and run it in your app.</p>
<pre><code class="lang-javascript"><span class="hljs-keyword">if</span>(<span class="hljs-string">'serviceWorker'</span> <span class="hljs-keyword">in</span> navigator){
    <span class="hljs-built_in">window</span>.addEventListener(<span class="hljs-string">'load'</span>, <span class="hljs-function">() =&gt;</span> {
      navigator.serviceWorker.register(<span class="hljs-string">'/service-worker.js'</span>).then(<span class="hljs-function"><span class="hljs-params">reg</span> =&gt;</span> {
        reg.onupdatefound = <span class="hljs-function">() =&gt;</span> {
          <span class="hljs-keyword">const</span> newWorker = reg.installing;
          newWorker.onstatechange = <span class="hljs-function">() =&gt;</span> {
            <span class="hljs-keyword">if</span> (newWorker.state === <span class="hljs-string">'installed'</span> &amp;&amp; navigator.serviceWorker.controller) {
              <span class="hljs-built_in">window</span>.location.reload();
            }
          };
        };
      });
    })
 }
</code></pre>
<h3 id="heading-step-2-enable-custom-install-prompt">Step 2: Enable Custom Install Prompt</h3>
<p>Next, we will allow users to install the weather PWA with a custom button. Inside the <code>install.js</code>file, add the <code>beforeinstallprompt</code> event which intercepts the default prompt and shows your install button instead. When clicked, it triggers the install prompt.</p>
<pre><code class="lang-javascript">
  <span class="hljs-keyword">let</span> deferredPrompt;

<span class="hljs-built_in">document</span>.addEventListener(<span class="hljs-string">'DOMContentLoaded'</span>, <span class="hljs-function">() =&gt;</span> {
  <span class="hljs-keyword">const</span> installBtn = <span class="hljs-built_in">document</span>.getElementById(<span class="hljs-string">'installBtn'</span>);

  <span class="hljs-built_in">window</span>.addEventListener(<span class="hljs-string">'beforeinstallprompt'</span>, <span class="hljs-function">(<span class="hljs-params">e</span>) =&gt;</span> {
    e.preventDefault();
    deferredPrompt = e;

    <span class="hljs-comment">// Show the button</span>
    installBtn.style.display = <span class="hljs-string">'block'</span>;

    installBtn.addEventListener(<span class="hljs-string">'click'</span>, <span class="hljs-function">() =&gt;</span> {
      <span class="hljs-comment">// Directly triggered by user click</span>
      installBtn.style.display = <span class="hljs-string">'none'</span>;

      <span class="hljs-comment">// Show the install prompt</span>
      deferredPrompt.prompt();

      deferredPrompt.userChoice.then(<span class="hljs-function">(<span class="hljs-params">choiceResult</span>) =&gt;</span> {
        <span class="hljs-keyword">if</span> (choiceResult.outcome === <span class="hljs-string">'accepted'</span>) {
          <span class="hljs-built_in">console</span>.log(<span class="hljs-string">'User accepted the install prompt'</span>);
        } <span class="hljs-keyword">else</span> {
          <span class="hljs-built_in">console</span>.log(<span class="hljs-string">'User dismissed the install prompt'</span>);
        }
        deferredPrompt = <span class="hljs-literal">null</span>;
      });
    });
  });
</code></pre>
<p>The <code>appinstalled</code> event confirms successful installation.</p>
<pre><code class="lang-javascript">
<span class="hljs-built_in">window</span>.addEventListener(<span class="hljs-string">'appinstalled'</span>, <span class="hljs-function">() =&gt;</span> {
    <span class="hljs-built_in">console</span>.log(<span class="hljs-string">'PWA was installed'</span>);
  });
});
</code></pre>
<h3 id="heading-step-3-add-script-tag-to-import-installjs-in-indexhtml">Step 3: Add script tag to import <code>install.js</code> in <code>index.html</code></h3>
<p>Add the <code>&lt;script&gt;</code> tag for <code>install.js</code> inside the <code>index.html</code> file to include the installation logic.</p>
<pre><code class="lang-xml"> <span class="hljs-tag">&lt;<span class="hljs-name">script</span> <span class="hljs-attr">type</span>=<span class="hljs-string">"module"</span> <span class="hljs-attr">src</span>=<span class="hljs-string">"/js/install.js"</span>&gt;</span><span class="hljs-tag">&lt;/<span class="hljs-name">script</span>&gt;</span>
</code></pre>
<h2 id="heading-how-to-install-the-weather-app">How to Install the Weather App</h2>
<p>You can choose to install the Weatherly app on your phone or desktop. Below is a demonstration on how to install it on your mobile phone:</p>
<p>Open the <a target="_blank" href="https://weatherly-taupe-two.vercel.app/">Weatherly</a> app in your browser. You should see an <strong>“Install App”</strong> button, as shown in the image below. Click on the button to continue.</p>
<p><img src="https://cdn.hashnode.com/res/hashnode/image/upload/v1747272209446/2ade0ad7-eda5-46df-b443-a1efce90003b.png" alt="Weatherly app interface showing Install App button along with city search field, location services, and Tokyo weather history" class="image--center mx-auto" width="600" height="400" loading="lazy"></p>
<p>After clicking, a preview of the app will appear along with an <strong>“Install”</strong> option, as shown below. Click the Install button.</p>
<p><img src="https://cdn.hashnode.com/res/hashnode/image/upload/v1748387183047/b6be3b6c-6550-4c94-a453-eb928cc70dbe.png" alt="Browser PWA installation dialog showing Weatherly app preview with Install button and app description." class="image--center mx-auto" width="600" height="400" loading="lazy"></p>
<p>Once the installation is complete, the Weatherly app will appear on your home screen, just like a native app. And that’s it! Your weather app is now a Progressive Web App (PWA).</p>
<h2 id="heading-conclusion">Conclusion</h2>
<p>Progressive Web Apps combine the best of web and native app experiences, and service workers are the backbone of that functionality. With tools like Workbox, you don’t have to worry about manually handling caching, offline support, or background sync. Its simple APIs and built-in strategies make it easier to build fast, reliable, and installable web apps. Whether it’s a small weather app like <a target="_blank" href="https://weatherly-pwa.vercel.app/">Weatherly</a> or a more complex project, Workbox helps you deliver a seamless user experience.</p>
<p>You can check out the full project and assets on <a target="_blank" href="https://github.com/LolaVictoria/weatherly">GitHub</a></p>
 ]]>
                </content:encoded>
            </item>
        
            <item>
                <title>
                    <![CDATA[ How To Create a Progressive Web App (PWA) Using Next.js ]]>
                </title>
                <description>
                    <![CDATA[ Have you ever wanted to create a web app that works smoothly on any device—whether it's on the web, mobile, or desktop? Imagine if your app could load quickly, work without an internet connection, and feel like a native app, all without needing to be... ]]>
                </description>
                <link>https://www.freecodecamp.org/news/how-to-create-a-nextjs-pwa/</link>
                <guid isPermaLink="false">66ededaa06a4bf699b266b23</guid>
                
                    <category>
                        <![CDATA[ Next.js ]]>
                    </category>
                
                    <category>
                        <![CDATA[ React ]]>
                    </category>
                
                    <category>
                        <![CDATA[ PWA ]]>
                    </category>
                
                <dc:creator>
                    <![CDATA[ Spruce Emmanuel ]]>
                </dc:creator>
                <pubDate>Fri, 20 Sep 2024 21:48:26 +0000</pubDate>
                <media:content url="https://cdn.hashnode.com/res/hashnode/image/upload/v1726728761614/ba739b83-78b9-4cd7-9040-13ade8e515f7.png" medium="image" />
                <content:encoded>
                    <![CDATA[ <p>Have you ever wanted to create a web app that works smoothly on any device—whether it's on the web, mobile, or desktop? Imagine if your app could load quickly, work without an internet connection, and feel like a native app, all without needing to be installed from an app store. That’s exactly what Progressive Web Apps (PWAs) can do.</p>
<p>In this tutorial, you’ll learn how to build a PWA using Next.js. We’ll start by creating a functional movie search website with these tools. Once we have the basics set up, we’ll transform this app into a PWA, adding offline support and faster load times. By the end, you’ll have a powerful PWA that offers a smooth user experience across all platforms—all from a single codebase.</p>
<h3 id="heading-what-well-cover"><strong>What We’ll Cover</strong></h3>
<ul>
<li><p><strong>Setting Up the Project:</strong> We'll begin by creating the movie search app using Next.js, which is an ideal choice in 2024 for building fast, reliable React apps that work well on all devices.</p>
</li>
<li><p><strong>Turning the App into a PWA:</strong> Next, we’ll walk through the steps to convert the app into a Progressive Web App, covering the key features and best practices of PWAs.</p>
</li>
<li><p><strong>Adding Offline Support:</strong> Finally, we’ll ensure your app stays functional even when there’s no internet connection by implementing offline capabilities.</p>
</li>
</ul>
<p>Here’s what the final application will look like:</p>
<p><img src="https://cdn.hashnode.com/res/hashnode/image/upload/v1726724446891/47398ad8-fa1f-46d2-8a22-0a5d49e6a67e.png" alt="This screenshot shows the completed MovieMaster PWA, highlighting its sleek design and offline capabilities." class="image--center mx-auto" width="2624" height="1754" loading="lazy"></p>
<h3 id="heading-audience"><strong>Audience</strong></h3>
<p>This tutorial is for React developers of all levels, whether you’re just starting out or already experienced. If you want to enhance your web apps with PWA features, this guide will take you through the necessary steps.</p>
<h3 id="heading-prerequisites"><strong>Prerequisites</strong></h3>
<p>Before you begin, make sure you’re familiar with React.js and Next.js. If you’re new to PWAs, you might want to read some introductory articles to get a quick overview.</p>
<ul>
<li><p><a target="_blank" href="https://www.freecodecamp.org/news/what-are-progressive-web-apps-pwa-guide/"><em>What are Progressive Web Apps? PWA Guide for Beginners</em></a></p>
</li>
<li><p><a target="_blank" href="https://web.dev/learn/pwa"><em>Learn Progressive Web Apps</em></a></p>
</li>
</ul>
<aside>
  <h2 id="heading-table-of-contents">Table of Contents</h2>
  <ul>
    <li><a href="#0">Introduction</a></li>
    <li><a href="#heading-what-we-cover">What We’ll Cover</a></li>
    <li><a href="#heading-audience">Audience</a></li>
    <li><a href="#heading-prerequisites">Prerequisites</a></li>
    <li><a href="#heading-what-is-a-progressive-web-app-pwa">What is a Progressive Web App (PWA)?</a></li>
    <li><a href="#heading-why-turn-your-web-app-into-a-pwa">Why Turn Your Web App into a PWA?</a></li>
    <li><a href="#heading-getting-started-setting-up-the-nextjs-project">Getting Started: Setting Up the Next.js Project</a></li>
    <li><a href="#heading-why-choose-nextjs-in-2024">Why Choose Next.js in 2024?</a></li>
    <li><a href="#heading-project-installation">Project Installation</a></li>
    <li><a href="#heading-project-structure-overview">Project Structure Overview</a></li>
 <li><a href="#heading-understanding-layouts">Understanding Layouts</a></li>
    <li><a href="#heading-running-and-previewing-the-project">Running and Previewing the Project</a></li>
    <li><a href="#heading-how-to-turn-your-web-app-into-a-pwa">How to Turn Your Web App into a PWA</a></li>
    <li><a href="#heading-criteria-for-a-pwa">Criteria for a PWA</a></li>
    <li><a href="#heading-how-to-add-a-web-manifest-file-to-your-nextjs-app">How to Add a Web Manifest File to Your Next.js App</a></li>
    <li><a href="#heading-how-to-register-a-service-worker">How to Register a Service Worker</a></li>
    <li><a href="#heading-how-to-add-offline-support">How to Add Offline Support</a></li>
    <li><a href="#heading-what-to-cache">What to Cache?</a></li>
    <li><a href="#heading-when-to-cache">When to Cache?</a></li>
    <li><a href="#heading-dynamic-caching">Dynamic Caching</a></li>
    <li><a href="#heading-caching-api-requests">Caching API Requests</a></li>
    <li><a href="#heading-how-to-serve-cached-resources">How to Serve Cached Resources</a></li>
    <li><a href="#heading-providing-a-fallback-page">Providing a Fallback Page</a></li>
    <li><a href="#heading-conclusion">Conclusion</a></li>
  </ul>
</aside>

<h2 id="heading-what-is-a-progressive-web-app-pwa">What is a Progressive Web App (PWA)?</h2>
<p>A Progressive Web App (PWA) is a type of web application built using standard web technologies like HTML, CSS, and JavaScript. PWAs work on the web, desktop, and mobile devices, combining the best features of web and native apps to deliver a fast, reliable, and engaging experience.</p>
<p>What makes PWAs special is their ability to work offline, send push notifications, and be installed on a user’s device without an app store. In short, a PWA makes your web app feel like a native app while keeping the flexibility and wide reach of the web.</p>
<h3 id="heading-why-turn-your-web-app-into-a-pwa"><strong>Why Turn Your Web App into a PWA?</strong></h3>
<p>Converting your web app into a PWA brings several benefits:</p>
<ul>
<li><strong>Cross-Platform Availability:</strong> A PWA works on any device with a browser, so you only need to develop and maintain one codebase for web, mobile, and desktop apps. This saves time and ensures a consistent experience across all platforms.</li>
</ul>
<p><img src="https://cdn.hashnode.com/res/hashnode/image/upload/v1726724668376/acf531ba-7ff7-4c62-8d11-ac283d236bd8.png" alt="This image shows the MovieMaster PWA running on a mobile phone, web browser, and desktop, showcasing the versatile nature of PWAs." width="1600" height="661" loading="lazy"></p>
<ul>
<li><p><strong>Offline Capabilities:</strong> PWAs can work offline or in areas with poor connectivity by caching essential resources, keeping your app functional even without internet access.</p>
</li>
<li><p><strong>Improved Performance:</strong> PWAs are built to load quickly and run smoothly, even on slow networks, thanks to techniques like service workers and caching.</p>
</li>
<li><p><strong>Increased User Engagement:</strong> Users can add PWAs directly to their home screen without needing an app store. This easy access, along with features like push notifications, helps keep users engaged and coming back.</p>
</li>
</ul>
<p><strong>Disadvantages of PWAs</strong></p>
<p>While PWAs offer many benefits, there are a few downsides:</p>
<ul>
<li><p><strong>Limited Access to Device Features</strong>: PWAs don’t have full access to certain device features like Bluetooth or advanced camera controls. For apps that need deep hardware integration, this can be a limitation.</p>
</li>
<li><p><strong>Less Visibility</strong>: Since PWAs don’t go through app stores, they miss out on the visibility that app stores offer. Some users might also prefer downloading apps from app stores rather than directly from the browser.</p>
</li>
<li><p><strong>Limited iOS Support</strong>: Some features of PWAs, like push notifications, don’t work as well on iPhones and iPads compared to Android devices, which can limit engagement with iOS users.</p>
</li>
</ul>
<h2 id="heading-getting-started-setting-up-the-nextjs-project"><strong>Getting Started: Setting Up the Next.js Project</strong></h2>
<p>Now that we’ve talked about the benefits of PWAs, let’s get into the actual implementation. We’ll start by setting up the necessary files in our project.</p>
<h3 id="heading-why-choose-nextjs-in-2024"><strong>Why Choose Next.js in 2024?</strong></h3>
<p>Next.js is a top choice for building React apps in 2024. It offers features like server-side rendering and static site generation, making it easier to create fast and reliable web apps. These features ensure your app performs well on all devices and even works offline.</p>
<h3 id="heading-project-installation"><strong>Project Installation</strong></h3>
<p>Follow these steps to set up your Next.js project:</p>
<ol>
<li><p>Clone the Repository: Open your terminal and run:</p>
<pre><code class="lang-bash"> git <span class="hljs-built_in">clone</span> https://github.com/iamspruce/MovieMaster.git
</code></pre>
</li>
<li><p>Navigate to Your Project Directory:</p>
<pre><code class="lang-bash"> <span class="hljs-built_in">cd</span> your-repo
</code></pre>
</li>
<li><p>Install Dependencies: Install the required packages with:</p>
<pre><code class="lang-bash"> npm install
</code></pre>
</li>
<li><p>Configure Environment Variables: Create a .env.local file in the root directory and add your OMDB API key:</p>
<pre><code class="lang-plaintext"> NEXT_PUBLIC_OMDB_API_KEY=your-api-key
</code></pre>
</li>
</ol>
<p>You can obtain your API key from the <a target="_blank" href="https://www.omdbapi.com/apikey.aspx">OMDB API website</a>.</p>
<p><strong>Why is the OMDB API Key Needed?</strong><br>The OMDB API key allows your PWA to fetch movie data, like titles, posters, and descriptions, directly from the OMDB database. This is essential for a movie-related app like MovieMaster, as it provides up-to-date information for users without you having to store all the data yourself.</p>
<p>In a PWA, using an API like OMDB ensures that the app can deliver fresh content to users, even when it's installed on their devices. Combined with the PWA's caching and offline features, users can still view movie details that were previously fetched, even if they lose internet connectivity.</p>
<p><strong>Note</strong>: Make sure Node.js and npm are installed on your system. If they are not, you can download them from <a target="_blank" href="http://nodejs.org/">nodejs.org</a>.</p>
<h4 id="heading-project-structure-overview"><strong>Project Structure Overview</strong></h4>
<p>Here’s a brief overview of the project layout:</p>
<ul>
<li><p><strong>/public</strong>: Contains static files such as images and favicons.</p>
</li>
<li><p><strong>/src/app</strong>: Houses the main application files, including global styles (globals.css), the main page (<strong>page.tsx</strong>), layout configurations (<strong>layout.tsx</strong>), and client-side logic (<strong>RootLayoutClient.tsx</strong>).</p>
</li>
<li><p><strong>/src/components</strong>: Includes reusable components. Shadcn UI components are located in the /ui directory, and other specific components like <strong>MovieCard.tsx</strong> are here.</p>
</li>
<li><p><strong>/src/lib</strong>: Contains utility functions and data-fetching code, such as <strong>fetchMovies.ts</strong> and <strong>useMediaQuery.ts</strong>.</p>
</li>
</ul>
<p>For styling, we use:</p>
<ul>
<li><p><strong>TailwindCSS</strong>: Applied through <strong>globals.css</strong> for a utility-first approach to design.</p>
</li>
<li><p><strong>Shadcn UI</strong>: A library providing accessible, ready-to-use UI components.</p>
</li>
</ul>
<h4 id="heading-understanding-layouts"><strong>Understanding Layouts</strong></h4>
<p>The project uses two key layouts:</p>
<ol>
<li><p><strong>layout.tsx</strong>: Manages server-side rendering and sets the application’s metadata. It uses the <code>RootLayoutClient</code> component to handle client-side functionality. Here’s how it looks:</p>
<pre><code class="lang-javascript"> <span class="hljs-keyword">import</span> React <span class="hljs-keyword">from</span> <span class="hljs-string">"react"</span>;
 <span class="hljs-keyword">import</span> type { Metadata } <span class="hljs-keyword">from</span> <span class="hljs-string">"next"</span>;
 <span class="hljs-keyword">import</span> { cn } <span class="hljs-keyword">from</span> <span class="hljs-string">"@/lib/utils"</span>;
 <span class="hljs-keyword">import</span> { Inter <span class="hljs-keyword">as</span> FontSans } <span class="hljs-keyword">from</span> <span class="hljs-string">"next/font/google"</span>;

 <span class="hljs-keyword">import</span> RootLayoutClient <span class="hljs-keyword">from</span> <span class="hljs-string">"./RootLayoutClient"</span>;

 <span class="hljs-keyword">const</span> fontSans = FontSans({
   <span class="hljs-attr">subsets</span>: [<span class="hljs-string">"latin"</span>],
   <span class="hljs-attr">variable</span>: <span class="hljs-string">"--font-sans"</span>,
 });

 <span class="hljs-keyword">export</span> <span class="hljs-keyword">const</span> metadata: Metadata = {
   <span class="hljs-attr">title</span>: <span class="hljs-string">"MovieMaster"</span>,
   <span class="hljs-attr">description</span>: <span class="hljs-string">"MovieMaster PWA helps you find the latest movies with an easy search by genre, year, and more. It works smoothly on any device, even offline, giving you a great movie browsing experience."</span>,
   <span class="hljs-attr">manifest</span>: <span class="hljs-string">"/web.manifest"</span>,
 };

 <span class="hljs-keyword">export</span> <span class="hljs-keyword">default</span> <span class="hljs-function"><span class="hljs-keyword">function</span> <span class="hljs-title">RootLayout</span>(<span class="hljs-params">{ children }: { children: React.ReactNode }</span>) </span>{
   <span class="hljs-keyword">return</span> (
     <span class="xml"><span class="hljs-tag">&lt;<span class="hljs-name">html</span> <span class="hljs-attr">lang</span>=<span class="hljs-string">"en"</span> <span class="hljs-attr">suppressHydrationWarning</span>&gt;</span>
       <span class="hljs-tag">&lt;<span class="hljs-name">body</span> <span class="hljs-attr">className</span>=<span class="hljs-string">{cn(</span>"<span class="hljs-attr">min-h-screen</span> <span class="hljs-attr">bg-background</span> <span class="hljs-attr">font-sans</span> <span class="hljs-attr">antialiased</span>", <span class="hljs-attr">fontSans.variable</span>)}&gt;</span>
         <span class="hljs-tag">&lt;<span class="hljs-name">RootLayoutClient</span>&gt;</span>{children}<span class="hljs-tag">&lt;/<span class="hljs-name">RootLayoutClient</span>&gt;</span>
       <span class="hljs-tag">&lt;/<span class="hljs-name">body</span>&gt;</span>
     <span class="hljs-tag">&lt;/<span class="hljs-name">html</span>&gt;</span></span>
   );
 }
</code></pre>
</li>
<li><p><strong>RootLayoutClient.tsx</strong>: Handles client-side logic, essential for rendering interactive elements and managing UI states."</p>
<pre><code class="lang-javascript"> <span class="hljs-string">"use client"</span>;

 <span class="hljs-keyword">import</span> React <span class="hljs-keyword">from</span> <span class="hljs-string">"react"</span>;
 <span class="hljs-keyword">import</span> { Toaster } <span class="hljs-keyword">from</span> <span class="hljs-string">"@/components/ui/sonner"</span>;
 <span class="hljs-keyword">import</span> <span class="hljs-string">"./globals.css"</span>;

 <span class="hljs-keyword">export</span> <span class="hljs-keyword">default</span> <span class="hljs-function"><span class="hljs-keyword">function</span> <span class="hljs-title">RootLayoutClient</span>(<span class="hljs-params">{ children }: { children: React.ReactNode }</span>) </span>{
   <span class="hljs-keyword">return</span> (
     <span class="xml"><span class="hljs-tag">&lt;<span class="hljs-name">div</span> <span class="hljs-attr">className</span>=<span class="hljs-string">"text-white flex flex-col"</span>&gt;</span>
       <span class="hljs-tag">&lt;<span class="hljs-name">div</span> <span class="hljs-attr">className</span>=<span class="hljs-string">"container mx-auto px-4 max-w-[1024px]"</span>&gt;</span>
         {children}
         <span class="hljs-tag">&lt;<span class="hljs-name">Toaster</span> /&gt;</span>
       <span class="hljs-tag">&lt;/<span class="hljs-name">div</span>&gt;</span>
     <span class="hljs-tag">&lt;/<span class="hljs-name">div</span>&gt;</span></span>
   );
 }
</code></pre>
</li>
</ol>
<h4 id="heading-running-and-previewing-the-project"><strong>Running and Previewing the Project</strong></h4>
<p>To start working with your project:</p>
<p><strong>Start the Development Server</strong>: In your terminal, execute:</p>
<pre><code class="lang-bash">npm run dev
</code></pre>
<p>This will start the development server, and you can view the application by navigating to http://localhost:3000 in your browser.</p>
<h2 id="heading-how-to-turn-your-web-app-into-a-pwa"><strong>How to Turn Your Web App into a PWA</strong></h2>
<p>To transform your web app into a PWA, there are certain criteria that your app must meet. Let's walk through these requirements and implement the necessary changes step by step.</p>
<h3 id="heading-criteria-for-a-pwa">Criteria for a PWA</h3>
<ol>
<li><p><strong>Served Over HTTPS</strong>: Your app must be served over a secure origin (HTTPS) or <code>localhost</code> for development. If you’re developing locally, this criterion is already met.</p>
</li>
<li><p><strong>Web Manifest File</strong>: A web manifest file provides metadata about your app, such as its name, icons, and start URL. This file is crucial for making your app installable on a user's device.</p>
</li>
<li><p><strong>Service Worker with a</strong> <code>fetch</code> <strong>Event</strong>: Your app must register a service worker with at least a <code>fetch</code> event. Registering a service worker with at least a fetch event is essential for your app to be recognized as a PWA and be installable. Beyond that, service workers enhance your app's performance and reliability, allowing it to cache resources and handle network requests even when offline.</p>
</li>
</ol>
<h3 id="heading-how-to-add-a-web-manifest-file-to-your-nextjs-app"><strong>How to Add a Web Manifest File to Your Next.js App</strong></h3>
<p>To add a web manifest file in your Next.js app, place it in the <strong>public/</strong> directory and reference it in your layout file. Ensure that all the images you include in your manifest file are also in the <strong>public/</strong> directory.</p>
<p>Here’s an example of a <strong>web.manifest</strong> file:</p>
<pre><code class="lang-json">{
  <span class="hljs-attr">"name"</span>: <span class="hljs-string">"Movie Master"</span>,
  <span class="hljs-attr">"short_name"</span>: <span class="hljs-string">"Moviemaster"</span>,
  <span class="hljs-attr">"theme_color"</span>: <span class="hljs-string">"#8936FF"</span>,
  <span class="hljs-attr">"background_color"</span>: <span class="hljs-string">"#333333"</span>,
  <span class="hljs-attr">"start_url"</span>: <span class="hljs-string">"/"</span>,
  <span class="hljs-attr">"id"</span>: <span class="hljs-string">"MovieMaster"</span>,
  <span class="hljs-attr">"display"</span>: <span class="hljs-string">"standalone"</span>,
  <span class="hljs-attr">"description"</span>: <span class="hljs-string">"MovieMaster PWA helps you find the latest movies with an easy search by genre, year, and more. It works smoothly on any device, even offline, giving you a great movie browsing experience."</span>,
  <span class="hljs-attr">"icons"</span>: [
    {
      <span class="hljs-attr">"purpose"</span>: <span class="hljs-string">"maskable"</span>,
      <span class="hljs-attr">"sizes"</span>: <span class="hljs-string">"512x512"</span>,
      <span class="hljs-attr">"src"</span>: <span class="hljs-string">"icon512_maskable.png"</span>,
      <span class="hljs-attr">"type"</span>: <span class="hljs-string">"image/png"</span>
    },
    {
      <span class="hljs-attr">"purpose"</span>: <span class="hljs-string">"any"</span>,
      <span class="hljs-attr">"sizes"</span>: <span class="hljs-string">"512x512"</span>,
      <span class="hljs-attr">"src"</span>: <span class="hljs-string">"icon512_rounded.png"</span>,
      <span class="hljs-attr">"type"</span>: <span class="hljs-string">"image/png"</span>
    }
  ],
  <span class="hljs-attr">"screenshots"</span>: [
    {
      <span class="hljs-attr">"src"</span>: <span class="hljs-string">"screenshot1.png"</span>,
      <span class="hljs-attr">"type"</span>: <span class="hljs-string">"image/png"</span>,
      <span class="hljs-attr">"sizes"</span>: <span class="hljs-string">"1080x1920"</span>,
      <span class="hljs-attr">"form_factor"</span>: <span class="hljs-string">"narrow"</span>
    }
  ]
}
</code></pre>
<h4 id="heading-required-fields"><strong>Required Fields</strong></h4>
<ul>
<li><p><code>name</code>: The full name of your app.</p>
</li>
<li><p><code>short_name</code>: A shorter version of the app’s name, displayed when there isn’t enough space for the full name.</p>
</li>
<li><p><code>icons</code>: Icons representing your app at various sizes.</p>
</li>
<li><p><code>start_url</code>: The URL that opens when the app is launched.</p>
</li>
<li><p><code>display</code>: Defines the display mode (for example, <code>standalone</code> for a full-screen experience).</p>
</li>
</ul>
<h4 id="heading-recommended-fields"><strong>Recommended Fields</strong></h4>
<ul>
<li><code>theme_color</code>: Sets the theme color of the browser’s UI, such as the address bar. This color enhances the native feel of your PWA.</li>
</ul>
<p>This example shows how the theme color (#8936FF) is applied to the browser's UI, giving your PWA a native feel.</p>
<p><img src="https://cdn.hashnode.com/res/hashnode/image/upload/v1726725605476/32366f45-b981-44bc-ac60-0f3661b8ed2c.png" alt="A dark-themed movie search interface displaying &quot;The Avengers&quot; (2012) showing how theme color is applied to the browser's UI" width="2588" height="802" loading="lazy"></p>
<ul>
<li><p><code>background_color</code>: Defines the background color for the splash screen when your app is launched.</p>
</li>
<li><p><code>screenshots</code>: Provide screenshots of your app to improve the installation experience, especially on Android devices.</p>
</li>
</ul>
<p>This example illustrates how screenshots are displayed during the installation process, enhancing the user experience, especially on Android devices.</p>
<p><img src="https://cdn.hashnode.com/res/hashnode/image/upload/v1726725620443/53b6712b-574a-445c-aca2-7c57dd62a268.jpeg" alt="An image showing how screenshots are displayed during the installation process on Android." width="571" height="1280" loading="lazy"></p>
<ul>
<li><code>id</code>: Unique identifier for the app</li>
</ul>
<h3 id="heading-how-to-reference-the-web-manifest-file"><strong>How to Reference the Web Manifest File</strong></h3>
<p>Next, let’s add the manifest file to your pages. In Next.js, you can include it in the <code>metadata</code> of your <strong>Layout.tsx</strong>:</p>
<pre><code class="lang-javascript"><span class="hljs-keyword">export</span> <span class="hljs-keyword">const</span> metadata: Metadata = {
  <span class="hljs-attr">title</span>: <span class="hljs-string">"MovieMaster"</span>,
  <span class="hljs-attr">description</span>: <span class="hljs-string">"Find the latest movies with ease."</span>,
  <span class="hljs-attr">manifest</span>: <span class="hljs-string">"/web.manifest"</span>, <span class="hljs-comment">// Link to the manifest file</span>
};
</code></pre>
<h3 id="heading-how-to-register-a-service-worker"><strong>How to Register a Service Worker</strong></h3>
<p>A service worker is a script that your browser runs in the background, allowing you to control how your app handles network requests, caching, and other tasks.</p>
<p>Registering a service worker with at least a <code>fetch</code> event is essential for your app to be recognized as a PWA and be installable.</p>
<p>Create a <strong>service-worker.js</strong> file in the <strong>public/</strong> directory with the following code:</p>
<pre><code class="lang-javascript">self.addEventListener(<span class="hljs-string">'install'</span>, <span class="hljs-function">(<span class="hljs-params">event</span>) =&gt;</span> {
  <span class="hljs-built_in">console</span>.log(<span class="hljs-string">'Service Worker installing.'</span>);
});

self.addEventListener(<span class="hljs-string">'activate'</span>, <span class="hljs-function">(<span class="hljs-params">event</span>) =&gt;</span> {
  <span class="hljs-built_in">console</span>.log(<span class="hljs-string">'Service Worker activating.'</span>);
});

self.addEventListener(<span class="hljs-string">'fetch'</span>, <span class="hljs-function">(<span class="hljs-params">event</span>) =&gt;</span> {
  <span class="hljs-built_in">console</span>.log(<span class="hljs-string">'Fetching:'</span>, event.request.url);
  event.respondWith(fetch(event.request));
});
</code></pre>
<p>Then, register the service worker in your <strong>RootLayoutClient.tsx</strong> file:</p>
<pre><code class="lang-javascript"><span class="hljs-string">"use client"</span>;

<span class="hljs-keyword">import</span> React <span class="hljs-keyword">from</span> <span class="hljs-string">"react"</span>;

<span class="hljs-keyword">export</span> <span class="hljs-keyword">default</span> <span class="hljs-function"><span class="hljs-keyword">function</span> <span class="hljs-title">RootLayoutClient</span>(<span class="hljs-params">{ children }</span>) </span>{
  React.useEffect(<span class="hljs-function">() =&gt;</span> {
    <span class="hljs-keyword">if</span> (<span class="hljs-string">"serviceWorker"</span> <span class="hljs-keyword">in</span> navigator) {
      navigator.serviceWorker
        .register(<span class="hljs-string">"/service-worker.js"</span>)
        .then(<span class="hljs-function">(<span class="hljs-params">registration</span>) =&gt;</span> {
          <span class="hljs-built_in">console</span>.log(<span class="hljs-string">"Service Worker registered with scope:"</span>, registration.scope);
        })
        .catch(<span class="hljs-function">(<span class="hljs-params">error</span>) =&gt;</span> {
          <span class="hljs-built_in">console</span>.error(<span class="hljs-string">"Service Worker registration failed:"</span>, error);
        });
    }
  }, []);

  <span class="hljs-keyword">return</span> (
    <span class="xml"><span class="hljs-tag">&lt;<span class="hljs-name">div</span> <span class="hljs-attr">className</span>=<span class="hljs-string">"text-white flex flex-col"</span>&gt;</span>
      <span class="hljs-tag">&lt;<span class="hljs-name">div</span> <span class="hljs-attr">className</span>=<span class="hljs-string">"container mx-auto px-4 max-w-[1024px]"</span>&gt;</span>
        {children}
      <span class="hljs-tag">&lt;/<span class="hljs-name">div</span>&gt;</span>
    <span class="hljs-tag">&lt;/<span class="hljs-name">div</span>&gt;</span></span>
  );
}
</code></pre>
<p>Once your app meets all the criteria, users can easily install it on their devices. For example, when using the Edge browser, an install option will appear in the browser’s menu, allowing users to add your app directly to their desktop or home screen.</p>
<p>Here's what the installation process looks like:</p>
<p><img src="https://cdn.hashnode.com/res/hashnode/image/upload/v1726725642915/c7e6af8a-56e8-4eb2-b1b6-fd715ea7210a.png" alt="An image showing the install option on Edge browser" width="1600" height="949" loading="lazy"></p>
<h2 id="heading-how-to-add-offline-support">How to Add Offline Support</h2>
<p>At this stage, even though our app is technically a PWA, it still behaves like a regular web application. Whenever a user requests a resource, the app makes a network request, and if the network fails, the user is greeted with an error page. This isn’t ideal, especially when the power of a PWA lies in its ability to function offline or in poor network conditions.</p>
<p>With a PWA, you can intercept every request made by your app using a service worker. This gives you the flexibility to decide how to serve content—from the network or from a cache. This control allows you to ensure that users can still access the app, even without an internet connection.</p>
<h3 id="heading-how-to-deliver-resources-from-the-network"><strong>How to Deliver Resources from the Network</strong></h3>
<p>Let’s start by looking at how our app currently behaves, which is similar to any standard web application:</p>
<pre><code class="lang-javascript">self.addEventListener(<span class="hljs-string">"fetch"</span>, <span class="hljs-function">(<span class="hljs-params">event</span>) =&gt;</span> {
  event.respondWith(fetch(event.request));
});
</code></pre>
<p>This code simply fetches resources directly from the network. If the network is unavailable, the request will fail, leading to an error. This is the default behavior for a standard web app.</p>
<h3 id="heading-how-to-implement-offline-support"><strong>How to Implement Offline Support</strong></h3>
<p>To provide an offline experience, we need to cache our app’s resources when the user is online and then serve these cached resources when the user is offline. For this, we’ll use the Cache Storage API, which allows us to store resources locally on the user's device.</p>
<h3 id="heading-what-to-cache"><strong>What to Cache?</strong></h3>
<p>The decision on what to cache depends on the needs of your application. For a movie search app like ours, we’ll want to cache the essential resources required to render a basic version of the application:</p>
<ul>
<li><p>The main HTML page</p>
</li>
<li><p>CSS stylesheets needed to render the site</p>
</li>
<li><p>Images used in the user interface</p>
</li>
<li><p>JavaScript files required for functionality</p>
</li>
<li><p>API request responses</p>
</li>
</ul>
<p><strong>Note:</strong> While you can cache almost anything, be mindful of storage limitations, as all cached items are stored on the user’s device. Use storage wisely to avoid taking up too much space.</p>
<h3 id="heading-when-to-cache"><strong>When to Cache?</strong></h3>
<p>Once we know what to cache, the next thing to consider is when to cache. Should you cache everything during the service worker installation, or should you cache resources as they are requested?</p>
<p>The answer depends on the app's needs, but a good practice is to cache the core files required to render a basic version of the app during the service worker installation.</p>
<p>Here’s how you can do that:</p>
<pre><code class="lang-javascript"><span class="hljs-keyword">const</span> CACHE_NAME = <span class="hljs-string">"MOVIE_MASTER_V1"</span>;

<span class="hljs-keyword">async</span> <span class="hljs-function"><span class="hljs-keyword">function</span> <span class="hljs-title">cacheCoreAssets</span>(<span class="hljs-params"></span>) </span>{
  <span class="hljs-keyword">const</span> cache = <span class="hljs-keyword">await</span> caches.open(CACHE_NAME);
  <span class="hljs-keyword">return</span> cache.addAll([
    <span class="hljs-string">"/"</span>,
    <span class="hljs-string">"/imdb-logo.svg"</span>,
    <span class="hljs-string">"/rotten-tomatoes-logo.svg"</span>,
  ]);
}

self.addEventListener(<span class="hljs-string">"install"</span>, <span class="hljs-function">(<span class="hljs-params">event</span>) =&gt;</span> {
  event.waitUntil(cacheCoreAssets());
  self.skipWaiting();
});
</code></pre>
<p>In this code, <code>self.skipWaiting()</code> ensures that the new service worker activates immediately after installation, bypassing the waiting phase.</p>
<p>It’s also important to delete old caches when a new service worker is activated:</p>
<pre><code class="lang-javascript"><span class="hljs-keyword">async</span> <span class="hljs-function"><span class="hljs-keyword">function</span> <span class="hljs-title">clearOldCaches</span>(<span class="hljs-params"></span>) </span>{
  <span class="hljs-keyword">const</span> cacheNames = <span class="hljs-keyword">await</span> caches.keys();
  <span class="hljs-keyword">return</span> <span class="hljs-built_in">Promise</span>.all(
    cacheNames
      .filter(<span class="hljs-function">(<span class="hljs-params">name</span>) =&gt;</span> name !== CACHE_NAME)
      .map(<span class="hljs-function">(<span class="hljs-params">name</span>) =&gt;</span> caches.delete(name))
  );
}

self.addEventListener(<span class="hljs-string">"activate"</span>, <span class="hljs-function">(<span class="hljs-params">event</span>) =&gt;</span> {
  event.waitUntil(clearOldCaches());
  self.clients.claim();
});
</code></pre>
<p>The <code>self.clients.claim()</code> method ensures that the new service worker takes control of all pages as soon as it activates.</p>
<h3 id="heading-dynamic-caching"><strong>Dynamic Caching</strong></h3>
<p>Dynamic caching is particularly useful for React apps like ours, where static files are automatically generated. With dynamic caching, you don’t need to know all the files in advance. Instead, it handles the caching process for you as files are requested.</p>
<pre><code class="lang-javascript"><span class="hljs-keyword">async</span> <span class="hljs-function"><span class="hljs-keyword">function</span> <span class="hljs-title">dynamicCaching</span>(<span class="hljs-params">request</span>) </span>{
  <span class="hljs-keyword">const</span> cache = <span class="hljs-keyword">await</span> caches.open(CACHE_NAME);

  <span class="hljs-keyword">try</span> {
    <span class="hljs-keyword">const</span> response = <span class="hljs-keyword">await</span> fetch(request);
    <span class="hljs-keyword">const</span> responseClone = response.clone();
    <span class="hljs-keyword">await</span> cache.put(request, responseClone);
    <span class="hljs-keyword">return</span> response;
  } <span class="hljs-keyword">catch</span> (error) {
    <span class="hljs-built_in">console</span>.error(<span class="hljs-string">"Dynamic caching failed:"</span>, error);
    <span class="hljs-keyword">return</span> caches.match(request);
  }
}
</code></pre>
<p>With dynamic caching, files requested by the app are cached as they are fetched, ensuring that they are available for future offline use.</p>
<h3 id="heading-caching-api-requests"><strong>Caching API Requests</strong></h3>
<p>When it comes to caching API responses, instead of caching the entire response, it’s often better to cache the specific data returned by the API. For this, we can use IndexedDB, a local database built into the browser.</p>
<p>IndexedDB is more powerful than the Cache Storage API, especially for storing and retrieving structured data like JSON. This makes it an excellent choice for apps that require storing complex data or handling large amounts of information efficiently.</p>
<p><img src="https://cdn.hashnode.com/res/hashnode/image/upload/v1726726244513/6e99d4b6-fefb-40f1-9ad1-db88c2c7b3da.png" alt="A screenshot showing the structure and data stored in the IndexedDB for the MovieMaster PWA." width="1600" height="1027" loading="lazy"></p>
<h4 id="heading-how-to-set-up-indexeddb"><strong>How to Set Up IndexedDB</strong></h4>
<p>First, create a function to open the database and create an object store:</p>
<pre><code class="lang-javascript"><span class="hljs-keyword">const</span> DB_NAME = <span class="hljs-string">"MovieMaster"</span>;
<span class="hljs-keyword">const</span> DB_VERSION = <span class="hljs-number">1</span>;
<span class="hljs-keyword">const</span> DB_STORE_NAME = <span class="hljs-string">"myStore"</span>;

<span class="hljs-function"><span class="hljs-keyword">function</span> <span class="hljs-title">openDb</span>(<span class="hljs-params"></span>) </span>{
  <span class="hljs-keyword">return</span> <span class="hljs-keyword">new</span> <span class="hljs-built_in">Promise</span>(<span class="hljs-function">(<span class="hljs-params">resolve, reject</span>) =&gt;</span> {
    <span class="hljs-keyword">const</span> request = indexedDB.open(DB_NAME, DB_VERSION);
    request.onsuccess = <span class="hljs-function">() =&gt;</span> resolve(request.result);
    request.onerror = <span class="hljs-function">() =&gt;</span> reject(request.error);
    request.onupgradeneeded = <span class="hljs-function">(<span class="hljs-params">event</span>) =&gt;</span> {
      <span class="hljs-keyword">const</span> db = event.target.result;
      db.createObjectStore(DB_STORE_NAME, { <span class="hljs-attr">keyPath</span>: <span class="hljs-string">"url"</span> });
    };
  });
}
</code></pre>
<p>Next, create functions to add data to and retrieve data from the database:</p>
<pre><code class="lang-javascript"><span class="hljs-keyword">async</span> <span class="hljs-function"><span class="hljs-keyword">function</span> <span class="hljs-title">addData</span>(<span class="hljs-params">url, jsonData</span>) </span>{
  <span class="hljs-keyword">const</span> db = <span class="hljs-keyword">await</span> openDb();
  <span class="hljs-keyword">const</span> transaction = db.transaction(DB_STORE_NAME, <span class="hljs-string">"readwrite"</span>);
  <span class="hljs-keyword">const</span> store = transaction.objectStore(DB_STORE_NAME);

  <span class="hljs-keyword">const</span> data = {
    url,
    <span class="hljs-attr">response</span>: <span class="hljs-built_in">JSON</span>.stringify(jsonData),
  };

  <span class="hljs-keyword">const</span> request = store.put(data);
  <span class="hljs-keyword">await</span> <span class="hljs-keyword">new</span> <span class="hljs-built_in">Promise</span>(<span class="hljs-function">(<span class="hljs-params">resolve, reject</span>) =&gt;</span> {
    request.onsuccess = <span class="hljs-function">() =&gt;</span> resolve();
    request.onerror = <span class="hljs-function">() =&gt;</span> reject(request.error);
  });
}

<span class="hljs-keyword">async</span> <span class="hljs-function"><span class="hljs-keyword">function</span> <span class="hljs-title">getData</span>(<span class="hljs-params">url</span>) </span>{
  <span class="hljs-keyword">try</span> {
    <span class="hljs-keyword">const</span> db = <span class="hljs-keyword">await</span> openDb();
    <span class="hljs-keyword">const</span> transaction = db.transaction(DB_STORE_NAME, <span class="hljs-string">"readonly"</span>);
    <span class="hljs-keyword">const</span> store = transaction.objectStore(DB_STORE_NAME);

    <span class="hljs-keyword">const</span> request = store.get(url);

    <span class="hljs-keyword">const</span> result = <span class="hljs-keyword">await</span> <span class="hljs-keyword">new</span> <span class="hljs-built_in">Promise</span>(<span class="hljs-function">(<span class="hljs-params">resolve, reject</span>) =&gt;</span> {
      request.onsuccess = <span class="hljs-function">() =&gt;</span> resolve(request.result);
      request.onerror = <span class="hljs-function">() =&gt;</span> reject(request.error);
    });

    <span class="hljs-keyword">if</span> (result &amp;&amp; result.response) {
      <span class="hljs-keyword">return</span> <span class="hljs-built_in">JSON</span>.parse(result.response);
    }

    <span class="hljs-keyword">return</span> <span class="hljs-literal">null</span>;
  } <span class="hljs-keyword">catch</span> (error) {
    <span class="hljs-built_in">console</span>.error(<span class="hljs-string">"Error retrieving from IndexedDB:"</span>, error);
    <span class="hljs-keyword">return</span> <span class="hljs-literal">null</span>;
  }
}
</code></pre>
<h3 id="heading-how-to-serve-cached-resources"><strong>How to Serve Cached Resources</strong></h3>
<p>Once we have cached our assets and stored API data in IndexedDB, the next step is to serve this data to users when they are offline. There are several strategies to achieve this:</p>
<h4 id="heading-cache-first-strategy"><strong>Cache First Strategy</strong></h4>
<p>In the cache-first strategy, we check if a resource is available in the cache. If it is, we serve it from the cache; if not, we fetch it from the network. This is particularly useful for serving static assets like HTML, CSS, and JavaScript files:</p>
<pre><code class="lang-javascript"><span class="hljs-keyword">async</span> <span class="hljs-function"><span class="hljs-keyword">function</span> <span class="hljs-title">cacheFirstStrategy</span>(<span class="hljs-params">request</span>) </span>{
  <span class="hljs-keyword">try</span> {
    <span class="hljs-keyword">const</span> cache = <span class="hljs-keyword">await</span> caches.open(CACHE_NAME);
    <span class="hljs-keyword">const</span> cachedResponse = <span class="hljs-keyword">await</span> cache.match(request);

    <span class="hljs-keyword">if</span> (cachedResponse) {
      <span class="hljs-keyword">return</span> cachedResponse;
    }

    <span class="hljs-keyword">const</span> networkResponse = <span class="hljs-keyword">await</span> fetch(request);
    <span class="hljs-keyword">const</span> responseClone = networkResponse.clone();
    <span class="hljs-keyword">await</span> cache.put(request, responseClone);
    <span class="hljs-keyword">return</span> networkResponse;
  } <span class="hljs-keyword">catch</span> (error) {
    <span class="hljs-built_in">console</span>.error(<span class="hljs-string">"Cache first strategy failed:"</span>, error);
    <span class="hljs-keyword">return</span> caches.match(<span class="hljs-string">"/offline"</span>);
  }
}

self.addEventListener(<span class="hljs-string">"fetch"</span>, <span class="hljs-function">(<span class="hljs-params">event</span>) =&gt;</span> {
  <span class="hljs-keyword">const</span> { request } = event;
  <span class="hljs-keyword">if</span> (event.request.mode === <span class="hljs-string">"navigate"</span>) {
    event.respondWith(cacheFirstStrategy(request));
  } <span class="hljs-keyword">else</span> {
    event.respondWith(dynamicCaching(request));
  }
});
</code></pre>
<p>In this setup, the cache-first strategy is applied when navigating to new pages, while dynamic caching handles other requests.</p>
<h4 id="heading-network-first-strategy"><strong>Network First Strategy</strong></h4>
<p>The network-first strategy is the opposite: it attempts to fetch resources from the network first, and if the network is unavailable, it falls back to the cache. This strategy is particularly useful for API requests where you want the most up-to-date data:</p>
<pre><code class="lang-javascript"><span class="hljs-keyword">async</span> <span class="hljs-function"><span class="hljs-keyword">function</span> <span class="hljs-title">networkFirstStrategy</span>(<span class="hljs-params">request</span>) </span>{
  <span class="hljs-keyword">try</span> {
    <span class="hljs-keyword">const</span> networkResponse = <span class="hljs-keyword">await</span> fetch(request);

    <span class="hljs-keyword">if</span> (networkResponse.ok) {
      <span class="hljs-keyword">const</span> responseClone = networkResponse.clone();
      <span class="hljs-keyword">const</span> responseData = <span class="hljs-keyword">await</span> responseClone.json();
      <span class="hljs-keyword">await</span> addData(request.url, responseData);
      <span class="hljs-keyword">return</span> networkResponse;
    }

    <span class="hljs-keyword">throw</span> <span class="hljs-keyword">new</span> <span class="hljs-built_in">Error</span>(<span class="hljs-string">"Network response was not ok"</span>);
  } <span class="hljs-keyword">catch</span> (error) {
    <span class="hljs-built_in">console</span>.error(<span class="hljs-string">"Network first strategy failed:"</span>, error);
    <span class="hljs-keyword">const</span> cachedResponse = <span class="hljs-keyword">await</span> getData(request.url);

    <span class="hljs-keyword">if</span> (cachedResponse) {
      <span class="hljs-built_in">console</span>.log(<span class="hljs-string">"Using cached response:"</span>, cachedResponse);
      <span class="hljs-keyword">return</span> <span class="hljs-keyword">new</span> Response(<span class="hljs-built_in">JSON</span>.stringify(cachedResponse), {
        <span class="hljs-attr">status</span>: <span class="hljs-number">200</span>,
        <span class="hljs-attr">headers</span>: { <span class="hljs-string">"Content-Type"</span>: <span class="hljs-string">"application/json"</span> },
      });
    }

    <span class="hljs-keyword">return</span> <span class="hljs-keyword">new</span> Response(<span class="hljs-string">"[]"</span>, { <span class="hljs-attr">status</span>: <span class="hljs-number">200</span> });
  }
}

self.addEventListener(<span class="hljs-string">"fetch"</span>, <span class="hljs-function">(<span class="hljs-params">event</span>) =&gt;</span> {
  <span class="hljs-keyword">const</span> { request } = event;
  <span class="hljs-keyword">const</span> url = <span class="hljs-keyword">new</span> URL(request.url);

  <span class="hljs-keyword">if</span> (url.origin === <span class="hljs-string">"https://www.omdbapi.com"</span>) {
    event.respondWith(networkFirstStrategy(request));
  } <span class="hljs-keyword">else</span> <span class="hljs-keyword">if</span> (event.request.mode === <span class="hljs-string">"navigate"</span>) {
    event.respondWith(cacheFirstStrategy(request));
  } <span class="hljs-keyword">else</span> {
    event.respondWith(dynamicCaching(request));
  }
});
</code></pre>
<p>In our app, we use the network-first strategy for API calls, ensuring that the user gets the latest data when online, while falling back to cached data in IndexedDB when offline.</p>
<h4 id="heading-full-service-worker-code"><strong>Full Service Worker Code</strong></h4>
<p>Here’s the complete <strong>service-worker.js</strong> file that incorporates everything we’ve discussed:</p>
<pre><code class="lang-javascript"><span class="hljs-keyword">const</span> CACHE_NAME = <span class="hljs-string">"MOVIE_MASTER_V1"</span>;
<span class="hljs-keyword">const</span> DB_NAME = <span class="hljs-string">"MovieMaster"</span>;
<span class="hljs-keyword">const</span> DB_VERSION = <span class="hljs-number">1</span>;
<span class="hljs-keyword">const</span> DB_STORE_NAME = <span class="hljs-string">"myStore"</span>;

<span class="hljs-keyword">async</span> <span class="hljs-function"><span class="hljs-keyword">function</span> <span class="hljs-title">cacheCoreAssets</span>(<span class="hljs-params"></span>) </span>{
  <span class="hljs-keyword">const</span> cache = <span class="hljs-keyword">await</span> caches.open(CACHE_NAME);
  <span class="hljs-keyword">return</span> cache.addAll([
    <span class="hljs-string">"/"</span>,
    <span class="hljs-string">"/imdb-logo.svg"</span>,
    <span class="hljs-string">"/rotten-tomatoes-logo.svg"</span>,
    <span class="hljs-string">"/offline"</span>,
  ]);
}

self.addEventListener(<span class="hljs-string">"install"</span>, <span class="hljs-function">(<span class="hljs-params">event</span>) =&gt;</span> {
  event.waitUntil(cacheCoreAssets());
  self.skipWaiting();
});

<span class="hljs-keyword">async</span> <span class="hljs-function"><span class="hljs-keyword">function</span> <span class="hljs-title">clearOldCaches</span>(<span class="hljs-params"></span>) </span>{
  <span class="hljs-keyword">const</span> cacheNames = <span class="hljs-keyword">await</span> caches.keys();
  <span class="hljs-keyword">return</span> <span class="hljs-built_in">Promise</span>.all(
    cacheNames
      .filter(<span class="hljs-function">(<span class="hljs-params">name</span>) =&gt;</span> name !== CACHE_NAME)
      .map(<span class="hljs-function">(<span class="hljs-params">name</span>) =&gt;</span> caches.delete(name))
  );
}

self.addEventListener(<span class="hljs-string">"activate"</span>, <span class="hljs-function">(<span class="hljs-params">event</span>) =&gt;</span> {
  event.waitUntil(clearOldCaches());
  self.clients.claim();
});

<span class="hljs-keyword">async</span> <span class="hljs-function"><span class="hljs-keyword">function</span> <span class="hljs-title">dynamicCaching</span>(<span class="hljs-params">request</span>) </span>{
  <span class="hljs-keyword">const</span> cache = <span class="hljs-keyword">await</span> caches.open(CACHE_NAME);

  <span class="hljs-keyword">try</span> {
    <span class="hljs-keyword">const</span> response = <span class="hljs-keyword">await</span> fetch(request);
    <span class="hljs-keyword">const</span> responseClone = response.clone();
    <span class="hljs-keyword">await</span> cache.put(request, responseClone);
    <span class="hljs-keyword">return</span> response;
  } <span class="hljs-keyword">catch</span> (error) {
    <span class="hljs-built_in">console</span>.error(<span class="hljs-string">"Dynamic caching failed:"</span>, error);
    <span class="hljs-keyword">return</span> caches.match(request);
  }
}

<span class="hljs-function"><span class="hljs-keyword">function</span> <span class="hljs-title">openDb</span>(<span class="hljs-params"></span>) </span>{
  <span class="hljs-keyword">return</span> <span class="hljs-keyword">new</span> <span class="hljs-built_in">Promise</span>(<span class="hljs-function">(<span class="hljs-params">resolve, reject</span>) =&gt;</span> {
    <span class="hljs-keyword">const</span> request = indexedDB.open(DB_NAME, DB_VERSION);
    request.onsuccess = <span class="hljs-function">() =&gt;</span> resolve(request.result);
    request.onerror = <span class="hljs-function">() =&gt;</span> reject(request.error);
    request.onupgradeneeded = <span class="hljs-function">(<span class="hljs-params">event</span>) =&gt;</span> {
      <span class="hljs-keyword">const</span> db = event.target.result;
      db.createObjectStore(DB_STORE_NAME, { <span class="hljs-attr">keyPath</span>: <span class="hljs-string">"url"</span> });
    };
  });
}

<span class="hljs-keyword">async</span> <span class="hljs-function"><span class="hljs-keyword">function</span> <span class="hljs-title">addData</span>(<span class="hljs-params">url, jsonData</span>) </span>{
  <span class="hljs-keyword">const</span> db = <span class="hljs-keyword">await</span> openDb();
  <span class="hljs-keyword">const</span> transaction = db.transaction(DB_STORE_NAME, <span class="hljs-string">"readwrite"</span>);
  <span class="hljs-keyword">const</span> store = transaction.objectStore(DB_STORE_NAME);

  <span class="hljs-keyword">const</span> data = {
    url,
    <span class="hljs-attr">response</span>: <span class="hljs-built_in">JSON</span>.stringify(jsonData),
  };

  <span class="hljs-keyword">const</span> request = store.put(data);
  <span class="hljs-keyword">await</span> <span class="hljs-keyword">new</span> <span class="hljs-built_in">Promise</span>(<span class="hljs-function">(<span class="hljs-params">resolve, reject</span>) =&gt;</span> {
    request.onsuccess = <span class="hljs-function">() =&gt;</span> resolve();
    request.onerror = <span class="hljs-function">() =&gt;</span> reject(request.error);
  });
}

<span class="hljs-keyword">async</span> <span class="hljs-function"><span class="hljs-keyword">function</span> <span class="hljs-title">getData</span>(<span class="hljs-params">url</span>) </span>{
  <span class="hljs-keyword">try</span> {
    <span class="hljs-keyword">const</span> db = <span class="hljs-keyword">await</span> openDb();
    <span class="hljs-keyword">const</span> transaction = db.transaction(DB_STORE_NAME, <span class="hljs-string">"readonly"</span>);
    <span class="hljs-keyword">const</span> store = transaction.objectStore(DB_STORE_NAME);

    <span class="hljs-keyword">const</span> request = store.get(url);

    <span class="hljs-keyword">const</span> result = <span class="hljs-keyword">await</span> <span class="hljs-keyword">new</span> <span class="hljs-built_in">Promise</span>(<span class="hljs-function">(<span class="hljs-params">resolve, reject</span>) =&gt;</span> {
      request.onsuccess = <span class="hljs-function">() =&gt;</span> resolve(request.result);
      request.onerror = <span class="hljs-function">() =&gt;</span> reject(request.error);
    });

    <span class="hljs-keyword">if</span> (result &amp;&amp; result.response) {
      <span class="hljs-keyword">return</span> <span class="hljs-built_in">JSON</span>.parse(result.response);
    }

    <span class="hljs-keyword">return</span> <span class="hljs-literal">null</span>;
  } <span class="hljs-keyword">catch</span> (error) {
    <span class="hljs-built_in">console</span>.error(<span class="hljs-string">"Error retrieving from IndexedDB:"</span>, error);
    <span class="hljs-keyword">return</span> <span class="hljs-literal">null</span>;
  }
}

<span class="hljs-keyword">async</span> <span class="hljs-function"><span class="hljs-keyword">function</span> <span class="hljs-title">cacheFirstStrategy</span>(<span class="hljs-params">request</span>) </span>{
  <span class="hljs-keyword">try</span> {
    <span class="hljs-keyword">const</span> cache = <span class="hljs-keyword">await</span> caches.open(CACHE_NAME);
    <span class="hljs-keyword">const</span> cachedResponse = <span class="hljs-keyword">await</span> cache.match(request);

    <span class="hljs-keyword">if</span> (cachedResponse) {
      <span class="hljs-keyword">return</span> cachedResponse;
    }

    <span class="hljs-keyword">const</span> networkResponse = <span class="hljs-keyword">await</span> fetch(request);
    <span class="hljs-keyword">const</span> responseClone = networkResponse.clone();
    <span class="hljs-keyword">await</span> cache.put(request, responseClone);
    <span class="hljs-keyword">return</span> networkResponse;
  } <span class="hljs-keyword">catch</span> (error) {
    <span class="hljs-built_in">console</span>.error(<span class="hljs-string">"Cache first strategy failed:"</span>, error);
    <span class="hljs-keyword">return</span> caches.match(<span class="hljs-string">"/offline"</span>);
  }
}

<span class="hljs-keyword">async</span> <span class="hljs-function"><span class="hljs-keyword">function</span> <span class="hljs-title">networkFirstStrategy</span>(<span class="hljs-params">request</span>) </span>{
  <span class="hljs-keyword">try</span> {
    <span class="hljs-keyword">const</span> networkResponse = <span class="hljs-keyword">await</span> fetch(request);

    <span class="hljs-keyword">if</span> (networkResponse.ok) {
      <span class="hljs-keyword">const</span> responseClone = networkResponse.clone();
      <span class="hljs-keyword">const</span> responseData = <span class="hljs-keyword">await</span> responseClone.json();
      <span class="hljs-keyword">await</span> addData(request.url, responseData);
      <span class="hljs-keyword">return</span> networkResponse;
    }

    <span class="hljs-keyword">throw</span> <span class="hljs-keyword">new</span> <span class="hljs-built_in">Error</span>(<span class="hljs-string">"Network response was not ok"</span>);
  } <span class="hljs-keyword">catch</span> (error) {
    <span class="hljs-built_in">console</span>.error(<span class="hljs-string">"Network first strategy failed:"</span>, error);
    <span class="hljs-keyword">const</span> cachedResponse = <span class="hljs-keyword">await</span> getData(request.url);

    <span class="hljs-keyword">if</span> (cachedResponse) {
      <span class="hljs-built_in">console</span>.log(<span class="hljs-string">"Using cached response:"</span>, cachedResponse);
      <span class="hljs-keyword">return</span> <span class="hljs-keyword">new</span> Response(<span class="hljs-built_in">JSON</span>.stringify(cachedResponse), {
        <span class="hljs-attr">status</span>: <span class="hljs-number">200</span>,
        <span class="hljs-attr">headers</span>: { <span class="hljs-string">"Content-Type"</span>: <span class="hljs-string">"application/json"</span> },
      });
    }

    <span class="hljs-keyword">return</span> <span class="hljs-keyword">new</span> Response(<span class="hljs-string">"[]"</span>, { <span class="hljs-attr">status</span>: <span class="hljs-number">200</span> });
  }
}

self.addEventListener(<span class="hljs-string">"fetch"</span>, <span class="hljs-function">(<span class="hljs-params">event</span>) =&gt;</span> {
  <span class="hljs-keyword">const</span> { request } = event;
  <span class="hljs-keyword">const</span> url = <span class="hljs-keyword">new</span> URL(request.url);

  <span class="hljs-keyword">if</span> (url.origin === <span class="hljs-string">"https://www.omdbapi.com"</span>) {
    event.respondWith(networkFirstStrategy(request));
  } <span class="hljs-keyword">else</span> <span class="hljs-keyword">if</span> (event.request.mode === <span class="hljs-string">"navigate"</span>) {
    event.respondWith(cacheFirstStrategy(request));
  } <span class="hljs-keyword">else</span> {
    event.respondWith(dynamicCaching(request));
  }
});
</code></pre>
<p>With this setup, your PWA is now fully equipped to handle both static and dynamic content, provide an offline experience, and cache API data intelligently.</p>
<h4 id="heading-further-reading"><strong>Further Reading</strong></h4>
<p>There are many more strategies and nuances to building a robust offline experience with service workers. If you want to dive deeper into this topic, consider reading more about:</p>
<ul>
<li><p>Different caching strategies: Cache-First, Network-First, Stale-While-Revalidate, and so on.</p>
</li>
<li><p>Advanced service worker features like background sync and push notifications.</p>
</li>
<li><p>Best practices for managing cache and storage limits</p>
</li>
</ul>
<p>By understanding and implementing these concepts, you can ensure that your app remains functional and user-friendly, even in challenging network conditions.</p>
<h3 id="heading-providing-a-fallback-page"><strong>Providing a Fallback Page</strong></h3>
<p>Even with caching strategies in place, there may be times when users try to access a resource that isn’t available offline and in the network. To handle these situations gracefully, we can create a fallback page. This page will be shown whenever a user tries to access content that can't be retrieved from either the cache or the network.</p>
<p>If you cloned the example project for this tutorial, you should already have a fallback page located in the app directory. This page is designed to handle offline scenarios gracefully and includes a simple Tic-Tac-Toe game for users to play while waiting for the connection to be restored. Here’s what the fallback page looks like:</p>
<pre><code class="lang-javascript"><span class="hljs-string">"use client"</span>;
<span class="hljs-keyword">import</span> TicTacToe <span class="hljs-keyword">from</span> <span class="hljs-string">"@/components/TicTacToe"</span>;
<span class="hljs-keyword">import</span> { useState, useEffect } <span class="hljs-keyword">from</span> <span class="hljs-string">"react"</span>;
<span class="hljs-keyword">import</span> { useRouter } <span class="hljs-keyword">from</span> <span class="hljs-string">"next/navigation"</span>;
<span class="hljs-keyword">import</span> Link <span class="hljs-keyword">from</span> <span class="hljs-string">"next/link"</span>;

<span class="hljs-keyword">const</span> Fallback: React.FC = <span class="hljs-function">() =&gt;</span> {
  <span class="hljs-keyword">const</span> [isOnline, setIsOnline] = useState(<span class="hljs-literal">false</span>);
  <span class="hljs-keyword">const</span> router = useRouter();

  useEffect(<span class="hljs-function">() =&gt;</span> {
    <span class="hljs-keyword">const</span> handleOnline = <span class="hljs-function">() =&gt;</span> {
      setIsOnline(<span class="hljs-literal">true</span>);
      <span class="hljs-comment">// Redirect to homepage if online</span>
      router.push(<span class="hljs-string">"/"</span>);
    };

    <span class="hljs-keyword">const</span> handleOffline = <span class="hljs-function">() =&gt;</span> {
      setIsOnline(<span class="hljs-literal">false</span>);
    };

    <span class="hljs-built_in">window</span>.addEventListener(<span class="hljs-string">"online"</span>, handleOnline);
    <span class="hljs-built_in">window</span>.addEventListener(<span class="hljs-string">"offline"</span>, handleOffline);

    <span class="hljs-keyword">return</span> <span class="hljs-function">() =&gt;</span> {
      <span class="hljs-built_in">window</span>.removeEventListener(<span class="hljs-string">"online"</span>, handleOnline);
      <span class="hljs-built_in">window</span>.removeEventListener(<span class="hljs-string">"offline"</span>, handleOffline);
    };
  }, [router]);

  <span class="hljs-keyword">const</span> handleRefresh = <span class="hljs-function">() =&gt;</span> {
    <span class="hljs-keyword">if</span> (navigator.onLine) {
      router.push(<span class="hljs-string">"/"</span>);
    } <span class="hljs-keyword">else</span> {
      setIsOnline(<span class="hljs-literal">false</span>);
    }
  };

  <span class="hljs-keyword">return</span> (
    <span class="xml"><span class="hljs-tag">&lt;<span class="hljs-name">div</span> <span class="hljs-attr">className</span>=<span class="hljs-string">"flex mx-auto h-screen max-w-[500px] w-full flex-col items-center justify-center h-screen bg-foreground text-black p-6 mt-12 text-white"</span>&gt;</span>
      <span class="hljs-tag">&lt;<span class="hljs-name">h1</span> <span class="hljs-attr">className</span>=<span class="hljs-string">"text-3xl font-bold mb-6"</span>&gt;</span>
        {isOnline ? "You are online!" : "You are offline"}
      <span class="hljs-tag">&lt;/<span class="hljs-name">h1</span>&gt;</span>
      <span class="hljs-tag">&lt;<span class="hljs-name">p</span> <span class="hljs-attr">className</span>=<span class="hljs-string">"text-lg text-center mb-6"</span>&gt;</span>
        {isOnline
          ? "You are back online."
          : "Please check your internet connection and try again."}
      <span class="hljs-tag">&lt;/<span class="hljs-name">p</span>&gt;</span>
      <span class="hljs-tag">&lt;<span class="hljs-name">div</span> <span class="hljs-attr">className</span>=<span class="hljs-string">""</span>&gt;</span>
        <span class="hljs-tag">&lt;<span class="hljs-name">TicTacToe</span> /&gt;</span>
      <span class="hljs-tag">&lt;/<span class="hljs-name">div</span>&gt;</span>
      {isOnline ? (
        <span class="hljs-tag">&lt;<span class="hljs-name">Link</span>
          <span class="hljs-attr">href</span>=<span class="hljs-string">{</span>"/"}
          <span class="hljs-attr">className</span>=<span class="hljs-string">"mt-6 px-4 py-2 bg-blue-500 text-white rounded shadow hover:bg-blue-600"</span>
        &gt;</span>
          Return to Homepage
        <span class="hljs-tag">&lt;/<span class="hljs-name">Link</span>&gt;</span>
      ) : (
        <span class="hljs-tag">&lt;<span class="hljs-name">button</span>
          <span class="hljs-attr">onClick</span>=<span class="hljs-string">{handleRefresh}</span>
          <span class="hljs-attr">className</span>=<span class="hljs-string">"mt-6 px-4 py-2 bg-blue-500 text-white rounded shadow hover:bg-blue-600"</span>
        &gt;</span>
          Refresh
        <span class="hljs-tag">&lt;/<span class="hljs-name">button</span>&gt;</span>
      )}
    <span class="hljs-tag">&lt;/<span class="hljs-name">div</span>&gt;</span></span>
  );
};

<span class="hljs-keyword">export</span> <span class="hljs-keyword">default</span> Fallback;
</code></pre>
<p><img src="https://cdn.hashnode.com/res/hashnode/image/upload/v1726726501345/46e5c3a7-7640-4e54-b6b8-2f43c5b24dcd.png" alt="An image showing our Fallback page" width="1600" height="1195" loading="lazy"></p>
<p><strong>Note:</strong> You can customize this fallback page to suit your application’s needs, whether that’s displaying helpful offline content, providing a message, or offering a small interactive feature like the Tic-Tac-Toe game included here.</p>
<h4 id="heading-caching-the-fallback-page"><strong>Caching the Fallback Page</strong></h4>
<p>Next, ensure that the fallback page is cached when the service worker is installed:</p>
<pre><code class="lang-javascript"><span class="hljs-keyword">const</span> CACHE_NAME = <span class="hljs-string">"MOVIE_MASTER_V1"</span>;

<span class="hljs-keyword">async</span> <span class="hljs-function"><span class="hljs-keyword">function</span> <span class="hljs-title">cacheCoreAssets</span>(<span class="hljs-params"></span>) </span>{
  <span class="hljs-keyword">const</span> cache = <span class="hljs-keyword">await</span> caches.open(CACHE_NAME);
  <span class="hljs-keyword">return</span> <span class="hljs-keyword">await</span> cache.addAll([
    <span class="hljs-string">"/"</span>,
    <span class="hljs-string">"/fallback"</span>,
    <span class="hljs-comment">// other assets</span>
  ]);
}
</code></pre>
<h4 id="heading-serving-the-fallback-page"><strong>Serving the Fallback Page</strong></h4>
<p>Finally, modify the <code>cacheFirstStrategy</code> to serve the <strong>offline.html</strong> page when a request fails:</p>
<pre><code class="lang-javascript"><span class="hljs-keyword">async</span> <span class="hljs-function"><span class="hljs-keyword">function</span> <span class="hljs-title">cacheFirstStrategy</span>(<span class="hljs-params">request</span>) </span>{
  <span class="hljs-keyword">try</span> {
    <span class="hljs-keyword">const</span> cache = <span class="hljs-keyword">await</span> caches.open(CACHE_NAME);
    <span class="hljs-keyword">const</span> cachedResponse = <span class="hljs-keyword">await</span> cache.match(request);

    <span class="hljs-keyword">if</span> (cachedResponse) {
      <span class="hljs-keyword">return</span> cachedResponse;
    }

    <span class="hljs-keyword">const</span> networkResponse = <span class="hljs-keyword">await</span> fetch(request);
    <span class="hljs-keyword">const</span> responseClone = networkResponse.clone();
    <span class="hljs-keyword">await</span> cache.put(request, responseClone);
    <span class="hljs-keyword">return</span> networkResponse;
  } <span class="hljs-keyword">catch</span> (error) {
    <span class="hljs-built_in">console</span>.error(<span class="hljs-string">"Cache first strategy failed:"</span>, error);
    <span class="hljs-keyword">return</span> caches.match(<span class="hljs-string">"/offline.html"</span>);
  }
}
</code></pre>
<p>This approach ensures that users always see a meaningful message instead of an error when they’re offline or when a resource is unavailable.</p>
<h2 id="heading-conclusion"><strong>Conclusion</strong></h2>
<p>With our Next.js app set up, we’ve successfully transformed it into a fully functional Progressive Web App (PWA), making it better and more user-friendly.</p>
<p>This guide showed how to build a strong PWA using Next.js by adding features like offline support, caching, and service workers. These improvements boost performance and provide a smooth experience on all devices, combining the best of web and native apps.</p>
<p>With these tips, you’ll be ready to create engaging, reliable, and high-performance PWAs that stand out in web development.</p>
 ]]>
                </content:encoded>
            </item>
        
            <item>
                <title>
                    <![CDATA[ What are Progressive Web Apps? PWA Guide for Beginners ]]>
                </title>
                <description>
                    <![CDATA[ Progressive Web Apps (PWAs) are simply installable web applications – websites that you can install on your device, much like you would install an app from an app store.  They bring together the best parts of using a website (easy to access, no need ... ]]>
                </description>
                <link>https://www.freecodecamp.org/news/what-are-progressive-web-apps-pwa-guide/</link>
                <guid isPermaLink="false">66c5a34c3d77fae9eb82a477</guid>
                
                    <category>
                        <![CDATA[ progressive web app ]]>
                    </category>
                
                    <category>
                        <![CDATA[ PWA ]]>
                    </category>
                
                <dc:creator>
                    <![CDATA[ Ophy Boamah ]]>
                </dc:creator>
                <pubDate>Thu, 18 Jan 2024 15:56:46 +0000</pubDate>
                <media:content url="https://www.freecodecamp.org/news/content/images/2024/01/Essentials.png" medium="image" />
                <content:encoded>
                    <![CDATA[ <p>Progressive Web Apps (PWAs) are simply installable web applications – websites that you can install on your device, much like you would install an app from an app store. </p>
<p>They bring together the best parts of using a website (easy to access, no need to install anything) with the great features of mobile apps (fast, can work offline), offering a high-quality user experience. </p>
<p>The core of a PWA's usefulness lies in the offline-first approach, where applications are designed to function seamlessly without a constant internet connection. This means you can still use these apps even when your internet is slow or not available, and makes PWAs very user-friendly and accessible, even where an internet connection is not always reliable.</p>
<p><img src="https://www.freecodecamp.org/news/content/images/2024/01/pwaa.png" alt="Image" width="600" height="400" loading="lazy">
<em>Graphic showing a native app vs a web application vs a PWA</em></p>
<h2 id="heading-5-reasons-to-use-a-pwa-instead-of-a-native-app">5 Reasons to Use a PWA Instead of a Native app</h2>
<p>Progressive Web Apps (PWAs) are reshaping the landscape of digital interaction. They offer a hybrid experience that combines the best features of web and mobile apps. </p>
<p>Let's delve deeper into why PWAs are often a superior choice compared to native apps:</p>
<h3 id="heading-1-speed-and-ease-of-installation">1. Speed and Ease of Installation</h3>
<p>PWAs eliminate the lengthy download and installation process typical of native apps. You can access a PWA as swiftly as you'd load a web page, making it as straightforward as bookmarking your favorite site – no waiting for downloads or navigating through app stores. </p>
<p>Consider Twitter Lite, a PWA version of the popular social media platform, which offers the full Twitter experience without the need to wait for a separate app download. </p>
<h3 id="heading-2-minimal-storage-requirements">2. Minimal Storage Requirements</h3>
<p>One of the most significant advantages of PWAs is their minimal storage requirements. Since they primarily store data online, much like cloud services, they occupy significantly less space on a user’s device. </p>
<p>This feature is particularly beneficial for users with limited device storage or expensive data plans. For example, Pinterest’s PWA takes up less space than its native counterpart but still provides a rich, engaging user experience. </p>
<h3 id="heading-3-reliable-offline-performance">3. Reliable Offline Performance</h3>
<p>A PWA's ability to function offline is like having a good book downloaded to your e-reader, ready to enjoy even when you're out of range of a good signal. </p>
<p>PWAs can function effectively even in areas with poor or no internet connectivity, using cached data from previous online activities. This ensures that users have uninterrupted access to essential features. </p>
<p>A notable example is the Starbucks PWA, which allows customers to browse the menu and customize orders, regardless of their internet connection for a consistent user experience.</p>
<h3 id="heading-4-cost-effective-development-and-cross-device-compatibility">4. Cost-Effective Development and Cross-Device Compatibility</h3>
<p>Developing a PWA can be more cost-effective than building native apps for various platforms. This unified approach not only saves significant development time and resources but also simplifies maintenance and updates. </p>
<p>Businesses like Uber have leveraged PWAs to provide a seamless user experience across all devices without the need for multiple native apps. </p>
<h3 id="heading-5-automatic-updates-and-push-notifications">5. Automatic Updates and Push Notifications</h3>
<p>PWAs update themselves seamlessly, much like a web page loading the latest content each time it’s visited. Users always have access to the most current version of the app without going through the process of manual updates. This feature ensures that users are not hindered by outdated versions, a common issue with native apps. </p>
<p>For instance, Google Maps’ PWA automatically incorporates the latest features and improvements without user intervention. In addition to staying current, PWAs can engage users with push notifications, just like native apps, keeping them informed and involved without the need for a visit to the app store. </p>
<p><img src="https://www.freecodecamp.org/news/content/images/2024/01/pwass.png" alt="Image" width="600" height="400" loading="lazy">
<em>Infographic of a Progressive Web App (PWA) showcasing its features: adaptability across devices, security, offline functionality, fast performance, updates, and push notifications.</em></p>
<h2 id="heading-pwa-core-concepts">PWA Core Concepts</h2>
<p>To fully grasp the potential and functionality of Progressive Web Apps, it's essential to understand their foundational components. These core concepts not only define the structure and behavior of PWAs but also distinguish them from traditional web applications.</p>
<h3 id="heading-service-workers-the-backbone-of-pwas">Service Workers: The Backbone of PWAs</h3>
<p>Service workers are scripts that run in the background, separate from your web page. They act as a proxy between the web application and the network. </p>
<p>Think of service workers as behind-the-scenes helpers for your web app. Their main job is to manage how your app talks to the internet. </p>
<p>They can save important parts of your app on the user's device, which means your app can work even when there's no internet. They're also in charge of quietly updating app content and can send notifications, just like a native app on your phone.</p>
<h3 id="heading-the-app-manifest-your-pwas-identity">The App Manifest: Your PWA's Identity</h3>
<p>The web app manifest is a JSON file which is essential because it tells the user's device how your app should look and behave. </p>
<p>It's like your app's ID card – it includes the app's name, the icons it uses, the first page it should open, and how it should display (like in full screen). This file makes your web app feel more like a regular app, allowing users to 'install' it on their home screen.</p>
<h3 id="heading-caching-the-key-to-offline-functionality">Caching: The Key to Offline Functionality</h3>
<p>Effective caching is vital for a robust offline experience. Caching is like your app's memory. It stores important parts of your app so they can be quickly loaded later, even if there's no internet. It's crucial for making your app work offline. </p>
<p>There are different ways to handle caching, such as cache-first (where the app checks the cache before the internet), network-first (the opposite), and stale-while-revalidate (a mix of both). The choice depends on what your app does and what kind of information it handles, affecting how your app stores and retrieves its data.</p>
<p><img src="https://www.freecodecamp.org/news/content/images/2024/01/pwa-components.png" alt="Image" width="600" height="400" loading="lazy">
<em>Icons representing key components of a PWA: Service worker, Manifest and HTTPS</em></p>
<h2 id="heading-overview-of-building-a-pwa">Overview of Building a PWA</h2>
<p>Building a Progressive Web App (PWA) might sound complex, but it's quite manageable when you break it down into steps. In this article, I'll give you an overview of the process – and in my next tutorial, I'll go into the process in more detail.</p>
<h3 id="heading-1-start-with-a-basic-webpage">1. Start with a Basic Webpage:</h3>
<p>Create a simple website using HTML for structure, CSS for style, and JavaScript for functionality.</p>
<pre><code class="lang-html"><span class="hljs-meta">&lt;!DOCTYPE <span class="hljs-meta-keyword">html</span>&gt;</span>
<span class="hljs-tag">&lt;<span class="hljs-name">html</span>&gt;</span>
<span class="hljs-tag">&lt;<span class="hljs-name">head</span>&gt;</span>
    <span class="hljs-tag">&lt;<span class="hljs-name">title</span>&gt;</span>Ophy's To-Do List PWA<span class="hljs-tag">&lt;/<span class="hljs-name">title</span>&gt;</span>
<span class="hljs-tag">&lt;/<span class="hljs-name">head</span>&gt;</span>
<span class="hljs-tag">&lt;<span class="hljs-name">body</span>&gt;</span>
    <span class="hljs-tag">&lt;<span class="hljs-name">h1</span>&gt;</span>Ophy's To-Do List<span class="hljs-tag">&lt;/<span class="hljs-name">h1</span>&gt;</span>
    <span class="hljs-tag">&lt;<span class="hljs-name">input</span> <span class="hljs-attr">type</span>=<span class="hljs-string">"text"</span> <span class="hljs-attr">id</span>=<span class="hljs-string">"todo-input"</span> <span class="hljs-attr">placeholder</span>=<span class="hljs-string">"Add a new task..."</span>&gt;</span>
    <span class="hljs-tag">&lt;<span class="hljs-name">button</span> <span class="hljs-attr">onclick</span>=<span class="hljs-string">"addTask()"</span>&gt;</span>Add<span class="hljs-tag">&lt;/<span class="hljs-name">button</span>&gt;</span>
    <span class="hljs-tag">&lt;<span class="hljs-name">ul</span> <span class="hljs-attr">id</span>=<span class="hljs-string">"todo-list"</span>&gt;</span>
        <span class="hljs-comment">&lt;!-- Tasks will go here --&gt;</span>
    <span class="hljs-tag">&lt;/<span class="hljs-name">ul</span>&gt;</span>

    <span class="hljs-tag">&lt;<span class="hljs-name">script</span> <span class="hljs-attr">src</span>=<span class="hljs-string">"app.js"</span>&gt;</span><span class="hljs-tag">&lt;/<span class="hljs-name">script</span>&gt;</span>
<span class="hljs-tag">&lt;/<span class="hljs-name">body</span>&gt;</span>
<span class="hljs-tag">&lt;/<span class="hljs-name">html</span>&gt;</span>
</code></pre>
<pre><code class="lang-css"><span class="hljs-selector-tag">body</span> {
    <span class="hljs-attribute">font-family</span>: <span class="hljs-string">'Arial'</span>, sans-serif;
}

<span class="hljs-selector-id">#todo-list</span> {
    <span class="hljs-attribute">list-style-type</span>: none;
}

<span class="hljs-selector-id">#todo-list</span> <span class="hljs-selector-tag">li</span> {
    <span class="hljs-attribute">padding</span>: <span class="hljs-number">5px</span>;
    <span class="hljs-attribute">margin</span>: <span class="hljs-number">5px</span>;
    <span class="hljs-attribute">background-color</span>: <span class="hljs-number">#f2f2f2</span>;
    <span class="hljs-attribute">border-radius</span>: <span class="hljs-number">3px</span>;
}
</code></pre>
<pre><code class="lang-javascript"><span class="hljs-function"><span class="hljs-keyword">function</span> <span class="hljs-title">addTask</span>(<span class="hljs-params"></span>) </span>{
    <span class="hljs-keyword">var</span> input = <span class="hljs-built_in">document</span>.getElementById(<span class="hljs-string">'todo-input'</span>);
    <span class="hljs-keyword">var</span> newTask = input.value;
    <span class="hljs-keyword">if</span> (newTask) {
        <span class="hljs-keyword">var</span> listItem = <span class="hljs-built_in">document</span>.createElement(<span class="hljs-string">'li'</span>);
        listItem.textContent = newTask;
        <span class="hljs-built_in">document</span>.getElementById(<span class="hljs-string">'todo-list'</span>).appendChild(listItem);
        input.value = <span class="hljs-string">''</span>; <span class="hljs-comment">// Clear the input</span>
    }
}
</code></pre>
<h3 id="heading-2-create-a-manifest-file">2. Create a Manifest File:</h3>
<p>In the manifest file, write down your app's name, the icons it uses, and the first page it should open. This makes your website act more like an app you can install.</p>
<pre><code class="lang-json">{
    <span class="hljs-attr">"name"</span>: <span class="hljs-string">"Ophy's To-Do List"</span>,
    <span class="hljs-attr">"short_name"</span>: <span class="hljs-string">"OphyToDo"</span>,
    <span class="hljs-attr">"icons"</span>: [
        {
            <span class="hljs-attr">"src"</span>: <span class="hljs-string">"favicon.ico"</span>,
            <span class="hljs-attr">"sizes"</span>: <span class="hljs-string">"64x64 32x32 24x24 16x16"</span>,
            <span class="hljs-attr">"type"</span>: <span class="hljs-string">"image/x-icon"</span>
        }
    ],
    <span class="hljs-attr">"start_url"</span>: <span class="hljs-string">"/"</span>,
    <span class="hljs-attr">"display"</span>: <span class="hljs-string">"standalone"</span>,
    <span class="hljs-attr">"theme_color"</span>: <span class="hljs-string">"#000000"</span>,
    <span class="hljs-attr">"background_color"</span>: <span class="hljs-string">"#ffffff"</span>
}
</code></pre>
<h3 id="heading-3-set-up-a-service-worker">3. Set Up a Service Worker:</h3>
<p>In your main JavaScript file, add a service worker. This is a special script that works separately from your website.</p>
<p>The service worker's job is to handle how your app stores and retrieves data, especially for offline use.</p>
<pre><code class="lang-javascript">self.addEventListener(<span class="hljs-string">'install'</span>, <span class="hljs-function"><span class="hljs-keyword">function</span>(<span class="hljs-params">event</span>) </span>{
    <span class="hljs-comment">// Perform install steps</span>
    <span class="hljs-keyword">var</span> CACHE_NAME = <span class="hljs-string">'ophy-todo-cache-v1'</span>;
    <span class="hljs-keyword">var</span> urlsToCache = [
        <span class="hljs-string">'/'</span>,
        <span class="hljs-string">'/styles.css'</span>,
        <span class="hljs-string">'/app.js'</span>
    ];

    event.waitUntil(
        caches.open(CACHE_NAME)
            .then(<span class="hljs-function"><span class="hljs-keyword">function</span>(<span class="hljs-params">cache</span>) </span>{
                <span class="hljs-built_in">console</span>.log(<span class="hljs-string">'Opened cache'</span>);
                <span class="hljs-keyword">return</span> cache.addAll(urlsToCache);
            })
    );
});
</code></pre>
<p>Following these steps will help you build a simple PWA that users can install and use, even when they're offline.</p>
<h2 id="heading-real-world-use-cases-for-pwas">Real World Use Cases for PWAs</h2>
<p>These real-world examples demonstrate the practical benefits and impact of offline-first strategies:</p>
<ol>
<li><strong>Starbucks</strong>: Starbucks has a PWA that lets customers look at the menu and order drinks and food even when they're offline. This not only makes the experience better for customers but also helps increase sales and customer interaction with the brand, as they can order anytime, anywhere.</li>
<li><strong>Khan academy</strong>: Khan Academy, a well-known educational site, has a PWA allowing students to download and access lessons and courses without an internet connection. This feature is particularly helpful for those in areas with unreliable internet, ensuring uninterrupted learning. Just like the Starbucks PWA, Khan Academy's use of this technology improves the user experience, making education more accessible and flexible.</li>
<li><strong>Trivago</strong>: Trivago, a popular hotel search engine, developed a PWA to enhance user experience, especially for travelers with unstable internet connections. The PWA allows users to browse hotel deals and information offline, making trip planning more flexible and accessible. This has led to increased user engagement and higher conversion rates, as travelers can continue interacting with the app even in areas with poor connectivity.</li>
<li><strong>Forbes</strong>: Forbes, a leading global media company, launched a PWA to improve the mobile experience for its readers. The PWA significantly reduced loading times and allowed offline reading of articles. This innovation not only enhanced user engagement but also resulted in a noticeable increase in readership and time spent on the site. The Forbes PWA demonstrates how media outlets can leverage offline-first strategies to reach and retain a wider audience, regardless of their internet connectivity.</li>
</ol>
<h2 id="heading-conclusion">Conclusion</h2>
<p>PWAs are changing the game in web development, bringing features we usually see in native apps to the web. </p>
<p>They offer a blend of the accessibility of web applications with the engaging user experience of native apps. They represent a forward-thinking solution for businesses and developers looking to maximize reach and functionality while minimizing costs and complexity. </p>
<p>PWAs aren't just a passing trend – they're the next big thing in how we make and use web apps. By focusing on working offline, PWAs provide a more reliable and user-friendly experience. </p>
<p>Whether you're just curious or ready to start building, the following resources remain great places to begin exploring the possibilities of offline-first web applications.</p>
<ul>
<li><a target="_blank" href="https://developers.google.com/web/progressive-web-apps/checklist">Google's PWA Checklist</a></li>
<li><a target="_blank" href="https://developer.mozilla.org/en-US/docs/Web/API/Service_Worker_API">MDN Web Docs on Service Workers</a></li>
<li><a target="_blank" href="https://web.dev/progressive-web-apps/">Web.dev for PWA tutorials</a></li>
</ul>
 ]]>
                </content:encoded>
            </item>
        
            <item>
                <title>
                    <![CDATA[ What is a PWA? Progressive Web Apps for Beginners ]]>
                </title>
                <description>
                    <![CDATA[ These days, everything is made possible with the help of mobile phones and applications. Let's say you need to order food – you can do so instantly via the company's app. Maybe you need government services – the same thing applies. You can even get m... ]]>
                </description>
                <link>https://www.freecodecamp.org/news/what-are-progressive-web-apps/</link>
                <guid isPermaLink="false">66b8dc0a34a5a4a0df79b3ee</guid>
                
                    <category>
                        <![CDATA[ progressive web app ]]>
                    </category>
                
                    <category>
                        <![CDATA[ PWA ]]>
                    </category>
                
                    <category>
                        <![CDATA[ responsive design ]]>
                    </category>
                
                <dc:creator>
                    <![CDATA[ Hillary Nyakundi ]]>
                </dc:creator>
                <pubDate>Tue, 27 Jun 2023 12:00:00 +0000</pubDate>
                <media:content url="https://www.freecodecamp.org/news/content/images/2023/06/CoverImagePWA.png" medium="image" />
                <content:encoded>
                    <![CDATA[ <p>These days, everything is made possible with the help of mobile phones and applications.</p>
<p>Let's say you need to order food – you can do so instantly via the company's app. Maybe you need government services – the same thing applies. You can even get medical emergency dial services via an app.</p>
<p>There's an app for everything – from banking to studying and from trading to shopping. Every business has an app, and even our governments have simplified their services into app form.</p>
<p>Hold on, building and maintaining an app is cumbersome, and it's quite expensive for small businesses, so how do they manage?</p>
<p>Well it's simple: with the help of advancements in technology there is an option that helps small businesses out. This option combines the features of an app with the technology used in web development to build affordable services for businesses – I'm talking about <strong>Progressive Web Apps</strong>.</p>
<p>Let's dive in and get a better understanding of what PWAs are all about.</p>
<h2 id="heading-what-is-a-progressive-web-app">What is a Progressive Web App?</h2>
<p>Progressive Web Applications (PWAs) are apps built with web technologies that we probably all know and love, like HTML, CSS, and JavaScript. But they have the feel and functionality of an actual native app. Wait a minute! Native Apps, what do we mean by this?</p>
<p>A <strong>Native App</strong> is a software application built in a specific programming language for a specific device platform, either IOS or Android.<br>PWAs are built with the capabilities like push notifications and the ability to work offline. They are also built on and enhanced with modern APIs which makes it easy to deliver improved capabilities along with reliability and the ability to install them on any device.</p>
<p>PWAs takes advantage of the huge web ecosystem this is inclusive of the plugins, and community and the relative ease of deploying and keeping a website contrary to a native application which is pretty difficult to develop. This means you can build a PWA quickly and easily.</p>
<p>With its popularity many companies have shifted into the product, I tend to believe that this is because of its ability to run on an android and iOS without much difference. Some good examples of top companies who have their products as PWAs include: <em>Twitter, Pintrest, Uber, Tiktok, Spotify, Jumia (a leading e-commerce site in Africa)</em> etc...</p>
<p>A common feature about this products is that they are all installable on your home screen, able to work offline from where you last left and offer a comparable experience and features to their native apps.</p>
<p>Just like when building a native mobile app there are some expectations that should be met to make a good product for consumer use, the same thing applies to PWAs. Let's discuss what makes a good PWA.</p>
<h2 id="heading-characteristics-of-pwas">Characteristics of PWAs</h2>
<p>Below is what should be considered when developing a PWA:</p>
<h3 id="heading-responsiveness">Responsiveness</h3>
<p>Different companies produce gadgets with different screen sizes, and as a developer it's your responsibility to ensure all the different users enjoy the product regardless the device they are using. So it's a good idea to make sure your app can be used on any screen size and it's content is available at any view-port size.</p>
<h3 id="heading-installable">Installable</h3>
<p>Research has shown that users tend to engage more with installed apps compared to visiting the official sites. Having a PWA as your product gives the users the look, feel and engagement of a normal app.</p>
<h3 id="heading-independent-connectivity">Independent Connectivity</h3>
<p>By keeping a user engaged to your app even while they are offline, provides a more consistent experience than dropping them back to a default offline page.</p>
<p>A good example to illustrate this will be that of a music app, your users should be able to access offline playback and listen to saved music even without internet connection. Another good example is twitter app, a user is able to go back a read through tweets which they might have missed.</p>
<h3 id="heading-discoverability">Discoverability</h3>
<p>Since most PWAs are converted websites, it is fair to make them discoverable on the search engines, this will help generate extra traffic to your app. This also acts as an advantage over native apps which can't be discovered over the search engines.</p>
<h3 id="heading-appearance">Appearance</h3>
<p>The appearance of the app should feel and look like that of a normal app, so be sure to include things like an app icon, this will help make it easily recognizable also things like splash screen will add the touch and feel of an app.</p>
<h3 id="heading-cross-platform">Cross Platform</h3>
<p>PWAs are developed as web app first, which means that they need to work on all browsers/systems and not just a selected few. Users should be able to use them in any browser before they decide to install them.</p>
<p>So folks! there you have it, the general info about PWAs. Along the way you might have noticed occasionally a comparison between PWAs and Native App and this might have confused you a bit, Well let's clear the airwaves by checking the comparison between the two to get a clear understanding.</p>
<h2 id="heading-differences-between-pwas-and-native-apps">Differences Between PWAs and Native Apps</h2>
<h3 id="heading-development-cost">Development Cost</h3>
<p>PWAs are cheaper to develop compared to Native AppsWhen you're developing a native app, you'll have to learn a certain programming language and then build a version of the app for each type of device, Android and iOS. On the other hand you can choose to hire a experienced professional to do the work for you which will even turn out to be more costly.</p>
<p>Down the road, you will also need resources to maintain and update the app, which means lots of money and time is required.</p>
<p>In the case of a PWA, you can have a single codebase for the different platforms. It's also time-saving since you will not need to develop it from scratch you can configure your current web site to fit.</p>
<p>And if you choose to hire developer it will only be one compared to native where you can hire up-to two depending on where you need your app.</p>
<h3 id="heading-discoverability-1">Discoverability</h3>
<p>Native apps cannot be indexed by the search engines, they can just be found through the App/Play store's website. You can make your app more discoverable on the App/Play store by using App Store Optimization(ASO), but that's another story.</p>
<p>Unlike native apps, PWAs work like websites so they can be indexed by search engines. This helps them rank better in search results.</p>
<h3 id="heading-safety">Safety</h3>
<p>Nowadays in order to run a website, it should be encrypted with a SSL certificate, this adds an extra layer of security. Now, as we already know PWAs are site converted into app which means they are more secure because they run on HTTPS. These are security protocols that allow safe exchange of data between client and server so that is doesn't get tampered with.</p>
<p>To secure your native apps, you need to implement various security measures, like multi-factor authentication and so on.</p>
<h3 id="heading-installation-and-download">Installation and Download</h3>
<p>Native apps need to be downloaded and installed from an app store. This requires some commitment from the user to do it from start to finish. Users have to pass and check multiple permissions before installing an app.</p>
<p>On the other hand, PWAs don't require any of those steps. From the browser you can bookmark it and add the app to your home screen with just a few taps.</p>
<h2 id="heading-benefits-of-pwas">Benefits of PWAs</h2>
<p>A lot of organizations both private and public are switching to PWAs not only because they are cheap to develop but also because they offer greater engagement.<br>Now let's look at a quick summary of the benefits of a PWA:</p>
<ul>
<li>They are responsive and work with many different screen sizes.</li>
<li>They can run on multiple platforms and any device with a modern web browser.</li>
<li>They function just like normal Native Apps.</li>
<li>The updates are independent, you don't need to visit the play store for an update.</li>
<li>They're built with common web technologies.</li>
<li>They're fast and lightweight.</li>
<li>They work offline unlike other sites.</li>
<li>They are discoverable via search engine.</li>
<li>They are easily installable.</li>
<li>Low maintenance cost.</li>
</ul>
<h2 id="heading-requirements-to-get-started-with-pwa-development">Requirements to Get Started with PWA Development</h2>
<p>It does not take much to get started building a PWA. You just need a few things and you are good to go.</p>
<p><strong>Tools</strong><br>The best known technology stack to develop PWAs is AngularJS. Speaking of Angular, <a target="_blank" href="https://allfront.io/blog/how-to-take-your-angular-apps-offline/">here</a> is a resourceful guide on how you can convert your already existing Angular app into PWA. Others stacks include ReactJS and Polymer.</p>
<p><strong>HTTPS</strong><br>You will need a server with a HTTPS connection. This makes sure your user's data is secure. It adds an extra layer of security to you site.</p>
<p><strong>Application Shell</strong><br>It provides a good first impression when your app loads. In simpler words this is what the user sees when they interact with your app for the first time.</p>
<p><strong>Service workers</strong><br>This is one of the key technologies behind PWAs. They help support your app work offline, and they perform advanced caching and run background tasks. Service workers can complete tasks even when your PWA is not running.Some other functions associated with Service Worker include:</p>
<ul>
<li>Sending push notification</li>
<li>Badging icons</li>
<li>Running background fetch tasks etc...</li>
</ul>
<p><strong>Manifest file</strong><br>This is a JSON file that is created with a <a target="_blank" href="https://app-manifest.firebaseapp.com/">Web App Manifest Generator</a>. This file contains the information that tells how your PWA should appear and function. It allows you to determine the name, description, icon, colors and other features of your PWA. Here's an example of a manifest file:</p>
<pre><code class="lang-json">{
<span class="hljs-attr">"short_name"</span>: <span class="hljs-string">"DevBlogger"</span>,  
<span class="hljs-attr">"name"</span>: <span class="hljs-string">"DevBlogger"</span>,  
<span class="hljs-attr">"description"</span>: <span class="hljs-string">"All dev stories under one roof"</span>,
<span class="hljs-attr">"theme_color"</span>: <span class="hljs-string">"#eb5252"</span>,  
<span class="hljs-attr">"background_color"</span>: <span class="hljs-string">"#000000"</span>,  
<span class="hljs-attr">"display"</span>: <span class="hljs-string">"fullscreen"</span>,  
<span class="hljs-attr">"Scope"</span>: <span class="hljs-string">"/"</span>,  <span class="hljs-attr">"orientation"</span>: <span class="hljs-string">"portrait"</span>,  
<span class="hljs-attr">"icons"</span>: [    
    {
        <span class="hljs-attr">"src"</span>: <span class="hljs-string">"images/android/android-launchericon-48-48.png"</span>,      
        <span class="hljs-attr">"type"</span>: <span class="hljs-string">"image/png"</span>,      
        <span class="hljs-attr">"sizes"</span>: <span class="hljs-string">"48x48"</span>
    },
    {      
        <span class="hljs-attr">"src"</span>: <span class="hljs-string">"images/android/android-launchericon-96-96.png"</span>,
        <span class="hljs-attr">"type"</span>: <span class="hljs-string">"image/png"</span>,      
        <span class="hljs-attr">"sizes"</span>: <span class="hljs-string">"96x96"</span>    
    },    
    {      
        <span class="hljs-attr">"src"</span>: <span class="hljs-string">"images/android/android-launchericon-192-192.png"</span>,
        <span class="hljs-attr">"type"</span>: <span class="hljs-string">"image/png"</span>,      
        <span class="hljs-attr">"sizes"</span>: <span class="hljs-string">"192x192"</span>    
    }  
   ],  
       <span class="hljs-attr">"start_url"</span>: <span class="hljs-string">"index.html?utm_source=homescreen"</span>
  }
</code></pre>
<ul>
<li><strong>Audit your App</strong><br>This is possible using the Google Lighthouse tool. Google Lighthouse is a open-source software that anyone can use on any webpage. Google is a big champion of PWAs and pushes them as the future of the web. You can use Lighthouse to help you see how fast, accessible, and SEO readiness your PWA is.</li>
</ul>
<h2 id="heading-how-to-build-a-pwa">How to Build a PWA</h2>
<p>By following the steps below, you can easily create a fully functional PWA that offers an mazing user experience across all devices.</p>
<h3 id="heading-step-1-plan-your-app">Step 1 - Plan your app</h3>
<p>Before diving into development, you should consider the goals of your PWA, what features you want to include, priorities and user experience. You can create first design concepts and wireframes for the app to visualize the structure and layout.<br>In most scenarios, this is often referred to as a ‘<a target="_blank" href="https://allfront.io/blog/project-discovery-phase-guide/">discovery phase</a>’. You get the opportunity to ideate and gather user and stakeholder feedback as well as considering the functionalities of your to be product.</p>
<h3 id="heading-step-2-designing-the-user-interface">Step 2 - Designing the User Interface</h3>
<p>After getting everything right from planning, you can now proceed to designing the UI of your app. During this stage consider things like responsiveness, compatibility with different platforms etc.. Be sure to capture all details that are crucial to the user including their interaction and engagement during usage.</p>
<h3 id="heading-step-3-developing-the-front-end">Step 3 - Developing the Front-End</h3>
<p>Using the web technologies that is HTML, CSS, JavaScript and frameworks like Angular. React or Vue.js develop a visually appealing interface for the users. And always remember they key principle in development using this stack implement a mobile first approach while ensuring responsiveness for larger screens too.</p>
<h3 id="heading-step-4-implementing-service-workers">Step 4 - Implementing Service Workers</h3>
<p>As mentioned previously, service workers are a key component of PWAs. They are JavaScript files that run in the background, enabling offline functionality, push notifications, and caching. To make sure your PWA works to its fullest potential, you’ll need to register and implement a service worker. The way on how you can do this massively depends on which framework you are using.</p>
<h3 id="heading-step-5-adding-push-notifications">Step 5 - Adding Push Notifications</h3>
<p>Leverage the Push API and service workers to implement push notifications. Obtain the necessary user consent and use a push notification service to send notifications to users.</p>
<h3 id="heading-step-6-optimizing-performance">Step 6 - Optimizing Performance</h3>
<p>Optimization is a very important step in development in general. This is how you provide a seamless experience to your users. by ensuring you reduce loading times. by leveraging techniques such as code splitting and caching we should be able to achieve a fast and efficient operation for our PWA.</p>
<h3 id="heading-step-7-testing-and-debugging">Step 7 - Testing and Debugging</h3>
<p>Test your PWA on different devices, browsers and network condition to be sure that it meets the objective. Also be sure to gather user feedback and make necessary improvements when necessary.</p>
<h2 id="heading-resources-to-get-started-with-pwa-development">Resources to Get Started with PWA Development</h2>
<p>If you want to learn and move with the trend, finding resources to help you might be a bit tedious, to help you get started here are some of the best resources listed for you:<br><strong>Online Tutorials and Guides</strong></p>
<ul>
<li><a target="_blank" href="https://developers.google.com/web/progressive-web-apps">Google Developers - Progressive Web Apps</a></li>
<li><a target="_blank" href="https://developer.mozilla.org/en-US/docs/Web/Progressive_web_apps">MDN Web Docs - Progressive Web Apps</a></li>
<li><a target="_blank" href="https://www.freecodecamp.org/learn/front-end-development-libraries/">freeCodeCamp - Frontend Technologies</a></li>
<li><a target="_blank" href="https://web.dev/learn/pwa/">Learn PWA</a></li>
</ul>
<p><strong>Documentation and Reference Materials</strong></p>
<ul>
<li><a target="_blank" href="https://developer.mozilla.org/en-US/docs/Web/API/Service_Worker_API">Service Workers API Documentation</a></li>
<li><a target="_blank" href="https://learn.microsoft.com/microsoft-edge/progressive-web-apps-chromium/?WT.mc_id=%3Fwt.mc_id%3Dstudentamb_223976">Progressive Web Apps (PWAs)</a></li>
</ul>
<p><strong>PWA Development Tools</strong></p>
<ul>
<li><a target="_blank" href="https://developers.google.com/web/tools/workbox">Workbox - Offline Caching and Service Worker Library</a></li>
<li><a target="_blank" href="https://www.pwabuilder.com/">PWA Builder - PWA Generation Platform</a></li>
</ul>
<h2 id="heading-conclusion">Conclusion</h2>
<p>Keeping in mind that PWAs are new to the industry and haven't yet been fully utilized, they can be a great addition to add to your toolkit.</p>
<p>With the latest technologies and the right tools getting started with PWAs can ultimately increase sales and monetary gain for your product either as an individual or organization. With it's many features including they are fast, able to work offline, and also they perform like normal native apps. This offers your users a great experience and keeps them satisfied.</p>
<p>If you have read this far I really appreciate it.</p>
<p>Enjoy Coding ❤.</p>
 ]]>
                </content:encoded>
            </item>
        
            <item>
                <title>
                    <![CDATA[ How to Protect Your PWA – Web App Security Best Practices ]]>
                </title>
                <description>
                    <![CDATA[ Hey, everybody! I'm Rahul. In today's world, web apps have become more prevalent because they work seamlessly on multiple devices. But as with any application, PWAs can be vulnerable to security threats, which can compromise user data and damage the ... ]]>
                </description>
                <link>https://www.freecodecamp.org/news/how-to-protect-your-pwa/</link>
                <guid isPermaLink="false">66c861a051f56da58aee7b78</guid>
                
                    <category>
                        <![CDATA[ progressive web app ]]>
                    </category>
                
                    <category>
                        <![CDATA[ PWA ]]>
                    </category>
                
                    <category>
                        <![CDATA[ Security ]]>
                    </category>
                
                <dc:creator>
                    <![CDATA[ Rahul ]]>
                </dc:creator>
                <pubDate>Mon, 17 Apr 2023 21:28:20 +0000</pubDate>
                <media:content url="https://www.freecodecamp.org/news/content/images/2023/04/pwa.png" medium="image" />
                <content:encoded>
                    <![CDATA[ <p>Hey, everybody! I'm <a target="_blank" href="https://fueler.io/rahoool">Rahul</a>. In today's world, web apps have become more prevalent because they work seamlessly on multiple devices. But as with any application, PWAs can be vulnerable to security threats, which can compromise user data and damage the reputation.</p>
<p>It's natural to get excited about launching something new, but it's also important to remember the basics of website security. As a developer, you know it’s important to protect your web app from malicious users, attackers, hackers, and other cyber threats.</p>
<p>You've put in a lot of hard work building the perfect user experience and now it's time to keep it secure.</p>
<p>In this tutorial, we'll walk through some simple yet powerful strategies that help you protect your PWA. Let’s get started!</p>
<h1 id="heading-what-is-a-pwa">What is a PWA?</h1>
<p><img src="https://www.freecodecamp.org/news/content/images/2023/04/undraw_progressive_app_m-9-ms.svg" alt="PWA Illustration by undraw.co" width="600" height="400" loading="lazy">
<em>PWA Illustration by undraw.co</em></p>
<p>Are you interested in bringing a native mobile app experience to your users without the headache of app development? Progressive Web App is the way.</p>
<p>PWAs are a type of web application that uses the latest web tech to deliver a user experience similar to that of a native mobile app.</p>
<p>They are built to take advantage of native mobile device features and work for apps regardless of whether the user has poor or no internet connection.</p>
<p>Additionally, PWAs are reliable, fast, and engaging — plus, they’re easier to develop and maintain than traditional apps.</p>
<p>The best part about PWAs is that they offer the same experience on all major browsers like Chrome, Firefox, Edge and Safari — so you don’t have to be concerned about compatibility issues.</p>
<p>Also, since PWAs don’t require installation from an app store, users don’t have to wait for their device manufacturer to approve or update them.</p>
<p>All these factors make PWAs ideal for businesses looking for cost-effective mobile solutions.</p>
<h1 id="heading-how-to-secure-a-pwa">How to Secure a PWA</h1>
<h2 id="heading-authentication-and-authorization-in-pwas">Authentication and Authorization in PWAs</h2>
<p><img src="https://www.freecodecamp.org/news/content/images/2023/04/undraw_authentication_re_svpt.svg" alt="Image" width="600" height="400" loading="lazy">
<em>authentication illustration by undraw.co</em></p>
<p>If you're building a Progressive Web App (PWA), you need to ensure that it is properly secured. In order to do this, you'll need to understand the basics of authentication and authorization.</p>
<p>Authentication is the process by which users prove their identity and verify that they have the proper authorization to access the app.</p>
<p>Authorization, on the other hand, ensures that those users have access only to areas and content that they are allowed to see, while enforcing restrictions on what they can do.</p>
<p>In order to implement effective authentication and authorization within your PWA, there are some best practices you should follow.</p>
<ol>
<li>Firstly, use secure passwords that are difficult for hackers or bots to guess or crack, as well as two-factor authentication (2FA) for added security.</li>
<li>Secondly, use roles-based access control (RBAC) for authorization. This establishes user permissions based on roles which can be quickly adjusted when needed.</li>
<li>Lastly, use HTTPS with SSL/TLS encryption for all web traffic from your PWA. This will ensure that all data sent between clients and servers is encrypted, protecting user information from malicious actors.</li>
</ol>
<p>It is important to keep in mind that authentication and authorization can also be vulnerable to attack by malicious users if not implemented correctly. So it is essential to stay aware of common attack vectors like SQL injection threats and cross-site scripting (XSS) attacks.</p>
<p>By implementing proper security measures such as input validation and penetration testing, you can protect your PWA from these threats.</p>
<h2 id="heading-how-to-use-https-and-ssltls-to-secure-your-pwas">How to Use HTTPS and SSL/TLS to Secure Your PWAs</h2>
<p>HTTPs and SSL/TLS are two of the most important tools when it comes to securing PWAs.</p>
<p>Let's see what exactly they are:</p>
<h3 id="heading-what-is-https">What is HTTPS?</h3>
<p>HTTPS stands for Hyper Text Transfer Protocol Secure. It's a protocol used for secure communication between a web server and a web browser, and is vital for protecting data from being intercepted by third parties.</p>
<p>To put it simply, HTTPS helps ensure that the data you send and receive from your PWA is always secure.</p>
<h3 id="heading-what-is-ssltls">What is SSL/TLS?</h3>
<p>SSL (Secure Sockets Layer) and <a target="_blank" href="https://www.freecodecamp.org/news/what-is-tls-transport-layer-security-encryption-explained-in-plain-english/">TLS (Transport Layer Security)</a> are cryptographic protocols that provide authentication, encryption and data integrity.</p>
<p>These protocols allow secure connections between web browsers, applications, and servers over the internet via an encrypted handshake process.</p>
<p>As such, they play an important role in ensuring that your web app's data remains safe and secure.</p>
<h3 id="heading-best-practices-for-implementing-https-and-ssltls-in-pwas">Best Practices for Implementing HTTPS and SSL/TLS in PWAs</h3>
<p>To protect your PWA effectively, you should make sure that you're following some best practices for implementing HTTPS and SSL/TLS in PWAs:</p>
<ol>
<li>Use HTTPS Everywhere: Make sure that all of your pages are served over HTTPS, rather than insecure HTTP connections. This will help ensure your users' data is protected while they're on your website or application.</li>
<li>Install an SSL Certificate: This will enable HTTPs connections on your site and provide authentication between the server hosting your PWA and the users visiting it.</li>
<li>Regularly Test Connections: Ensure that all of your connections are still secure by testing them regularly for any vulnerabilities or risks to security</li>
<li>Implement Certificate Pinning: Certificate pinning is an important security measure that involves binding a host domain to a specific SSL certificate. This prevents <strong>man-in-the-middle attacks</strong> and will help protect your PWA from malicious actors.</li>
<li>Keep Your Security Protocols Up to Date: Regularly update your web server's security protocols to ensure that you're always running the most secure version.</li>
<li>Configure Appropriate Firewall Rules: Configure your firewall rules to allow only secure connections and block any suspicious traffic. This will help protect your PWA from malicious attacks.</li>
</ol>
<p>Once you've implemented these best practices, you can feel pretty confident that your PWA is as secure as possible.</p>
<p>But, <a target="_blank" href="https://www.freecodecamp.org/news/attacks-on-ssl-tls-and-how-to-protect-your-system/">security isn't just about preventing malicious attacks</a>. It's about ensuring that your users' data is always kept safe and secure.</p>
<p>By taking the above steps, you can ensure that your PWA is always compliant with the latest security standards and guidelines. This will help protect your users' data from unauthorized access and potential security threats.</p>
<h2 id="heading-how-to-protect-against-cross-site-scripting-xss-and-cross-site-request-forgery-csrf">How to Protect against Cross-Site Scripting (XSS) and Cross-Site Request Forgery (CSRF)</h2>
<p><a target="_blank" href="https://www.freecodecamp.org/news/how-to-protect-against-dom-xss-attacks/">Cross-site scripting</a> and <a target="_blank" href="https://www.freecodecamp.org/news/what-is-cross-site-request-forgery/">cross-site request forgery</a> are both security threats that can cause serious damage to PWAs.</p>
<p>XSS works by injecting malicious code into the browser via a vulnerable web application.</p>
<p>This code can be used to exfiltrate data from the PWA, like passwords or credit card numbers, or to execute any other malicious code.</p>
<p>CSRF, on the other hand, is a form of attack that manipulates a user's browser into sending forged requests without them knowing.</p>
<p>This can be used to execute actions within an application, such as transferring money or deleting data.</p>
<p>Fortunately, there are steps you can take to protect your PWA against XSS and CSRF attacks:</p>
<ul>
<li><strong>Sanitize all user input</strong>: Sanitizing user input is one of the best ways to protect against XSS and CSRF attacks. Make sure that you always validate and filter out any potentially harmful content before it’s added to your database.</li>
<li><strong>Use CAPTCHA protection</strong>: Adding CAPTCHA protection to web forms will help ensure that any requests from users are coming from real people rather than malicious bots or scripts.</li>
<li><strong>Use secure cookies</strong>: Secure cookies allow you to store data about your users in an encrypted format and limit access only to authorized users. This can help make sure that data isn’t exposed in case of a compromise.</li>
</ul>
<p>By taking these steps, you can help protect your PWA against XSS and CSRF attacks.</p>
<h2 id="heading-how-to-develop-a-content-security-policy-csp">How to Develop a Content Security Policy (CSP)</h2>
<p>Content Security Policy (CSP) is an important web security mechanism designed to prevent malicious attacks.</p>
<p>To put it simply, a CSP is a set of rules that specify what sources are allowed when loading resources such as scripts, images, and stylesheets. By defining the sources of these content types, CSP helps protect your applications from cross-site scripting (XSS) and data injection attacks.</p>
<p>By implementing a CSP on your PWA, you can add an extra layer of protection against malicious attacks.</p>
<p>Some best practices for implementing CSP include:</p>
<ul>
<li><strong>Defining allowed origins</strong>: This makes sure that only authorized sources can access content on your application.</li>
<li><strong>Whitelisting inline styles</strong>: When possible, use the nonce attribute to whitelist specific inline scripts and styles so that the code is only executed if the nonce matches what is specified in the policy.</li>
<li><strong>Disallowing unsafe methods</strong>: Include a directive that blocks dangerous methods such as eval() in order to protect against code injection attacks.</li>
<li><strong>Setting up reporting/logging</strong>: Configure reporting/logging so you can track incoming requests and flag any suspicious activity.</li>
</ul>
<p>By following these best practices and setting up CSP properly, you can effectively protect your PWAs from malicious attacks while ensuring they remain secure and performant.</p>
<h2 id="heading-conclusion">Conclusion</h2>
<p>In short, if you want to keep your web app secure, it’s important to follow best practice security protocols such as robust authentication and encryption, regular security patch updates, and secure APIs. It's also essential to have a secure development process in place to ensure that only the most secure code is being deployed.</p>
<p>Finally, user education is key when it comes to web apps. Providing users with a secure platform is only half the battle, as ultimately it’s up to the user to stay vigilant about their online security.</p>
<p>Read more on PWAs:</p>
<ul>
<li><a target="_blank" href="https://www.freecodecamp.org/news/what-are-progressive-web-apps/">What is a PWA? Progressive Web Apps for Beginners</a></li>
<li><a target="_blank" href="https://www.freecodecamp.org/news/build-a-pwa-from-scratch-with-html-css-and-javascript/">How to build a PWA from scratch with HTML, CSS, and JavaScript</a></li>
</ul>
 ]]>
                </content:encoded>
            </item>
        
            <item>
                <title>
                    <![CDATA[ React Tutorial – How to Build a Text Translation PWA ]]>
                </title>
                <description>
                    <![CDATA[ By Nishant Kumar In this article, I'll show you how to build a text translator application using React. It will support 17 languages, and you can do cross translation too.  Here's what we'll create: This is how our application will look after we're ... ]]>
                </description>
                <link>https://www.freecodecamp.org/news/react-tutorial-build-a-text-translation-pwa/</link>
                <guid isPermaLink="false">66d46076d14641365a050935</guid>
                
                    <category>
                        <![CDATA[ JavaScript ]]>
                    </category>
                
                    <category>
                        <![CDATA[ projects ]]>
                    </category>
                
                    <category>
                        <![CDATA[ PWA ]]>
                    </category>
                
                    <category>
                        <![CDATA[ React ]]>
                    </category>
                
                    <category>
                        <![CDATA[ Web Development ]]>
                    </category>
                
                <dc:creator>
                    <![CDATA[ freeCodeCamp ]]>
                </dc:creator>
                <pubDate>Thu, 16 Sep 2021 14:54:16 +0000</pubDate>
                <media:content url="https://www.freecodecamp.org/news/content/images/2021/09/How-to-Build-a-Weather-Application-using-React--7-.png" medium="image" />
                <content:encoded>
                    <![CDATA[ <p>By Nishant Kumar</p>
<p>In this article, I'll show you how to build a text translator application using React. It will support 17 languages, and you can do cross translation too. </p>
<p>Here's what we'll create:</p>
<p><img src="https://www.freecodecamp.org/news/content/images/2021/09/Screenshot-2021-09-12-123442-1.png" alt="Image" width="600" height="400" loading="lazy"></p>
<p>This is how our application will look after we're done building it. It has two text area inputs – one will contain our Source Text, and other will contain our Result Text. We also have a select field where the user can choose their desired languages. </p>
<p>So, let's get right into it.</p>
<h2 id="heading-how-to-build-the-ui">How to Build the UI</h2>
<p>In order to create the interface effortlessly, we will use a UI Library called Semantic UI.</p>
<p>So, navigate to Semantic UI's Website at <a target="_blank" href="https://react.semantic-ui.com/">https://react.semantic-ui.com/</a>.</p>
<p>Then, select Get Started from the sidebar menu:</p>
<p><img src="https://www.freecodecamp.org/news/content/images/2021/09/Screenshot-2021-09-12-112404.png" alt="Image" width="600" height="400" loading="lazy"></p>
<p>Install it using one of the below commands. You can use yarn add or npm install.</p>
<pre><code>$  yarn add semantic-ui-react semantic-ui-css
## Or
$  npm install semantic-ui-react semantic-ui-css
</code></pre><p>After it finishes installing, we need to import the package into our index.js file like this:</p>
<pre><code class="lang-js"><span class="hljs-keyword">import</span> <span class="hljs-string">'semantic-ui-css/semantic.min.css'</span>
</code></pre>
<p>Now, we can use Semantic UI.</p>
<h3 id="heading-how-to-create-the-apps-components">How to Create the App's Components</h3>
<p>Let's create a component called <strong>Translate</strong>. This will contain all the elements that we need. </p>
<p>First, we need an application title heading. So, inside the Translate component, create a heading like this:</p>
<pre><code><span class="hljs-keyword">import</span> React <span class="hljs-keyword">from</span> <span class="hljs-string">'react'</span>;

<span class="hljs-keyword">export</span> <span class="hljs-keyword">default</span> <span class="hljs-function"><span class="hljs-keyword">function</span> <span class="hljs-title">Translate</span>(<span class="hljs-params"></span>) </span>{
    <span class="hljs-keyword">return</span> (
        <span class="xml"><span class="hljs-tag">&lt;<span class="hljs-name">div</span>&gt;</span>
            <span class="hljs-tag">&lt;<span class="hljs-name">div</span> <span class="hljs-attr">className</span>=<span class="hljs-string">"app-header"</span>&gt;</span>
                <span class="hljs-tag">&lt;<span class="hljs-name">h2</span> <span class="hljs-attr">className</span>=<span class="hljs-string">"header"</span>&gt;</span>Texty Translator<span class="hljs-tag">&lt;/<span class="hljs-name">h2</span>&gt;</span>
            <span class="hljs-tag">&lt;/<span class="hljs-name">div</span>&gt;</span>
        <span class="hljs-tag">&lt;/<span class="hljs-name">div</span>&gt;</span></span>
    )
}
</code></pre><p>Now let's add a little styling to it with CSS:</p>
<pre><code>@<span class="hljs-keyword">import</span> url(<span class="hljs-string">'https://fonts.googleapis.com/css2?family=Azeret+Mono&amp;display=swap'</span>);

.app-header{
  text-align: center;
  padding: <span class="hljs-number">20</span>px;
}

.header{
  font-family: <span class="hljs-string">'Azeret Mono'</span>, monospace;
  font-size: <span class="hljs-number">30</span>px;
}
</code></pre><p>Here, we are using a font called Azeret Mono from Google Fonts, and we've aligned the header and given it some padding.</p>
<p>This is how our header will look at this point:</p>
<p><img src="https://www.freecodecamp.org/news/content/images/2021/09/Screenshot-2021-09-12-113234.png" alt="Image" width="600" height="400" loading="lazy"></p>
<p>We also need four other elements. First is our input text area, second is the select dropdown to select the language, the third is the output text area where our translated text will be reflected, and the last one is a button which will translate our text.</p>
<p>We can import the Form, TextArea, Button, and Icon elements directly from Semantic UI like this:</p>
<pre><code><span class="hljs-keyword">import</span> {
    Form,
    TextArea,
    Button,
    Icon
} <span class="hljs-keyword">from</span> <span class="hljs-string">'semantic-ui-react'</span>;
</code></pre><p>Next, we'll create another div after <code>app-header</code> called <code>app-body</code> with the following code:</p>
<pre><code><span class="hljs-keyword">import</span> React <span class="hljs-keyword">from</span> <span class="hljs-string">'react'</span>;
<span class="hljs-keyword">import</span> {
    Form,
    TextArea,
    Button,
    Icon
} <span class="hljs-keyword">from</span> <span class="hljs-string">'semantic-ui-react'</span>;

<span class="hljs-keyword">export</span> <span class="hljs-keyword">default</span> <span class="hljs-function"><span class="hljs-keyword">function</span> <span class="hljs-title">Translate</span>(<span class="hljs-params"></span>) </span>{
    <span class="hljs-keyword">return</span> (
        <span class="xml"><span class="hljs-tag">&lt;<span class="hljs-name">div</span>&gt;</span>
            <span class="hljs-tag">&lt;<span class="hljs-name">div</span> <span class="hljs-attr">className</span>=<span class="hljs-string">"app-header"</span>&gt;</span>
                <span class="hljs-tag">&lt;<span class="hljs-name">h2</span> <span class="hljs-attr">className</span>=<span class="hljs-string">"header"</span>&gt;</span>Texty Translator<span class="hljs-tag">&lt;/<span class="hljs-name">h2</span>&gt;</span>
            <span class="hljs-tag">&lt;/<span class="hljs-name">div</span>&gt;</span>

            <span class="hljs-tag">&lt;<span class="hljs-name">div</span> <span class="hljs-attr">className</span>=<span class="hljs-string">'app-body'</span>&gt;</span>
                <span class="hljs-tag">&lt;<span class="hljs-name">div</span>&gt;</span>
                    <span class="hljs-tag">&lt;<span class="hljs-name">Form</span>&gt;</span>
                        <span class="hljs-tag">&lt;<span class="hljs-name">Form.Field</span>
                            <span class="hljs-attr">control</span>=<span class="hljs-string">{TextArea}</span>
                            <span class="hljs-attr">placeholder</span>=<span class="hljs-string">'Type Text to Translate..'</span>
                        /&gt;</span>

                        <span class="hljs-tag">&lt;<span class="hljs-name">select</span> <span class="hljs-attr">className</span>=<span class="hljs-string">"language-select"</span>&gt;</span>
                            <span class="hljs-tag">&lt;<span class="hljs-name">option</span>&gt;</span>Please Select Language..<span class="hljs-tag">&lt;/<span class="hljs-name">option</span>&gt;</span>
                        <span class="hljs-tag">&lt;/<span class="hljs-name">select</span>&gt;</span>

                        <span class="hljs-tag">&lt;<span class="hljs-name">Form.Field</span>
                            <span class="hljs-attr">control</span>=<span class="hljs-string">{TextArea}</span>
                            <span class="hljs-attr">placeholder</span>=<span class="hljs-string">'Your Result Translation..'</span>
                        /&gt;</span>

                        <span class="hljs-tag">&lt;<span class="hljs-name">Button</span>
                            <span class="hljs-attr">color</span>=<span class="hljs-string">"orange"</span>
                            <span class="hljs-attr">size</span>=<span class="hljs-string">"large"</span>
                        &gt;</span>
                            <span class="hljs-tag">&lt;<span class="hljs-name">Icon</span> <span class="hljs-attr">name</span>=<span class="hljs-string">'translate'</span> /&gt;</span>
                            Translate<span class="hljs-tag">&lt;/<span class="hljs-name">Button</span>&gt;</span>
                    <span class="hljs-tag">&lt;/<span class="hljs-name">Form</span>&gt;</span>
                <span class="hljs-tag">&lt;/<span class="hljs-name">div</span>&gt;</span>
            <span class="hljs-tag">&lt;/<span class="hljs-name">div</span>&gt;</span>
        <span class="hljs-tag">&lt;/<span class="hljs-name">div</span>&gt;</span></span>
    )
}
</code></pre><p>And we'll add some styling to it with the following CSS:</p>
<pre><code>@<span class="hljs-keyword">import</span> url(<span class="hljs-string">'https://fonts.googleapis.com/css2?family=Azeret+Mono&amp;display=swap'</span>);

.app-header{
  text-align: center;
  padding: <span class="hljs-number">20</span>px;
}

.header{
  font-family: <span class="hljs-string">'Azeret Mono'</span>, monospace;
  font-size: <span class="hljs-number">30</span>px;
}

.app-body{
  <span class="hljs-attr">padding</span>: <span class="hljs-number">20</span>px;
  text-align: center;
}

.language-select{
  <span class="hljs-attr">height</span>: <span class="hljs-number">40</span>px !important;
  margin-bottom: <span class="hljs-number">15</span>px;
  outline: none !important;
}
</code></pre><p>This is how our application will look now. You can see that we have the text areas, select options, and a button to translate.</p>
<p><img src="https://www.freecodecamp.org/news/content/images/2021/09/Screenshot-2021-09-12-114039.png" alt="Image" width="600" height="400" loading="lazy"></p>
<h3 id="heading-how-to-set-up-the-apis">How to Set Up the APIs</h3>
<p>To enable translation, we'll use the <a target="_blank" href="https://libretranslate.de/docs">LibreTranslate</a> API. So, go to their website to choose your API.</p>
<p><img src="https://www.freecodecamp.org/news/content/images/2021/09/Screenshot-2021-09-12-114304.png" alt="Image" width="600" height="400" loading="lazy"></p>
<p>As you can see in the image above, it has four API's. </p>
<p>To start, we need to detect our input language using the /detect POST API. </p>
<h3 id="heading-how-to-install-axios">How to Install Axios</h3>
<p>But first, let's install Axios, as we'll need to use it to make API requests. </p>
<p>To install Axios, simply type the below command:</p>
<pre><code>yarn add axios

##OR

npm i axios
</code></pre><p>We can either use yarn add axios or npm i axios, depending on your package manager you installed.</p>
<p>Now, let's import it in our Translate Component.</p>
<pre><code><span class="hljs-keyword">import</span> axios <span class="hljs-keyword">from</span> <span class="hljs-string">'axios'</span>;
</code></pre><p>We also need the useState and useEffect hooks.</p>
<pre><code><span class="hljs-keyword">import</span> React, { useState, useEffect } <span class="hljs-keyword">from</span> <span class="hljs-string">'react'</span>;
</code></pre><p>Then, create one state called inputText.</p>
<pre><code><span class="hljs-keyword">const</span> [inputText, setInputText] = useState(<span class="hljs-string">''</span>);
</code></pre><p>And in the input text area field, bind it to an onChange event handler.</p>
<pre><code>&lt;Form.Field
 control={TextArea}
 placeholder=<span class="hljs-string">'Type Text to Translate..'</span>
 onChange={<span class="hljs-function">(<span class="hljs-params">e</span>) =&gt;</span> setInputText(e.target.value)}
/&gt;
</code></pre><p>If we enter any text, it will be stored it the inputText state.</p>
<h3 id="heading-how-to-call-the-language-detect-api">How to Call the Language Detect API</h3>
<p>Now, let's call the detect language API to detect our input language.</p>
<p>Create a function called <code>getLanguageSource()</code> like this:</p>
<pre><code><span class="hljs-keyword">const</span> getLanguageSource = <span class="hljs-function">() =&gt;</span> {
        axios.post(<span class="hljs-string">`https://libretranslate.de/detect`</span>, {
            <span class="hljs-attr">q</span>: inputText
        })
        .then(<span class="hljs-function">(<span class="hljs-params">response</span>) =&gt;</span> {
            <span class="hljs-built_in">console</span>.log(response.data[<span class="hljs-number">0</span>].language)
        })
    }
</code></pre><p>Here, we are calling the detect API, and we are passing our input as the body. </p>
<p>We are using axios.post to send the input text as body, and we're using q as the header parameter.</p>
<p>Also, we'll want to call this function on the click of the Translate button, so bind this function to the Translate button like this:</p>
<pre><code>&lt;Button
                            color=<span class="hljs-string">"orange"</span>
                            size=<span class="hljs-string">"large"</span>
                            onClick={getLanguageSource}
                        &gt;
                            <span class="xml"><span class="hljs-tag">&lt;<span class="hljs-name">Icon</span> <span class="hljs-attr">name</span>=<span class="hljs-string">'translate'</span> /&gt;</span></span>
                            Translate&lt;/Button&gt;
</code></pre><p>Type something in the first input box, then press the Translate button. You will see the detected language object key in the console, which we need.</p>
<p><img src="https://www.freecodecamp.org/news/content/images/2021/09/Screenshot-2021-09-12-115444.png" alt="Image" width="600" height="400" loading="lazy"></p>
<p>Now, we need to store this language key in a state. So, create a state called <code>detectLanguageKey</code>.</p>
<p>Then, set the state from the response like this:</p>
<pre><code><span class="hljs-keyword">const</span> getLanguageSource = <span class="hljs-function">() =&gt;</span> {
        axios.post(<span class="hljs-string">`https://libretranslate.de/detect`</span>, {
            <span class="hljs-attr">q</span>: inputText
        })
            .then(<span class="hljs-function">(<span class="hljs-params">response</span>) =&gt;</span> {
                setdetectedLanguageKey(response.data[<span class="hljs-number">0</span>].language)
            })
    }
</code></pre><p>We are setting the zeroth index from the response data, because that's where our data starts.</p>
<p>Here is the whole code up to this point:</p>
<pre><code><span class="hljs-keyword">import</span> React, { useState, useEffect } <span class="hljs-keyword">from</span> <span class="hljs-string">'react'</span>;
<span class="hljs-keyword">import</span> {
    Form,
    TextArea,
    Button,
    Icon
} <span class="hljs-keyword">from</span> <span class="hljs-string">'semantic-ui-react'</span>;
<span class="hljs-keyword">import</span> axios <span class="hljs-keyword">from</span> <span class="hljs-string">'axios'</span>;

<span class="hljs-keyword">export</span> <span class="hljs-keyword">default</span> <span class="hljs-function"><span class="hljs-keyword">function</span> <span class="hljs-title">Translate</span>(<span class="hljs-params"></span>) </span>{
    <span class="hljs-keyword">const</span> [inputText, setInputText] = useState(<span class="hljs-string">''</span>);
    <span class="hljs-keyword">const</span> [detectLanguageKey, setdetectedLanguageKey] = useState(<span class="hljs-string">''</span>)
    <span class="hljs-keyword">const</span> getLanguageSource = <span class="hljs-function">() =&gt;</span> {
        axios.post(<span class="hljs-string">`https://libretranslate.de/detect`</span>, {
            <span class="hljs-attr">q</span>: inputText
        })
            .then(<span class="hljs-function">(<span class="hljs-params">response</span>) =&gt;</span> {
                setdetectedLanguageKey(response.data[<span class="hljs-number">0</span>].language)
            })
    }

    <span class="hljs-keyword">return</span> (
        <span class="xml"><span class="hljs-tag">&lt;<span class="hljs-name">div</span>&gt;</span>
            <span class="hljs-tag">&lt;<span class="hljs-name">div</span> <span class="hljs-attr">className</span>=<span class="hljs-string">"app-header"</span>&gt;</span>
                <span class="hljs-tag">&lt;<span class="hljs-name">h2</span> <span class="hljs-attr">className</span>=<span class="hljs-string">"header"</span>&gt;</span>Texty Translator<span class="hljs-tag">&lt;/<span class="hljs-name">h2</span>&gt;</span>
            <span class="hljs-tag">&lt;/<span class="hljs-name">div</span>&gt;</span>

            <span class="hljs-tag">&lt;<span class="hljs-name">div</span> <span class="hljs-attr">className</span>=<span class="hljs-string">'app-body'</span>&gt;</span>
                <span class="hljs-tag">&lt;<span class="hljs-name">div</span>&gt;</span>
                    <span class="hljs-tag">&lt;<span class="hljs-name">Form</span>&gt;</span>
                        <span class="hljs-tag">&lt;<span class="hljs-name">Form.Field</span>
                            <span class="hljs-attr">control</span>=<span class="hljs-string">{TextArea}</span>
                            <span class="hljs-attr">placeholder</span>=<span class="hljs-string">'Type Text to Translate..'</span>
                            <span class="hljs-attr">onChange</span>=<span class="hljs-string">{(e)</span> =&gt;</span> setInputText(e.target.value)}
                        /&gt;

                        <span class="hljs-tag">&lt;<span class="hljs-name">select</span> <span class="hljs-attr">className</span>=<span class="hljs-string">"language-select"</span>&gt;</span>
                            <span class="hljs-tag">&lt;<span class="hljs-name">option</span>&gt;</span>Please Select Language..<span class="hljs-tag">&lt;/<span class="hljs-name">option</span>&gt;</span>
                        <span class="hljs-tag">&lt;/<span class="hljs-name">select</span>&gt;</span>

                        <span class="hljs-tag">&lt;<span class="hljs-name">Form.Field</span>
                            <span class="hljs-attr">control</span>=<span class="hljs-string">{TextArea}</span>
                            <span class="hljs-attr">placeholder</span>=<span class="hljs-string">'Your Result Translation..'</span>
                        /&gt;</span>

                        <span class="hljs-tag">&lt;<span class="hljs-name">Button</span>
                            <span class="hljs-attr">color</span>=<span class="hljs-string">"orange"</span>
                            <span class="hljs-attr">size</span>=<span class="hljs-string">"large"</span>
                            <span class="hljs-attr">onClick</span>=<span class="hljs-string">{getLanguageSource}</span>
                        &gt;</span>
                            <span class="hljs-tag">&lt;<span class="hljs-name">Icon</span> <span class="hljs-attr">name</span>=<span class="hljs-string">'translate'</span> /&gt;</span>
                            Translate<span class="hljs-tag">&lt;/<span class="hljs-name">Button</span>&gt;</span>
                    <span class="hljs-tag">&lt;/<span class="hljs-name">Form</span>&gt;</span>
                <span class="hljs-tag">&lt;/<span class="hljs-name">div</span>&gt;</span>
            <span class="hljs-tag">&lt;/<span class="hljs-name">div</span>&gt;</span>
        <span class="hljs-tag">&lt;/<span class="hljs-name">div</span>&gt;</span></span>
    )
}
</code></pre><h3 id="heading-how-to-call-the-supported-languages-api-for-the-select-dropdown">How to Call the Supported Languages API for the Select Dropdown</h3>
<p>Now, the second API gets the supported languages. We will use the list in our select dropdown.</p>
<p>Create a useEffect hook to call our supported languages API. useEffect is a function that will run every time our component renders or loads.</p>
<pre><code>useEffect(<span class="hljs-function">() =&gt;</span> {
        axios.get(<span class="hljs-string">`https://libretranslate.de/languages`</span>)
            .then(<span class="hljs-function">(<span class="hljs-params">response</span>) =&gt;</span> {
                <span class="hljs-built_in">console</span>.log(response.data)
            })
    }, [])
</code></pre><p>Here we are calling the API for supported languages using the axios.get method. Then we are consoling the response in the console.</p>
<p>Open up the console to check the languages list. You should see something like this:</p>
<p><img src="https://www.freecodecamp.org/news/content/images/2021/09/Screenshot-2021-09-12-120348.png" alt="Image" width="600" height="400" loading="lazy"></p>
<p>Let's set this data into a state. So, create a state called languagesList. It will be an empty array.</p>
<pre><code><span class="hljs-keyword">const</span> [languagesList, setLanguagesList] = useState([])
</code></pre><pre><code>useEffect(<span class="hljs-function">() =&gt;</span> {
        axios.get(<span class="hljs-string">`https://libretranslate.de/languages`</span>)
            .then(<span class="hljs-function">(<span class="hljs-params">response</span>) =&gt;</span> {
                setLanguagesList(response.data)
            })
    }, [])
</code></pre><p>Then, in the useEffect Hook, we need to set the languages list using <code>setLanguagesList</code>.</p>
<p>We need to show this languages list in the select option. So, let's map the select dropdown using the <code>languagesList</code> state like this:</p>
<pre><code>&lt;select className=<span class="hljs-string">"language-select"</span>&gt;
                            <span class="xml"><span class="hljs-tag">&lt;<span class="hljs-name">option</span>&gt;</span>Please Select Language..<span class="hljs-tag">&lt;/<span class="hljs-name">option</span>&gt;</span></span>
                            {languagesList.map(<span class="hljs-function">(<span class="hljs-params">language</span>) =&gt;</span> {
                                <span class="hljs-keyword">return</span> (
                                    <span class="xml"><span class="hljs-tag">&lt;<span class="hljs-name">option</span> <span class="hljs-attr">value</span>=<span class="hljs-string">{language.code}</span>&gt;</span>
                                        {language.name}
                                    <span class="hljs-tag">&lt;/<span class="hljs-name">option</span>&gt;</span></span>
                                )
                            })}
                        &lt;/select&gt;
</code></pre><p><img src="https://www.freecodecamp.org/news/content/images/2021/09/Screenshot--605-.png" alt="Image" width="600" height="400" loading="lazy"></p>
<p>Now, we can select our language from the select dropdown.</p>
<h3 id="heading-how-to-get-the-selected-language-code">How to Get the Selected Language Code</h3>
<p>Now, if we select a language – let's say Spanish – we need to get the language code, as we need that language code in our final Translate API.</p>
<p>Create a function called <code>languageKey()</code> like this:</p>
<pre><code><span class="hljs-keyword">const</span> languageKey = <span class="hljs-function">() =&gt;</span> {

}
</code></pre><p>And on the select option, bind this function using onChange:</p>
<pre><code>&lt;select className=<span class="hljs-string">"language-select"</span> onChange={languageKey}&gt;
                            <span class="xml"><span class="hljs-tag">&lt;<span class="hljs-name">option</span>&gt;</span>Please Select Language..<span class="hljs-tag">&lt;/<span class="hljs-name">option</span>&gt;</span></span>
                            {languagesList.map(<span class="hljs-function">(<span class="hljs-params">language</span>) =&gt;</span> {
                                <span class="hljs-keyword">return</span> (
                                    <span class="xml"><span class="hljs-tag">&lt;<span class="hljs-name">option</span> <span class="hljs-attr">value</span>=<span class="hljs-string">{language.code}</span>&gt;</span>
                                        {language.name}
                                    <span class="hljs-tag">&lt;/<span class="hljs-name">option</span>&gt;</span></span>
                                )
                            })}
                        &lt;/select&gt;
</code></pre><p>Also, we need to store the language code in a state, so let's create it.</p>
<p>Create a state called <code>selectedLanguageKey</code>, which will contain our selected language key from the select input.</p>
<pre><code><span class="hljs-keyword">const</span> [selectedLanguageKey, setLanguageKey] = useState(<span class="hljs-string">''</span>)
</code></pre><p>This languageKey function will accept a parameter called <code>selectedLanguage</code>. And we will store this data in the <code>selectedLanguageKey</code> state, which we are getting from the select option.</p>
<pre><code><span class="hljs-keyword">const</span> languageKey = <span class="hljs-function">(<span class="hljs-params">selectedLanguage</span>) =&gt;</span> {
        setLanguageKey(selectedLanguage.target.value)
}
</code></pre><p>Now, if you look at the LibreTranslate documentation, we need three data inputs: </p>
<ol>
<li>The text to translate.</li>
<li>The source language code.</li>
<li>The target language code.</li>
</ol>
<p><img src="https://www.freecodecamp.org/news/content/images/2021/09/Screenshot-2021-09-12-120659.png" alt="Image" width="600" height="400" loading="lazy"></p>
<p>We don't need the API key because this service is free.</p>
<p>We have all three inputs that we need to send in the body contained in these states below:</p>
<pre><code><span class="hljs-keyword">const</span> [inputText, setInputText] = useState(<span class="hljs-string">''</span>);
<span class="hljs-keyword">const</span> [detectLanguageKey, setdetectedLanguageKey] = useState(<span class="hljs-string">''</span>);
<span class="hljs-keyword">const</span> [selectedLanguageKey, setLanguageKey] = useState(<span class="hljs-string">''</span>)
</code></pre><p>Now, let's call our final API, which is /translate.</p>
<h3 id="heading-how-to-call-the-translate-api-to-translate-our-text">How to Call the Translate API to Translate our Text</h3>
<p>Create one final state called resultText. This state will contain our output translated text.</p>
<pre><code><span class="hljs-keyword">const</span> [resultText, setResultText] = useState(<span class="hljs-string">''</span>);
</code></pre><p>Create a function that will call the translate API:</p>
<pre><code><span class="hljs-keyword">const</span> translateText = <span class="hljs-function">() =&gt;</span> {
       getLanguageSource();

        <span class="hljs-keyword">let</span> data = {
            <span class="hljs-attr">q</span> : inputText,
            <span class="hljs-attr">source</span>: detectLanguageKey,
            <span class="hljs-attr">target</span>: selectedLanguageKey
        }
        axios.post(<span class="hljs-string">`https://libretranslate.de/translate`</span>, data)
        .then(<span class="hljs-function">(<span class="hljs-params">response</span>) =&gt;</span> {
            setResultText(response.data.translatedText)
        })
    }
</code></pre><p>As you can see, we are setting inputText in the resultText state, and we are calling the getLanguageSource function inside translateText function. So, whenever this function runs, getLanguageSource will trigger them automatically to get the language source. </p>
<p>In other words, onClick of this function via the Translate button, it will set the language source via getLanguageSource(), then it will call the translate API.</p>
<p>So, on the Translate Button, bind this function:</p>
<pre><code>&lt;Button
                            color=<span class="hljs-string">"orange"</span>
                            size=<span class="hljs-string">"large"</span>
                            onClick={translateText}
                        &gt;
                            <span class="xml"><span class="hljs-tag">&lt;<span class="hljs-name">Icon</span> <span class="hljs-attr">name</span>=<span class="hljs-string">'translate'</span> /&gt;</span></span>
                            Translate&lt;/Button&gt;
</code></pre><p>Next let's create an object called data. Inside it we will send all the data we got previously, like inputText, detectLanguageKey, and the selectedLanguage key as q, source, and the target respectively.</p>
<pre><code><span class="hljs-keyword">let</span> data = {
            <span class="hljs-attr">q</span> : inputText,
            <span class="hljs-attr">source</span>: detectLanguageKey,
            <span class="hljs-attr">target</span>: selectedLanguageKey
        }
</code></pre><p>Then, we call the translate API using axios.post and send the data object as a body parameter.</p>
<pre><code><span class="hljs-keyword">let</span> data = {
            <span class="hljs-attr">q</span> : inputText,
            <span class="hljs-attr">source</span>: detectLanguageKey,
            <span class="hljs-attr">target</span>: selectedLanguageKey
        }
        axios.post(<span class="hljs-string">`https://libretranslate.de/translate`</span>, data)
</code></pre><p>Lastly, we set the incoming response data into the resultText state.</p>
<pre><code>.then(<span class="hljs-function">(<span class="hljs-params">response</span>) =&gt;</span> {
            setResultText(response.data.translatedText)
        })
</code></pre><p>So, now type something in the input box, select the language, and click Translate. You will get your translated text.</p>
<p>Here is the whole code up to this point, for your reference:</p>
<pre><code><span class="hljs-keyword">import</span> React, { useState, useEffect } <span class="hljs-keyword">from</span> <span class="hljs-string">'react'</span>;
<span class="hljs-keyword">import</span> {
    Form,
    TextArea,
    Button,
    Icon
} <span class="hljs-keyword">from</span> <span class="hljs-string">'semantic-ui-react'</span>;
<span class="hljs-keyword">import</span> axios <span class="hljs-keyword">from</span> <span class="hljs-string">'axios'</span>;

<span class="hljs-keyword">export</span> <span class="hljs-keyword">default</span> <span class="hljs-function"><span class="hljs-keyword">function</span> <span class="hljs-title">Translate</span>(<span class="hljs-params"></span>) </span>{
    <span class="hljs-keyword">const</span> [inputText, setInputText] = useState(<span class="hljs-string">''</span>);
    <span class="hljs-keyword">const</span> [detectLanguageKey, setdetectedLanguageKey] = useState(<span class="hljs-string">''</span>);
    <span class="hljs-keyword">const</span> [selectedLanguageKey, setLanguageKey] = useState(<span class="hljs-string">''</span>)
    <span class="hljs-keyword">const</span> [languagesList, setLanguagesList] = useState([])
    <span class="hljs-keyword">const</span> [resultText, setResultText] = useState(<span class="hljs-string">''</span>);
    <span class="hljs-keyword">const</span> getLanguageSource = <span class="hljs-function">() =&gt;</span> {
        axios.post(<span class="hljs-string">`https://libretranslate.de/detect`</span>, {
            <span class="hljs-attr">q</span>: inputText
        })
            .then(<span class="hljs-function">(<span class="hljs-params">response</span>) =&gt;</span> {
                setdetectedLanguageKey(response.data[<span class="hljs-number">0</span>].language)
            })
    }
    useEffect(<span class="hljs-function">() =&gt;</span> {
        axios.get(<span class="hljs-string">`https://libretranslate.de/languages`</span>)
            .then(<span class="hljs-function">(<span class="hljs-params">response</span>) =&gt;</span> {
                setLanguagesList(response.data)
            })
    }, [])

    <span class="hljs-keyword">const</span> languageKey = <span class="hljs-function">(<span class="hljs-params">selectedLanguage</span>) =&gt;</span> {
        setLanguageKey(selectedLanguage.target.value)
    }

    <span class="hljs-keyword">const</span> translateText = <span class="hljs-function">() =&gt;</span> {
        getLanguageSource();

        <span class="hljs-keyword">let</span> data = {
            <span class="hljs-attr">q</span> : inputText,
            <span class="hljs-attr">source</span>: detectLanguageKey,
            <span class="hljs-attr">target</span>: selectedLanguageKey
        }
        axios.post(<span class="hljs-string">`https://libretranslate.de/translate`</span>, data)
        .then(<span class="hljs-function">(<span class="hljs-params">response</span>) =&gt;</span> {
            setResultText(response.data.translatedText)
        })
    }

    <span class="hljs-keyword">return</span> (
        <span class="xml"><span class="hljs-tag">&lt;<span class="hljs-name">div</span>&gt;</span>
            <span class="hljs-tag">&lt;<span class="hljs-name">div</span> <span class="hljs-attr">className</span>=<span class="hljs-string">"app-header"</span>&gt;</span>
                <span class="hljs-tag">&lt;<span class="hljs-name">h2</span> <span class="hljs-attr">className</span>=<span class="hljs-string">"header"</span>&gt;</span>Texty Translator<span class="hljs-tag">&lt;/<span class="hljs-name">h2</span>&gt;</span>
            <span class="hljs-tag">&lt;/<span class="hljs-name">div</span>&gt;</span>

            <span class="hljs-tag">&lt;<span class="hljs-name">div</span> <span class="hljs-attr">className</span>=<span class="hljs-string">'app-body'</span>&gt;</span>
                <span class="hljs-tag">&lt;<span class="hljs-name">div</span>&gt;</span>
                    <span class="hljs-tag">&lt;<span class="hljs-name">Form</span>&gt;</span>
                        <span class="hljs-tag">&lt;<span class="hljs-name">Form.Field</span>
                            <span class="hljs-attr">control</span>=<span class="hljs-string">{TextArea}</span>
                            <span class="hljs-attr">placeholder</span>=<span class="hljs-string">'Type Text to Translate..'</span>
                            <span class="hljs-attr">onChange</span>=<span class="hljs-string">{(e)</span> =&gt;</span> setInputText(e.target.value)}
                        /&gt;

                        <span class="hljs-tag">&lt;<span class="hljs-name">select</span> <span class="hljs-attr">className</span>=<span class="hljs-string">"language-select"</span> <span class="hljs-attr">onChange</span>=<span class="hljs-string">{languageKey}</span>&gt;</span>
                            <span class="hljs-tag">&lt;<span class="hljs-name">option</span>&gt;</span>Please Select Language..<span class="hljs-tag">&lt;/<span class="hljs-name">option</span>&gt;</span>
                            {languagesList.map((language) =&gt; {
                                return (
                                    <span class="hljs-tag">&lt;<span class="hljs-name">option</span> <span class="hljs-attr">value</span>=<span class="hljs-string">{language.code}</span>&gt;</span>
                                        {language.name}
                                    <span class="hljs-tag">&lt;/<span class="hljs-name">option</span>&gt;</span>
                                )
                            })}
                        <span class="hljs-tag">&lt;/<span class="hljs-name">select</span>&gt;</span>

                        <span class="hljs-tag">&lt;<span class="hljs-name">Form.Field</span>
                            <span class="hljs-attr">control</span>=<span class="hljs-string">{TextArea}</span>
                            <span class="hljs-attr">placeholder</span>=<span class="hljs-string">'Your Result Translation..'</span>
                            <span class="hljs-attr">value</span>=<span class="hljs-string">{resultText}</span>
                        /&gt;</span>

                        <span class="hljs-tag">&lt;<span class="hljs-name">Button</span>
                            <span class="hljs-attr">color</span>=<span class="hljs-string">"orange"</span>
                            <span class="hljs-attr">size</span>=<span class="hljs-string">"large"</span>
                            <span class="hljs-attr">onClick</span>=<span class="hljs-string">{translateText}</span>
                        &gt;</span>
                            <span class="hljs-tag">&lt;<span class="hljs-name">Icon</span> <span class="hljs-attr">name</span>=<span class="hljs-string">'translate'</span> /&gt;</span>
                            Translate<span class="hljs-tag">&lt;/<span class="hljs-name">Button</span>&gt;</span>
                    <span class="hljs-tag">&lt;/<span class="hljs-name">Form</span>&gt;</span>
                <span class="hljs-tag">&lt;/<span class="hljs-name">div</span>&gt;</span>
            <span class="hljs-tag">&lt;/<span class="hljs-name">div</span>&gt;</span>
        <span class="hljs-tag">&lt;/<span class="hljs-name">div</span>&gt;</span></span>
    )
}
</code></pre><p>Now, the last step. In the useEffect Hook, call the <code>getLanguageSource()</code> function, and set inputText in the dependency array. This means that whenever our inputText text changes, or this state gets updated, the useEffect function will run, and it will call <code>getLanguageSource()</code> every time it runs.</p>
<pre><code>useEffect(<span class="hljs-function">() =&gt;</span> {
       axios.get(<span class="hljs-string">`https://libretranslate.de/languages`</span>)
       .then(<span class="hljs-function">(<span class="hljs-params">response</span>) =&gt;</span> {
        setLanguagesList(response.data)
       })

       getLanguageSource()
    }, [inputText])
</code></pre><p>Now, let's check our output:</p>
<p><img src="https://www.freecodecamp.org/news/content/images/2021/09/Screenshot-2021-09-12-123442.png" alt="Image" width="600" height="400" loading="lazy"></p>
<p>Type some text as input, and select the language. Press Translate, and you will see your translated data in the output.</p>
<h2 id="heading-conclusion">Conclusion</h2>
<p>Now you know how to build a Text Translator using React. You can create your own UI if you want. </p>
<p>So go ahead, build and experiment with it a bit. There are tons of things you can do.</p>
<p>You can check out my video on <a target="_blank" href="https://www.youtube.com/watch?v=R_I5t8r5qsA&amp;t=5s&amp;ab_channel=Cybernatico">Let's Build a Text Translator Application using React</a>, which is on my YouTube channel.</p>
<p>Feel free to download the code here: <a target="_blank" href="https://github.com/nishant-666/Sanjeet-s-Translator">https://github.com/nishant-666/Sanjeet-s-Translator</a>. Just please don't mind the name.</p>
<blockquote>
<p>Happy Learning.</p>
</blockquote>
 ]]>
                </content:encoded>
            </item>
        
            <item>
                <title>
                    <![CDATA[ How to build a PWA from scratch with HTML, CSS, and JavaScript ]]>
                </title>
                <description>
                    <![CDATA[ By Ibrahima Ndaw Progressive web apps are a way to bring that native app feeling to a traditional web app. With PWAs we can enhance our website with mobile app features which increase usability and offer a great user experience. In this article, we a... ]]>
                </description>
                <link>https://www.freecodecamp.org/news/build-a-pwa-from-scratch-with-html-css-and-javascript/</link>
                <guid isPermaLink="false">66d8501ec8d279d4f28c47b7</guid>
                
                    <category>
                        <![CDATA[ CSS ]]>
                    </category>
                
                    <category>
                        <![CDATA[ HTML ]]>
                    </category>
                
                    <category>
                        <![CDATA[ JavaScript ]]>
                    </category>
                
                    <category>
                        <![CDATA[ PWA ]]>
                    </category>
                
                <dc:creator>
                    <![CDATA[ freeCodeCamp ]]>
                </dc:creator>
                <pubDate>Mon, 03 Feb 2020 23:27:42 +0000</pubDate>
                <media:content url="https://www.freecodecamp.org/news/content/images/2020/01/Group-1.png" medium="image" />
                <content:encoded>
                    <![CDATA[ <p>By Ibrahima Ndaw</p>
<p>Progressive web apps are a way to bring that native app feeling to a traditional web app. With PWAs we can enhance our website with mobile app features which increase usability and offer a great user experience.</p>
<p>In this article, we are going to build a PWA from scratch with HTML, CSS, and JavaScript. Here are the topics we'll cover:</p>
<ul>
<li><a class="post-section-overview" href="#heading-what-is-a-progressive-web-app">What is a Progressive Web App ?</a></li>
<li><a class="post-section-overview" href="#heading-markup">Markup</a></li>
<li><a class="post-section-overview" href="#heading-styling">Styling</a></li>
<li><a class="post-section-overview" href="#heading-show-data-with-javascript">Show data with JavaScript</a></li>
<li><a class="post-section-overview" href="#heading-web-app-manifest">Web App Manifest</a></li>
<li><a class="post-section-overview" href="#heading-what-is-a-service-worker">What is a Service Worker?</a></li>
<li><a class="post-section-overview" href="#heading-cache-the-assets">Cache the assets</a></li>
<li><a class="post-section-overview" href="#heading-fetch-the-assets">Fetch the assets</a></li>
<li><a class="post-section-overview" href="#heading-register-the-service-worker">Register the Service Worker</a></li>
<li><a class="post-section-overview" href="#heading-final-thoughts">Final thoughts</a></li>
<li><a class="post-section-overview" href="#heading-next-steps">Next steps</a></li>
</ul>
<p>So, let's get started with an important question: What the heck is a PWA?</p>
<h2 id="heading-what-is-a-progressive-web-app">What is a Progressive Web App ?</h2>
<p>A Progressive Web App is a web app that delivers an app-like experience to users by using modern web capabilities. In the end, it's just your regular website that runs in a browser with some enhancements. It gives you the ability:</p>
<ul>
<li>To install it on a mobile home screen</li>
<li>To access it when offline</li>
<li>To access the camera</li>
<li>To get push notifications</li>
<li>To do background synchronization</li>
</ul>
<p>And so much more.</p>
<p>However, to be able to transform our traditional web app to a PWA, we have to adjust it a little bit by adding a web app manifest file and a service worker.</p>
<p>Don't worry about these new terms – we'll cover them below.</p>
<p>First, we have to build our traditional web app. So let's start with the markup.</p>
<h2 id="heading-markup">Markup</h2>
<p>The HTML file is relatively simple. We wrap everything in the <code>main</code> tag.</p>
<ul>
<li>In <code>index.html</code></li>
</ul>
<pre><code class="lang-html"><span class="hljs-meta">&lt;!DOCTYPE <span class="hljs-meta-keyword">html</span>&gt;</span>
<span class="hljs-tag">&lt;<span class="hljs-name">html</span> <span class="hljs-attr">lang</span>=<span class="hljs-string">"en"</span>&gt;</span>
  <span class="hljs-tag">&lt;<span class="hljs-name">head</span>&gt;</span>
    <span class="hljs-tag">&lt;<span class="hljs-name">meta</span> <span class="hljs-attr">charset</span>=<span class="hljs-string">"UTF-8"</span> /&gt;</span>
    <span class="hljs-tag">&lt;<span class="hljs-name">meta</span> <span class="hljs-attr">name</span>=<span class="hljs-string">"viewport"</span> <span class="hljs-attr">content</span>=<span class="hljs-string">"width=device-width, initial-scale=1.0"</span> /&gt;</span>
    <span class="hljs-tag">&lt;<span class="hljs-name">meta</span> <span class="hljs-attr">http-equiv</span>=<span class="hljs-string">"X-UA-Compatible"</span> <span class="hljs-attr">content</span>=<span class="hljs-string">"ie=edge"</span> /&gt;</span>
    <span class="hljs-tag">&lt;<span class="hljs-name">link</span> <span class="hljs-attr">rel</span>=<span class="hljs-string">"stylesheet"</span> <span class="hljs-attr">href</span>=<span class="hljs-string">"css/style.css"</span> /&gt;</span>
    <span class="hljs-tag">&lt;<span class="hljs-name">title</span>&gt;</span>Dev'Coffee PWA<span class="hljs-tag">&lt;/<span class="hljs-name">title</span>&gt;</span>
  <span class="hljs-tag">&lt;/<span class="hljs-name">head</span>&gt;</span>
  <span class="hljs-tag">&lt;<span class="hljs-name">body</span>&gt;</span>
    <span class="hljs-tag">&lt;<span class="hljs-name">main</span>&gt;</span>
      <span class="hljs-tag">&lt;<span class="hljs-name">nav</span>&gt;</span>
        <span class="hljs-tag">&lt;<span class="hljs-name">h1</span>&gt;</span>Dev'Coffee<span class="hljs-tag">&lt;/<span class="hljs-name">h1</span>&gt;</span>
        <span class="hljs-tag">&lt;<span class="hljs-name">ul</span>&gt;</span>
          <span class="hljs-tag">&lt;<span class="hljs-name">li</span>&gt;</span>Home<span class="hljs-tag">&lt;/<span class="hljs-name">li</span>&gt;</span>
          <span class="hljs-tag">&lt;<span class="hljs-name">li</span>&gt;</span>About<span class="hljs-tag">&lt;/<span class="hljs-name">li</span>&gt;</span>
          <span class="hljs-tag">&lt;<span class="hljs-name">li</span>&gt;</span>Blog<span class="hljs-tag">&lt;/<span class="hljs-name">li</span>&gt;</span>
        <span class="hljs-tag">&lt;/<span class="hljs-name">ul</span>&gt;</span>
      <span class="hljs-tag">&lt;/<span class="hljs-name">nav</span>&gt;</span>
      <span class="hljs-tag">&lt;<span class="hljs-name">div</span> <span class="hljs-attr">class</span>=<span class="hljs-string">"container"</span>&gt;</span><span class="hljs-tag">&lt;/<span class="hljs-name">div</span>&gt;</span>
    <span class="hljs-tag">&lt;/<span class="hljs-name">main</span>&gt;</span>
    <span class="hljs-tag">&lt;<span class="hljs-name">script</span> <span class="hljs-attr">src</span>=<span class="hljs-string">"js/app.js"</span>&gt;</span><span class="hljs-tag">&lt;/<span class="hljs-name">script</span>&gt;</span>
  <span class="hljs-tag">&lt;/<span class="hljs-name">body</span>&gt;</span>
<span class="hljs-tag">&lt;/<span class="hljs-name">html</span>&gt;</span>
</code></pre>
<p>And create a navigation bar with the <code>nav</code> tag. Then, the <code>div</code> with the class <code>.container</code> will hold our cards that we add later with JavaScript.</p>
<p>Now that we've gotten that out of the way, let's style it with CSS.</p>
<h2 id="heading-styling">Styling</h2>
<p>Here, as usual, we start by importing the fonts we need. Then we'll do some resets to prevent the default behavior.</p>
<ul>
<li>In <code>css/style.css</code></li>
</ul>
<pre><code class="lang-css"><span class="hljs-keyword">@import</span> url(<span class="hljs-string">"https://fonts.googleapis.com/css?family=Nunito:400,700&amp;display=swap"</span>);
* {
  <span class="hljs-attribute">margin</span>: <span class="hljs-number">0</span>;
  <span class="hljs-attribute">padding</span>: <span class="hljs-number">0</span>;
  <span class="hljs-attribute">box-sizing</span>: border-box;
}
<span class="hljs-selector-tag">body</span> {
  <span class="hljs-attribute">background</span>: <span class="hljs-number">#fdfdfd</span>;
  <span class="hljs-attribute">font-family</span>: <span class="hljs-string">"Nunito"</span>, sans-serif;
  <span class="hljs-attribute">font-size</span>: <span class="hljs-number">1rem</span>;
}
<span class="hljs-selector-tag">main</span> {
  <span class="hljs-attribute">max-width</span>: <span class="hljs-number">900px</span>;
  <span class="hljs-attribute">margin</span>: auto;
  <span class="hljs-attribute">padding</span>: <span class="hljs-number">0.5rem</span>;
  <span class="hljs-attribute">text-align</span>: center;
}
<span class="hljs-selector-tag">nav</span> {
  <span class="hljs-attribute">display</span>: flex;
  <span class="hljs-attribute">justify-content</span>: space-between;
  <span class="hljs-attribute">align-items</span>: center;
}
<span class="hljs-selector-tag">ul</span> {
  <span class="hljs-attribute">list-style</span>: none;
  <span class="hljs-attribute">display</span>: flex;
}

<span class="hljs-selector-tag">li</span> {
  <span class="hljs-attribute">margin-right</span>: <span class="hljs-number">1rem</span>;
}
<span class="hljs-selector-tag">h1</span> {
  <span class="hljs-attribute">color</span>: <span class="hljs-number">#e74c3c</span>;
  <span class="hljs-attribute">margin-bottom</span>: <span class="hljs-number">0.5rem</span>;
}
</code></pre>
<p>Then, we limit the <code>main</code> element's maximum width to <code>900px</code> to make it look good on a large screen.</p>
<p>For the navbar, I want the logo to be at the left and the links at the right. So for the <code>nav</code> tag, after making it a flex container, we use <code>justify-content: space-between;</code> to align them.</p>
<ul>
<li>In <code>css/style.css</code></li>
</ul>
<pre><code class="lang-css"><span class="hljs-selector-class">.container</span> {
  <span class="hljs-attribute">display</span>: grid;
  <span class="hljs-attribute">grid-template-columns</span>: <span class="hljs-built_in">repeat</span>(auto-fit, minmax(<span class="hljs-number">15rem</span>, <span class="hljs-number">1</span>fr));
  <span class="hljs-attribute">grid-gap</span>: <span class="hljs-number">1rem</span>;
  <span class="hljs-attribute">justify-content</span>: center;
  <span class="hljs-attribute">align-items</span>: center;
  <span class="hljs-attribute">margin</span>: auto;
  <span class="hljs-attribute">padding</span>: <span class="hljs-number">1rem</span> <span class="hljs-number">0</span>;
}
<span class="hljs-selector-class">.card</span> {
  <span class="hljs-attribute">display</span>: flex;
  <span class="hljs-attribute">align-items</span>: center;
  <span class="hljs-attribute">flex-direction</span>: column;
  <span class="hljs-attribute">width</span>: <span class="hljs-number">15rem</span> auto;
  <span class="hljs-attribute">height</span>: <span class="hljs-number">15rem</span>;
  <span class="hljs-attribute">background</span>: <span class="hljs-number">#fff</span>;
  <span class="hljs-attribute">box-shadow</span>: <span class="hljs-number">0</span> <span class="hljs-number">10px</span> <span class="hljs-number">20px</span> <span class="hljs-built_in">rgba</span>(<span class="hljs-number">0</span>, <span class="hljs-number">0</span>, <span class="hljs-number">0</span>, <span class="hljs-number">0.19</span>), <span class="hljs-number">0</span> <span class="hljs-number">6px</span> <span class="hljs-number">6px</span> <span class="hljs-built_in">rgba</span>(<span class="hljs-number">0</span>, <span class="hljs-number">0</span>, <span class="hljs-number">0</span>, <span class="hljs-number">0.23</span>);
  <span class="hljs-attribute">border-radius</span>: <span class="hljs-number">10px</span>;
  <span class="hljs-attribute">margin</span>: auto;
  <span class="hljs-attribute">overflow</span>: hidden;
}
<span class="hljs-selector-class">.card--avatar</span> {
  <span class="hljs-attribute">width</span>: <span class="hljs-number">100%</span>;
  <span class="hljs-attribute">height</span>: <span class="hljs-number">10rem</span>;
  <span class="hljs-attribute">object-fit</span>: cover;
}
<span class="hljs-selector-class">.card--title</span> {
  <span class="hljs-attribute">color</span>: <span class="hljs-number">#222</span>;
  <span class="hljs-attribute">font-weight</span>: <span class="hljs-number">700</span>;
  <span class="hljs-attribute">text-transform</span>: capitalize;
  <span class="hljs-attribute">font-size</span>: <span class="hljs-number">1.1rem</span>;
  <span class="hljs-attribute">margin-top</span>: <span class="hljs-number">0.5rem</span>;
}
<span class="hljs-selector-class">.card--link</span> {
  <span class="hljs-attribute">text-decoration</span>: none;
  <span class="hljs-attribute">background</span>: <span class="hljs-number">#db4938</span>;
  <span class="hljs-attribute">color</span>: <span class="hljs-number">#fff</span>;
  <span class="hljs-attribute">padding</span>: <span class="hljs-number">0.3rem</span> <span class="hljs-number">1rem</span>;
  <span class="hljs-attribute">border-radius</span>: <span class="hljs-number">20px</span>;
}
</code></pre>
<p>We'll have several cards, so for the container element it will be displayed as a grid. And, with <code>grid-template-columns: repeat(auto-fit, minmax(15rem, 1fr))</code>, we can now make our cards responsive so that they use at least <code>15rem</code> width if there is enough space (and <code>1fr</code> if not).</p>
<p>And to make them look nice we double the shadow effect on the <code>.card</code> class and use <code>object-fit: cover</code> on <code>.card--avatar</code> to prevent the image from stretching.</p>
<p>Now it looks much better – but we still don't have data to show.</p>
<p>Let's fix it in the next section</p>
<h2 id="heading-show-data-with-javascript">Show data with JavaScript</h2>
<p>Notice that I used large images that take some time to load. This will show you in the best way the power of service workers.</p>
<p>As I said earlier, the <code>.container</code> class will hold our cards. Therefore, we need to select it.</p>
<ul>
<li>In <code>js/app.js</code></li>
</ul>
<pre><code class="lang-javascript"><span class="hljs-keyword">const</span> container = <span class="hljs-built_in">document</span>.querySelector(<span class="hljs-string">".container"</span>)
<span class="hljs-keyword">const</span> coffees = [
  { <span class="hljs-attr">name</span>: <span class="hljs-string">"Perspiciatis"</span>, <span class="hljs-attr">image</span>: <span class="hljs-string">"images/coffee1.jpg"</span> },
  { <span class="hljs-attr">name</span>: <span class="hljs-string">"Voluptatem"</span>, <span class="hljs-attr">image</span>: <span class="hljs-string">"images/coffee2.jpg"</span> },
  { <span class="hljs-attr">name</span>: <span class="hljs-string">"Explicabo"</span>, <span class="hljs-attr">image</span>: <span class="hljs-string">"images/coffee3.jpg"</span> },
  { <span class="hljs-attr">name</span>: <span class="hljs-string">"Rchitecto"</span>, <span class="hljs-attr">image</span>: <span class="hljs-string">"images/coffee4.jpg"</span> },
  { <span class="hljs-attr">name</span>: <span class="hljs-string">" Beatae"</span>, <span class="hljs-attr">image</span>: <span class="hljs-string">"images/coffee5.jpg"</span> },
  { <span class="hljs-attr">name</span>: <span class="hljs-string">" Vitae"</span>, <span class="hljs-attr">image</span>: <span class="hljs-string">"images/coffee6.jpg"</span> },
  { <span class="hljs-attr">name</span>: <span class="hljs-string">"Inventore"</span>, <span class="hljs-attr">image</span>: <span class="hljs-string">"images/coffee7.jpg"</span> },
  { <span class="hljs-attr">name</span>: <span class="hljs-string">"Veritatis"</span>, <span class="hljs-attr">image</span>: <span class="hljs-string">"images/coffee8.jpg"</span> },
  { <span class="hljs-attr">name</span>: <span class="hljs-string">"Accusantium"</span>, <span class="hljs-attr">image</span>: <span class="hljs-string">"images/coffee9.jpg"</span> },
]
</code></pre>
<p>Then, we create an array of cards with names and images.</p>
<ul>
<li>In <code>js/app.js</code></li>
</ul>
<pre><code class="lang-javascript"><span class="hljs-keyword">const</span> showCoffees = <span class="hljs-function">() =&gt;</span> {
  <span class="hljs-keyword">let</span> output = <span class="hljs-string">""</span>
  coffees.forEach(
    <span class="hljs-function">(<span class="hljs-params">{ name, image }</span>) =&gt;</span>
      (output += <span class="hljs-string">`
              &lt;div class="card"&gt;
                &lt;img class="card--avatar" src=<span class="hljs-subst">${image}</span> /&gt;
                &lt;h1 class="card--title"&gt;<span class="hljs-subst">${name}</span>&lt;/h1&gt;
                &lt;a class="card--link" href="#"&gt;Taste&lt;/a&gt;
              &lt;/div&gt;
              `</span>)
  )
  container.innerHTML = output
}

<span class="hljs-built_in">document</span>.addEventListener(<span class="hljs-string">"DOMContentLoaded"</span>, showCoffees)
</code></pre>
<p>With this code above, we can now loop through the array and show them on the HTML file. And to make everything work, we wait until the DOM (Document Object Model) content finishes loading to run the <code>showCoffees</code> method.</p>
<p>We've done a lot, but for now, we just have a traditional web app. So, let's change that in the next section by introducing some PWA features.</p>
<p><img src="https://media.giphy.com/media/l3V0dy1zzyjbYTQQM/source.gif" alt="super-excited" width="500" height="281" loading="lazy"></p>
<h2 id="heading-web-app-manifest">Web App Manifest</h2>
<p>The web app manifest is a simple JSON file that informs the browser about your web app. It tells how it should behave when installed on the user's mobile device or desktop. And to show the Add to Home Screen prompt, the web app manifest is required.</p>
<p>Now that we know what a web manifest is, let's create a new file named <code>manifest.json</code> (you have to name it like that) in the root directory. Then add this code block below.</p>
<ul>
<li>In <code>manifest.json</code></li>
</ul>
<pre><code class="lang-javascript">{
  <span class="hljs-string">"name"</span>: <span class="hljs-string">"Dev'Coffee"</span>,
  <span class="hljs-string">"short_name"</span>: <span class="hljs-string">"DevCoffee"</span>,
  <span class="hljs-string">"start_url"</span>: <span class="hljs-string">"index.html"</span>,
  <span class="hljs-string">"display"</span>: <span class="hljs-string">"standalone"</span>,
  <span class="hljs-string">"background_color"</span>: <span class="hljs-string">"#fdfdfd"</span>,
  <span class="hljs-string">"theme_color"</span>: <span class="hljs-string">"#db4938"</span>,
  <span class="hljs-string">"orientation"</span>: <span class="hljs-string">"portrait-primary"</span>,
  <span class="hljs-string">"icons"</span>: [
    {
      <span class="hljs-string">"src"</span>: <span class="hljs-string">"/images/icons/icon-72x72.png"</span>,
      <span class="hljs-string">"type"</span>: <span class="hljs-string">"image/png"</span>, <span class="hljs-string">"sizes"</span>: <span class="hljs-string">"72x72"</span>
    },
    {
      <span class="hljs-string">"src"</span>: <span class="hljs-string">"/images/icons/icon-96x96.png"</span>,
      <span class="hljs-string">"type"</span>: <span class="hljs-string">"image/png"</span>, <span class="hljs-string">"sizes"</span>: <span class="hljs-string">"96x96"</span>
    },
    {
      <span class="hljs-string">"src"</span>: <span class="hljs-string">"/images/icons/icon-128x128.png"</span>,
      <span class="hljs-string">"type"</span>: <span class="hljs-string">"image/png"</span>,<span class="hljs-string">"sizes"</span>: <span class="hljs-string">"128x128"</span>
    },
    {
      <span class="hljs-string">"src"</span>: <span class="hljs-string">"/images/icons/icon-144x144.png"</span>,
      <span class="hljs-string">"type"</span>: <span class="hljs-string">"image/png"</span>, <span class="hljs-string">"sizes"</span>: <span class="hljs-string">"144x144"</span>
    },
    {
      <span class="hljs-string">"src"</span>: <span class="hljs-string">"/images/icons/icon-152x152.png"</span>,
      <span class="hljs-string">"type"</span>: <span class="hljs-string">"image/png"</span>, <span class="hljs-string">"sizes"</span>: <span class="hljs-string">"152x152"</span>
    },
    {
      <span class="hljs-string">"src"</span>: <span class="hljs-string">"/images/icons/icon-192x192.png"</span>,
      <span class="hljs-string">"type"</span>: <span class="hljs-string">"image/png"</span>, <span class="hljs-string">"sizes"</span>: <span class="hljs-string">"192x192"</span>
    },
    {
      <span class="hljs-string">"src"</span>: <span class="hljs-string">"/images/icons/icon-384x384.png"</span>,
      <span class="hljs-string">"type"</span>: <span class="hljs-string">"image/png"</span>, <span class="hljs-string">"sizes"</span>: <span class="hljs-string">"384x384"</span>
    },
    {
      <span class="hljs-string">"src"</span>: <span class="hljs-string">"/images/icons/icon-512x512.png"</span>,
      <span class="hljs-string">"type"</span>: <span class="hljs-string">"image/png"</span>, <span class="hljs-string">"sizes"</span>: <span class="hljs-string">"512x512"</span>
    }
  ]
}
</code></pre>
<p>In the end, it's just a JSON file with some mandatory and optional properties.</p>
<p>name: When the browser launches the splash screen, it will be the name displayed on the screen.</p>
<p>short_name: It will be the name displayed underneath your app shortcut on the home screen.</p>
<p>start_url: It will be the page shown to the user when your app is open.</p>
<p>display: It tells the browser how to display the app. There are several modes like <code>minimal-ui</code>, <code>fullscreen</code>, <code>browser</code> etc. Here, we use the <code>standalone</code> mode to hide everything related to the browser.</p>
<p>background_color: When the browser launches the splash screen, it will be the background of the screen.</p>
<p>theme_color: It will be the background color of the status bar when we open the app.</p>
<p>orientation: It tells the browser the orientation to have when displaying the app.</p>
<p>icons: When the browser launches the splash screen, it will be the icon displayed on the screen. Here, I used all sizes to fit any device's preferred icon. But you can just use one or two. It's up to you.</p>
<p>Now that we have a web app manifest, let's add it to the HTML file.</p>
<ul>
<li>In <code>index.html</code> (head tag)</li>
</ul>
<pre><code class="lang-html"><span class="hljs-tag">&lt;<span class="hljs-name">link</span> <span class="hljs-attr">rel</span>=<span class="hljs-string">"manifest"</span> <span class="hljs-attr">href</span>=<span class="hljs-string">"manifest.json"</span> /&gt;</span>
<span class="hljs-comment">&lt;!-- ios support --&gt;</span>
<span class="hljs-tag">&lt;<span class="hljs-name">link</span> <span class="hljs-attr">rel</span>=<span class="hljs-string">"apple-touch-icon"</span> <span class="hljs-attr">href</span>=<span class="hljs-string">"images/icons/icon-72x72.png"</span> /&gt;</span>
<span class="hljs-tag">&lt;<span class="hljs-name">link</span> <span class="hljs-attr">rel</span>=<span class="hljs-string">"apple-touch-icon"</span> <span class="hljs-attr">href</span>=<span class="hljs-string">"images/icons/icon-96x96.png"</span> /&gt;</span>
<span class="hljs-tag">&lt;<span class="hljs-name">link</span> <span class="hljs-attr">rel</span>=<span class="hljs-string">"apple-touch-icon"</span> <span class="hljs-attr">href</span>=<span class="hljs-string">"images/icons/icon-128x128.png"</span> /&gt;</span>
<span class="hljs-tag">&lt;<span class="hljs-name">link</span> <span class="hljs-attr">rel</span>=<span class="hljs-string">"apple-touch-icon"</span> <span class="hljs-attr">href</span>=<span class="hljs-string">"images/icons/icon-144x144.png"</span> /&gt;</span>
<span class="hljs-tag">&lt;<span class="hljs-name">link</span> <span class="hljs-attr">rel</span>=<span class="hljs-string">"apple-touch-icon"</span> <span class="hljs-attr">href</span>=<span class="hljs-string">"images/icons/icon-152x152.png"</span> /&gt;</span>
<span class="hljs-tag">&lt;<span class="hljs-name">link</span> <span class="hljs-attr">rel</span>=<span class="hljs-string">"apple-touch-icon"</span> <span class="hljs-attr">href</span>=<span class="hljs-string">"images/icons/icon-192x192.png"</span> /&gt;</span>
<span class="hljs-tag">&lt;<span class="hljs-name">link</span> <span class="hljs-attr">rel</span>=<span class="hljs-string">"apple-touch-icon"</span> <span class="hljs-attr">href</span>=<span class="hljs-string">"images/icons/icon-384x384.png"</span> /&gt;</span>
<span class="hljs-tag">&lt;<span class="hljs-name">link</span> <span class="hljs-attr">rel</span>=<span class="hljs-string">"apple-touch-icon"</span> <span class="hljs-attr">href</span>=<span class="hljs-string">"images/icons/icon-512x512.png"</span> /&gt;</span>
<span class="hljs-tag">&lt;<span class="hljs-name">meta</span> <span class="hljs-attr">name</span>=<span class="hljs-string">"apple-mobile-web-app-status-bar"</span> <span class="hljs-attr">content</span>=<span class="hljs-string">"#db4938"</span> /&gt;</span>
<span class="hljs-tag">&lt;<span class="hljs-name">meta</span> <span class="hljs-attr">name</span>=<span class="hljs-string">"theme-color"</span> <span class="hljs-attr">content</span>=<span class="hljs-string">"#db4938"</span> /&gt;</span>
</code></pre>
<p>As you can see, we linked our <code>manifest.json</code> file to the head tag. And add some other links which handle the iOS support to show the icons and colorize the status bar with our theme color.</p>
<p>With that, we can now dive into the final part and introduce the service worker.</p>
<h2 id="heading-what-is-a-service-worker">What is a Service Worker?</h2>
<p>Notice that PWAs run only on https because the service worker can access the request and handle it. Therefore security is required.</p>
<p>A service worker is a script that your browser runs in the background in a separate thread. That means it runs in a different place and is completely separate from your web page. That's the reason why it can't manipulate your DOM element.</p>
<p>However, it's super powerful. The service worker can intercept and handle network requests, manage the cache to enable offline support or send push notifications to your users.</p>
<p><img src="https://media.giphy.com/media/5VKbvrjxpVJCM/source.gif" alt="wow" width="470" height="376" loading="lazy"></p>
<p>S0 let's create our very first service worker in the root folder and name it <code>serviceWorker.js</code> (the name is up to you). But you have to put it in the root so that you don't limit its scope to one folder.</p>
<h3 id="heading-cache-the-assets">Cache the assets</h3>
<ul>
<li>In <code>serviceWorker.js</code></li>
</ul>
<pre><code class="lang-javascript"><span class="hljs-keyword">const</span> staticDevCoffee = <span class="hljs-string">"dev-coffee-site-v1"</span>
<span class="hljs-keyword">const</span> assets = [
  <span class="hljs-string">"/"</span>,
  <span class="hljs-string">"/index.html"</span>,
  <span class="hljs-string">"/css/style.css"</span>,
  <span class="hljs-string">"/js/app.js"</span>,
  <span class="hljs-string">"/images/coffee1.jpg"</span>,
  <span class="hljs-string">"/images/coffee2.jpg"</span>,
  <span class="hljs-string">"/images/coffee3.jpg"</span>,
  <span class="hljs-string">"/images/coffee4.jpg"</span>,
  <span class="hljs-string">"/images/coffee5.jpg"</span>,
  <span class="hljs-string">"/images/coffee6.jpg"</span>,
  <span class="hljs-string">"/images/coffee7.jpg"</span>,
  <span class="hljs-string">"/images/coffee8.jpg"</span>,
  <span class="hljs-string">"/images/coffee9.jpg"</span>,
]

self.addEventListener(<span class="hljs-string">"install"</span>, <span class="hljs-function"><span class="hljs-params">installEvent</span> =&gt;</span> {
  installEvent.waitUntil(
    caches.open(staticDevCoffee).then(<span class="hljs-function"><span class="hljs-params">cache</span> =&gt;</span> {
      cache.addAll(assets)
    })
  )
})
</code></pre>
<p>This code looks intimidating first but it just JavaScript (so don't worry).</p>
<p>We declare the name of our cache <code>staticDevCoffee</code> and the assets to store in the cache. And to perform that action, we need to attach a listener to <code>self</code>.</p>
<p><code>self</code> is the service worker itself. It enables us to listen to life cycle events and do something in return.</p>
<p>The service worker has several life cycles, and one of them is the <code>install</code> event. It runs when a service worker is installed. It's triggered as soon as the worker executes, and it's only called once per service worker.</p>
<p>When the <code>install</code> event is fired, we run the callback which gives us access to the <code>event</code> object.</p>
<p>Caching something on the browser can take some time to finish because it's asynchronous.</p>
<p>So to handle it, we need to use <code>waitUntil()</code> which, as you might guess, waits for the action to finish.</p>
<p>Once the cache API is ready, we can run the <code>open()</code> method and create our cache by passing its name as an argument to <code>caches.open(staticDevCoffee)</code>.</p>
<p>Then it returns a promise, which helps us store our assets in the cache with <code>cache.addAll(assets)</code>.</p>
<p><img src="https://www.freecodecamp.org/news/content/images/2021/10/cached-images.png" alt="Cached coffee images" width="600" height="400" loading="lazy"></p>
<p>Hopefully, you're still with me.</p>
<p><img src="https://media.giphy.com/media/OQEcw90jACeU8/source.gif" alt="desesperate" width="150" height="123" loading="lazy"></p>
<p>Now, we've successfully cached our assets in the browser. And the next time we load the page, the service worker will handle the request and fetch the cache if we are offline.</p>
<p>So, let's fetch our cache.</p>
<h3 id="heading-fetch-the-assets">Fetch the assets</h3>
<ul>
<li>In <code>serviceWorker.js</code></li>
</ul>
<pre><code class="lang-javascript">self.addEventListener(<span class="hljs-string">"fetch"</span>, <span class="hljs-function"><span class="hljs-params">fetchEvent</span> =&gt;</span> {
  fetchEvent.respondWith(
    caches.match(fetchEvent.request).then(<span class="hljs-function"><span class="hljs-params">res</span> =&gt;</span> {
      <span class="hljs-keyword">return</span> res || fetch(fetchEvent.request)
    })
  )
})
</code></pre>
<p>Here, we use the <code>fetch</code> event to, well, get back our data. The callback gives us access to <code>fetchEvent</code>. Then we attach <code>respondWith()</code> to prevent the browser's default response. Instead it returns a promise because the fetch action can take time to finish.</p>
<p>And once the cache ready, we apply the <code>caches.match(fetchEvent.request)</code>. It will check if something in the cache matches <code>fetchEvent.request</code>. By the way, <code>fetchEvent.request</code> is just our array of assets.</p>
<p>Then, it returns a promise. And finally, we can return the result if it exists or the initial fetch if not.</p>
<p>Now, our assets can be cached and fetched by the service worker which increases the load time of our images quite a bit.</p>
<p>And most important, it makes our app available in offline mode.</p>
<p>But a service worker alone can't do the job. We need to register it in our project.</p>
<p><img src="https://media.giphy.com/media/Z9EvIRmLEOS3JNFeVb/source.gif" alt="let-s-do-it" width="320" height="180" loading="lazy"></p>
<h2 id="heading-register-the-service-worker">Register the Service Worker</h2>
<ul>
<li>In <code>js/app.js</code></li>
</ul>
<pre><code class="lang-javascript"><span class="hljs-keyword">if</span> (<span class="hljs-string">"serviceWorker"</span> <span class="hljs-keyword">in</span> navigator) {
  <span class="hljs-built_in">window</span>.addEventListener(<span class="hljs-string">"load"</span>, <span class="hljs-function"><span class="hljs-keyword">function</span>(<span class="hljs-params"></span>) </span>{
    navigator.serviceWorker
      .register(<span class="hljs-string">"/serviceWorker.js"</span>)
      .then(<span class="hljs-function"><span class="hljs-params">res</span> =&gt;</span> <span class="hljs-built_in">console</span>.log(<span class="hljs-string">"service worker registered"</span>))
      .catch(<span class="hljs-function"><span class="hljs-params">err</span> =&gt;</span> <span class="hljs-built_in">console</span>.log(<span class="hljs-string">"service worker not registered"</span>, err))
  })
}
</code></pre>
<p>Here, we start by checking if the <code>serviceWorker</code> is supported by the current browser (as it's still not supported by all browsers).</p>
<p>Then, we listen to the page load event to register our service worker by passing the name of our file <code>serviceWorker.js</code> to <code>navigator.serviceWorker.register()</code> as a parameter to register our worker.</p>
<p>With this update, we have now transformed our regular web app to a PWA.</p>
<p><img src="https://media.giphy.com/media/3o6ZtlGkjeschymLNm/source.gif" alt="we-did-it" width="480" height="266" loading="lazy"></p>
<h2 id="heading-final-thoughts">Final thoughts</h2>
<p>Throughout this article, we have seen how amazing PWAs can be. By adding a web app manifest file and a service worker, it really improves the user experience of our traditional web app. This is because PWAs are fast, secure, reliable, and – most importantly – they support offline mode.</p>
<p>Many frameworks out there now come with a service worker file already set-up for us. But knowing how to implement it with Vanilla JavaScript can help you understand PWAs.</p>
<p>And you can go even further with service workers by caching assets dynamically or limiting the size of your cache and so on.</p>
<p>Thanks for reading this article.</p>
<p>You can check it out live <a target="_blank" href="https://devcoffee-pwa.netlify.com/">here</a> and the source code is <a target="_blank" href="https://github.com/ibrahima92/pwa-with-vanilla-js">here</a>.</p>
<p>Read more of my articles on <a target="_blank" href="https://www.ibrahima-ndaw.com/blog/how-to-build-pwa-with-javascript/">my blog</a></p>
<h2 id="heading-next-steps">Next steps</h2>
<p><a target="_blank" href="https://developers.google.com/web/fundamentals/web-app-manifest">Web Manifest Documentation</a></p>
<p><a target="_blank" href="https://developers.google.com/web/fundamentals/primers/service-workers">Service Worker Documentation</a></p>
<p><a target="_blank" href="https://app-manifest.firebaseapp.com/">Web Manifest Generator</a></p>
<p><a target="_blank" href="https://caniuse.com/#search=service%20worker">Browser Support</a></p>
 ]]>
                </content:encoded>
            </item>
        
            <item>
                <title>
                    <![CDATA[ How to Add to Homescreen in a Progressive Web App ]]>
                </title>
                <description>
                    <![CDATA[ Add To Homescreen Here the web app install banner is focused on web app, with the feature of add to homescreen. Browser Support for Add To Homescreen Add to Homescreen functionality is currently supported by: Chrome iOS Safari You can see the lates... ]]>
                </description>
                <link>https://www.freecodecamp.org/news/how-to-add-to-homescreen-in-a-progressive-web-app/</link>
                <guid isPermaLink="false">66c34ee85ced6d98e4bd332d</guid>
                
                    <category>
                        <![CDATA[ progressive web app ]]>
                    </category>
                
                    <category>
                        <![CDATA[ PWA ]]>
                    </category>
                
                    <category>
                        <![CDATA[ toothbrush ]]>
                    </category>
                
                <dc:creator>
                    <![CDATA[ freeCodeCamp ]]>
                </dc:creator>
                <pubDate>Sat, 01 Feb 2020 00:00:00 +0000</pubDate>
                <media:content url="https://www.freecodecamp.org/news/content/images/2020/02/daniel-korpai-beHREvNpn6k-unsplash.jpg" medium="image" />
                <content:encoded>
                    <![CDATA[ <h2 id="heading-add-to-homescreen"><strong>Add To Homescreen</strong></h2>
<p>Here the web app install banner is focused on web app, with the feature of add to homescreen.</p>
<h3 id="heading-browser-support-for-add-to-homescreen"><strong>Browser Support for Add To Homescreen</strong></h3>
<p>Add to Homescreen functionality is currently supported by:</p>
<ul>
<li>Chrome</li>
<li>iOS Safari</li>
</ul>
<p>You can see the latest status of browser support of this feature <a target="_blank" href="https://caniuse.com/#feat=web-app-manifest">here</a>.</p>
<h3 id="heading-on-android"><strong>On Android</strong></h3>
<p>On Android, the “add to homescreen” banner comes up automatically if you meet certain requirements. This is what it should look like on Android:</p>
<p>Add to homescreen promptWhen app launched</p>
<p><img src="https://user-images.githubusercontent.com/15358452/31663686-860779f0-b375-11e7-85c9-1387d9b3bfcf.png" alt="prompt" width="1080" height="1920" loading="lazy"></p>
<p><img src="https://user-images.githubusercontent.com/15358452/31663690-89b0d998-b375-11e7-8a84-f3e33be9a2c2.png" alt="launch" width="1080" height="1920" loading="lazy"></p>
<h3 id="heading-requirements">Requirements</h3>
<p>include a <code>manifest.json</code> file with the following properties:</p>
<ul>
<li><code>short name</code></li>
<li><code>name</code></li>
<li><code>192x192</code> size of <code>png</code> icon</li>
<li><code>start_url</code></li>
<li>include a service worker that is both registered and activated</li>
<li>the website served over HTTPS (you can still try this with localhost without HTTPS)</li>
<li>the website meets engagement heuristics defined by Chrome</li>
</ul>
<h4 id="heading-manifestjson"><strong>manifest.json</strong></h4>
<pre><code class="lang-json">{
  <span class="hljs-attr">"name"</span>: <span class="hljs-string">"FreeCodeCamp"</span>,
  <span class="hljs-attr">"short_name"</span>: <span class="hljs-string">"FreeCodeCamp"</span>,
  <span class="hljs-attr">"theme_color"</span>: <span class="hljs-string">"#00FF00"</span>,
  <span class="hljs-attr">"background_color"</span>: <span class="hljs-string">"#00FF00"</span>,
  <span class="hljs-attr">"display"</span>: <span class="hljs-string">"standalone"</span>,
  <span class="hljs-attr">"Scope"</span>: <span class="hljs-string">"/"</span>,
  <span class="hljs-attr">"start_url"</span>: <span class="hljs-string">"/"</span>,
  <span class="hljs-attr">"icons"</span>: [
    {
      <span class="hljs-attr">"src"</span>: <span class="hljs-string">"assets/images/icons/icon-72x72.png"</span>,
      <span class="hljs-attr">"sizes"</span>: <span class="hljs-string">"72x72"</span>,
      <span class="hljs-attr">"type"</span>: <span class="hljs-string">"image/png"</span>
    },
    {
      <span class="hljs-attr">"src"</span>: <span class="hljs-string">"assets/images/icons/icon-96x96.png"</span>,
      <span class="hljs-attr">"sizes"</span>: <span class="hljs-string">"96x96"</span>,
      <span class="hljs-attr">"type"</span>: <span class="hljs-string">"image/png"</span>
    },
    {
      <span class="hljs-attr">"src"</span>: <span class="hljs-string">"assets/images/icons/icon-128x128.png"</span>,
      <span class="hljs-attr">"sizes"</span>: <span class="hljs-string">"128x128"</span>,
      <span class="hljs-attr">"type"</span>: <span class="hljs-string">"image/png"</span>
    },
    {
      <span class="hljs-attr">"src"</span>: <span class="hljs-string">"assets/images/icons/icon-144x144.png"</span>,
      <span class="hljs-attr">"sizes"</span>: <span class="hljs-string">"144x144"</span>,
      <span class="hljs-attr">"type"</span>: <span class="hljs-string">"image/png"</span>
    },
    {
      <span class="hljs-attr">"src"</span>: <span class="hljs-string">"assets/images/icons/icon-152x152.png"</span>,
      <span class="hljs-attr">"sizes"</span>: <span class="hljs-string">"152x152"</span>,
      <span class="hljs-attr">"type"</span>: <span class="hljs-string">"image/png"</span>
    },
    {
      <span class="hljs-attr">"src"</span>: <span class="hljs-string">"assets/images/icons/icon-192x192.png"</span>,
      <span class="hljs-attr">"sizes"</span>: <span class="hljs-string">"192x192"</span>,
      <span class="hljs-attr">"type"</span>: <span class="hljs-string">"image/png"</span>
    },
    {
      <span class="hljs-attr">"src"</span>: <span class="hljs-string">"assets/images/icons/icon-384x384.png"</span>,
      <span class="hljs-attr">"sizes"</span>: <span class="hljs-string">"384x384"</span>,
      <span class="hljs-attr">"type"</span>: <span class="hljs-string">"image/png"</span>
    },
    {
      <span class="hljs-attr">"src"</span>: <span class="hljs-string">"assets/images/icons/icon-512x512.png"</span>,
      <span class="hljs-attr">"sizes"</span>: <span class="hljs-string">"512x512"</span>,
      <span class="hljs-attr">"type"</span>: <span class="hljs-string">"image/png"</span>
    }
  ],
  <span class="hljs-attr">"splash_pages"</span>: <span class="hljs-literal">null</span>
}
</code></pre>
<ul>
<li><code>name</code> is the name of the web app. (It will be shown in the launch screen)</li>
<li><code>short name</code> is the short name of the web app. (It will be shown below the icon of phone menu)</li>
<li><code>theme_color</code> is the color of the top of the browser.</li>
<li><code>background_color</code> is the background color of the launch screen.</li>
<li><code>display</code> is the way the web app should display once launched on the phone.</li>
<li><code>start_url</code> define the starting url of the website.</li>
<li><code>icons</code> is an array that store all the images location, sizes and type.</li>
</ul>
<h2 id="heading-on-other-devices">On other devices</h2>
<p>Although this method does not work on iOS and Windows, there is a fallback method.</p>
<h3 id="heading-ios">iOS</h3>
<p>On iOS, the “add to homescreen” button must be added manually. Add the following meta tags to the head section of your HTML to support iOS web app icon.</p>
<pre><code class="lang-html"><span class="hljs-tag">&lt;<span class="hljs-name">meta</span> <span class="hljs-attr">name</span>=<span class="hljs-string">"apple-mobile-web-app-capable"</span> <span class="hljs-attr">content</span>=<span class="hljs-string">"yes"</span>&gt;</span>
<span class="hljs-tag">&lt;<span class="hljs-name">meta</span> <span class="hljs-attr">name</span>=<span class="hljs-string">"apple-mobile-web-app-status-bar-style"</span> <span class="hljs-attr">content</span>=<span class="hljs-string">"green"</span>&gt;</span>
<span class="hljs-tag">&lt;<span class="hljs-name">meta</span> <span class="hljs-attr">name</span>=<span class="hljs-string">"apple-mobile-web-app-title"</span> <span class="hljs-attr">content</span>=<span class="hljs-string">"FreeCodeCamp"</span>&gt;</span>
<span class="hljs-tag">&lt;<span class="hljs-name">link</span> <span class="hljs-attr">rel</span>=<span class="hljs-string">"apple-touch-icon"</span> <span class="hljs-attr">href</span>=<span class="hljs-string">"/assets/images/icons/icon-72x72.png"</span> <span class="hljs-attr">sizes</span>=<span class="hljs-string">"72x72"</span>&gt;</span>
<span class="hljs-tag">&lt;<span class="hljs-name">link</span> <span class="hljs-attr">rel</span>=<span class="hljs-string">"apple-touch-icon"</span> <span class="hljs-attr">href</span>=<span class="hljs-string">"/assets/images/icons/icon-96x96.png"</span> <span class="hljs-attr">sizes</span>=<span class="hljs-string">"96x96"</span>&gt;</span>
<span class="hljs-tag">&lt;<span class="hljs-name">link</span> <span class="hljs-attr">rel</span>=<span class="hljs-string">"apple-touch-icon"</span> <span class="hljs-attr">href</span>=<span class="hljs-string">"/assets/images/icons/icon-128x128.png"</span> <span class="hljs-attr">sizes</span>=<span class="hljs-string">"128x128"</span>&gt;</span>
<span class="hljs-tag">&lt;<span class="hljs-name">link</span> <span class="hljs-attr">rel</span>=<span class="hljs-string">"apple-touch-icon"</span> <span class="hljs-attr">href</span>=<span class="hljs-string">"/assets/images/icons/icon-144x144.png"</span> <span class="hljs-attr">sizes</span>=<span class="hljs-string">"144x144"</span>&gt;</span>
<span class="hljs-tag">&lt;<span class="hljs-name">link</span> <span class="hljs-attr">rel</span>=<span class="hljs-string">"apple-touch-icon"</span> <span class="hljs-attr">href</span>=<span class="hljs-string">"/assets/images/icons/icon-152x152.png"</span> <span class="hljs-attr">sizes</span>=<span class="hljs-string">"152x152"</span>&gt;</span>
<span class="hljs-tag">&lt;<span class="hljs-name">link</span> <span class="hljs-attr">rel</span>=<span class="hljs-string">"apple-touch-icon"</span> <span class="hljs-attr">href</span>=<span class="hljs-string">"/assets/images/icons/icon-192x192.png"</span> <span class="hljs-attr">sizes</span>=<span class="hljs-string">"192x192"</span>&gt;</span>
<span class="hljs-tag">&lt;<span class="hljs-name">link</span> <span class="hljs-attr">rel</span>=<span class="hljs-string">"apple-touch-icon"</span> <span class="hljs-attr">href</span>=<span class="hljs-string">"/assets/images/icons/icon-384x384.png"</span> <span class="hljs-attr">sizes</span>=<span class="hljs-string">"384x384"</span>&gt;</span>
<span class="hljs-tag">&lt;<span class="hljs-name">link</span> <span class="hljs-attr">rel</span>=<span class="hljs-string">"apple-touch-icon"</span> <span class="hljs-attr">href</span>=<span class="hljs-string">"/assets/images/icons/icon-512x512.png"</span> <span class="hljs-attr">sizes</span>=<span class="hljs-string">"512x512"</span>&gt;</span>
</code></pre>
<h3 id="heading-windows">Windows</h3>
<p>On windows phone, add the following meta tags to the head section of your HTML:</p>
<pre><code class="lang-html"><span class="hljs-tag">&lt;<span class="hljs-name">meta</span> <span class="hljs-attr">name</span>=<span class="hljs-string">"msapplication-TileImage"</span> <span class="hljs-attr">content</span>=<span class="hljs-string">"/assets/images/icons/icon-144x144.png"</span>&gt;</span>
<span class="hljs-tag">&lt;<span class="hljs-name">meta</span> <span class="hljs-attr">name</span>=<span class="hljs-string">"msapplication-TileColor"</span> <span class="hljs-attr">content</span>=<span class="hljs-string">"green"</span>&gt;</span>
</code></pre>
 ]]>
                </content:encoded>
            </item>
        
            <item>
                <title>
                    <![CDATA[ Progressive Web Apps vs Accelerated Mobile Pages: What's the Difference and Which is Best for You? ]]>
                </title>
                <description>
                    <![CDATA[ Do you understand what PWAs and AMPs are, and which might be better for you? Let's have a look and find out. So many people own smartphones these days. This opens up endless opportunities for a business - opportunities which, however, are immediately... ]]>
                </description>
                <link>https://www.freecodecamp.org/news/pwa-vs-amp-what-is-the-difference-and-how-do-you-choose/</link>
                <guid isPermaLink="false">66be14ce2728aa32fea75a32</guid>
                
                    <category>
                        <![CDATA[ AMP ]]>
                    </category>
                
                    <category>
                        <![CDATA[ mobile ]]>
                    </category>
                
                    <category>
                        <![CDATA[ mobile app development ]]>
                    </category>
                
                    <category>
                        <![CDATA[ PWA ]]>
                    </category>
                
                <dc:creator>
                    <![CDATA[ Oleh Romanyuk ]]>
                </dc:creator>
                <pubDate>Tue, 07 Jan 2020 14:00:00 +0000</pubDate>
                <media:content url="https://www.freecodecamp.org/news/content/images/2020/01/PWA-vs-AMP.png" medium="image" />
                <content:encoded>
                    <![CDATA[ <p>Do you understand what PWAs and AMPs are, and which might be better for you? Let's have a look and find out.</p>
<p>So many people own smartphones these days. This opens up endless opportunities for a business - opportunities which, however, are immediately challenged by the immense number of competitors in the mobile software market. </p>
<p>Mobile apps are surely more convenient than web or desktop platforms. Yet, they are not the most comfortable option that the industry offers.</p>
<p>To hit the highest level of user satisfaction and to outrun competitors, inventive people opt in favor of progressive web apps (PWAs) or accelerated mobile pages (AMPs).</p>
<p>What are these things, and how should you choose the best option? Let’s consider each one-by-one by going through these simple questions:</p>
<ol>
<li>What is a PWA?</li>
<li>What is an AMP?</li>
<li>How are they similar?</li>
<li>How are they different?</li>
<li>Why PWAs are better than web pages</li>
<li>Why PWAs are better than native mobile</li>
<li>Why AMPs are better than web pages</li>
<li>Why AMPs are NOT better than native mobile</li>
</ol>
<h2 id="heading-1-what-is-a-pwa">1. What Is a PWA?</h2>
<p>A progressive web application, or PWA, unites the advantages of both web and mobile apps into a single software product. As <a target="_blank" href="https://developers.google.com/web/progressive-web-apps">Google</a> declares, PWAs are “user experiences that have the reach of the web and are reliable, fast, and engaging”. It is a technology that lets you use a website as if it was a native app.</p>
<p><a target="_blank" href="https://twitter.com/?lang=en">Twitter</a> is one of the major companies using PWAs. To install the app, you open the web version on your phone and add it to your home screen. When you open Twitter from the home screen icon, you will be opening it as a progressive web app.</p>
<p><img src="https://images.ctfassets.net/6xhdtf1foerq/1TGtQag89baQtjFwQFRG1t/29cacc2916819498d669921833c88ceb/8-min.png?fm=png&amp;q=85&amp;w=1000" alt="PWA" width="1000" height="563" loading="lazy"></p>
<h3 id="heading-fundamentals">Fundamentals</h3>
<p>PWAs are a highly responsive and easily shareable solution, which can work offline. They store HTML and CSS files in the browser cache and archive them with service workers. This makes it possible to use the web page offline. Service workers are one of the three essential components of a PWA, along with the manifest file and a secure protocol HTTPS.</p>
<p><strong>Service workers</strong> are JavaScript code components which play the role of a proxy between the network and the browser. </p>
<p>When you open a web page for the first time, service workers store the necessary data in the browser cache. When you open it for the second time, service workers retrieve this data from the cache even before the app checked network availability. </p>
<p>Not only do they provide the ability to work offline but also they greatly boost the response time. Service workers also manage push notifications.</p>
<p><strong>The manifest file</strong> is a JSON file containing all the information about your app. For instance, it contains data about the home screen icon of your PWA, its short name, color palette, or theme. </p>
<p>If you are using the Chrome browser on an Android phone, the manifest file will trigger the automatic installation of the PWA onto your phone.</p>
<p><strong>The secure HTTPS protocol</strong> is an absolute must if you develop a progressive web app. While service workers make the very concept of a PWA possible, they are vulnerable to network errors or breaches. Service workers can intercept network requests and modify responses. To ensure data security and network security, the secure protocol needs to be used.</p>
<h3 id="heading-success-stories">Success stories</h3>
<p>Twitter is not the only company that has benefited from PWAs. Check out these <a target="_blank" href="https://developers.google.com/web/showcase/">case studies</a> published by Google to see how this technology helped popular businesses succeed. Among these companies are <a target="_blank" href="https://www.pinterest.com/">Pinterest</a>, <a target="_blank" href="https://www.alibaba.com/">Alibaba</a>, <a target="_blank" href="https://weather.com/">The Weather Channel</a>, <a target="_blank" href="https://www.lancome.com/">Lancome</a>, and <a target="_blank" href="https://www.homedepot.com/">The Home Depot.</a></p>
<h2 id="heading-2-what-is-an-amp">2. What Is an AMP?</h2>
<p>AMP stands for accelerated mobile page. It's a mobile-friendly web page, which is designed to be loaded instantly. It is a fast and smoothly loading solution which is developed with the user experience in mind. Introduced as an open-source project, the AMP technology was integrated by Google in February 2016.</p>
<p>In 2016, <a target="_blank" href="https://www.theguardian.com/membership/2016/feb/24/todays-release-of-accelerated-mobile-pages-amp">The Guardian announced</a> that their platform was now available as an AMP. To help readers see how it worked, they displayed the same article both as <a target="_blank" href="https://www.theguardian.com/us-news/commentisfree/2016/feb/16/thomas-piketty-bernie-sanders-us-election-2016">a web version</a> and <a target="_blank" href="https://amp.theguardian.com/us-news/commentisfree/2016/feb/16/thomas-piketty-bernie-sanders-us-election-2016">an AMP version</a>. </p>
<p>There were some differences, but they were insignificant. But what you'd notice right away was how much faster the AMP article loaded compared to the regular web article.</p>
<p><img src="https://images.ctfassets.net/6xhdtf1foerq/1ARjDXZC1yH4p15rxRosIW/e23cf5144729c5985e5a8ac156fb66a6/2.7_billion_people_use_smartphones__1_-min.png?fm=png&amp;q=85&amp;w=1000" alt="*By comparing this illustration with the one I included earlier, you could notice an interesting point. A PWA needs to be installed. In turn, you do not need to install AMP. It is accessed via a different like.
" width="1000" height="563" loading="lazy">
<em>*By comparing this illustration with the one I included earlier, you could notice an interesting point. A PWA needs to be installed. In turn, you do not need to install AMP. It is accessed via a different like.</em></p>
<h3 id="heading-fundamentals-1">Fundamentals</h3>
<p>The idea of AMPs is to reduce the amount of unnecessary content and functionality so that the app displays essential content immediately. The data can be reduced up to ten times. The three essential components of AMPs are AMP HTML, AMP Components, and the AMP Cache.</p>
<p><strong>AMP HTML</strong> is a simplified version of regular HTML. AMP HTML does not allow some tags and elements of HTML (for example, forms). To understand better what AMP HTML should look like, check out the <a target="_blank" href="https://amp.dev/documentation/guides-and-tutorials/start/create/basic_markup/?referrer=ampproject.org">required mark-up</a>.</p>
<p><strong>AMP Components</strong> are the scripts that enable you to do without JavaScript. The idea of AMP is to get rid of all JavaScript scripts as make pages load more slowly. </p>
<p>But this does not mean that your page should do without animations, modified layouts, analytics data, autocomplete suggestions, or ads. There is an extensive <a target="_blank" href="https://amp.dev/documentation/components/?referrer=ampproject.org">library of components</a> that enable you to implement these and a lot of other features.</p>
<p><strong>The AMP Cache</strong> is a proxy-based content delivery network which fetches and caches page content. AMP Cache enables you as an app owner to easily introduce page updates. It optimizes and, if needed, modifies the AMP.</p>
<h3 id="heading-success-stories-1">Success stories</h3>
<p>The same as with PWAs, companies are often very proud of the business advantages that AMPs offer. Here is a collection of <a target="_blank" href="https://amp.dev/success-stories/">success stories</a> and case studies of companies that used AMPs and benefited from them. <a target="_blank" href="https://www.musement.com/us/">Musement</a>, <a target="_blank" href="http://www.rcsmediagroup.it/">RCS MediaGroup</a>, <a target="_blank" href="https://www.cnbc.com/">CNBC</a>, <a target="_blank" href="https://www.washingtonpost.com/">The Washington Post</a> are all companies that have implemented or plan to implement AMPs.</p>
<h2 id="heading-3-how-are-pwas-and-amps-similar">3. How Are PWAs and AMPs similar?</h2>
<p>Both PWAs and AMPs are methods of displaying web pages on mobile devices. Both of them are created to enhance the user experience. </p>
<p>AMPs and PWAs both help reduce page load time. While AMPs may be slightly more effective in terms of loading speed than PWAs, the difference between AMP and PWA loading times is barely noticeable. </p>
<p>Both technologies are actively supported by Google. There is <a target="_blank" href="https://developers.google.com/web/progressive-web-apps">a PWA page on Google Developers</a> and <a target="_blank" href="https://developers.google.com/amp">an AMP page on Google Developers</a> as well.</p>
<p>There are not a lot of other similarities, but this primary similarity is essential. </p>
<p>Now let’s see what the differences are. </p>
<h2 id="heading-4-how-are-pwas-and-amps-different">4. How Are PWAs and AMPs Different?</h2>
<h3 id="heading-appearance">Appearance</h3>
<p>By using a PWA you do not feel like you are using a web page. PWAs look and feel like a mobile app.</p>
<p>By using AMPs, you are well aware that you are using a web page because it looks the same.</p>
<h3 id="heading-development">Development</h3>
<p>In the case of PWAs, the application code is written either from scratch or with some parts of the existing code.</p>
<p>In the case of AMPs, the existing code of a web page is stripped of unnecessary CSS and JS so that the web page loads faster.</p>
<h3 id="heading-user-experience">User experience</h3>
<p>PWAs offer a much better user experience. They have push-notifications, a home screen icon, and no browser tabs. Also, they are much easier to download and lighter in size than a regular mobile app. PWAs load faster than a regular web version because they are embedded with App Shell. And PWAs can be used when the network connection is down.</p>
<p>AMPs offer a slightly improved user experience since the page loads faster than a regular page. Still, this is the only UX advantage that they offer. Unlike PWAs, AMPs cannot work offline.</p>
<h3 id="heading-performance">Performance</h3>
<p>From an SEO standpoint, AMP wins the competition. Google favors these pages and lists them in the carousel of top stories, which can increase your click-through rate.</p>
<p>PWAs, in turn, do not have a direct advantage for SEO. However, better user experience translates into higher retention rates, which helps you win with SEO.</p>
<h3 id="heading-support">Support</h3>
<p>PWAs are not supported equally on all devices, so you may find slight inconveniences when they're displayed on iOS. Also, they do not support all the hardware functionalities, such as Bluetooth, NFC, GPS, or accelerometers.</p>
<p>AMPs are supported by all major browsers on all devices.</p>
<h3 id="heading-apps-theyre-best-suited-for">Apps they're best suited for</h3>
<p>PWAs work perfectly for apps that require user interactions. E-commerce websites, social media, or online learning platforms where the app needs to be responsive and constantly updated can make use of this technology. This is why Twitter uses a PWA, for example.</p>
<p>AMPs are more suitable for platforms with a wall of content, such as online magazines or newspapers. AMPs load content instantly, but the interaction opportunities are limited. This is why The Guardian decided to employ AMPs.</p>
<h2 id="heading-5-why-pwas-are-better-than-web-pages">5. Why PWAs Are Better Than Web Pages</h2>
<p>If you access a web page on mobile, you will have to deal with browser tabs, slow loading times, and annoying pop-ups. If your device has a relatively small screen or a slow network connection, surfing the web on it becomes unbearable.</p>
<p>This problem is solved by progressive web apps. In a few clicks, you install the app on your phone and get down to using it. No need to type a link, no browser tabs, and no pop-up screens. The app works fast, and it does work if the network connection is down.</p>
<p>Alright, the benefits of this solution are evident, but it seems like native mobile apps could work perfectly instead. No, they would not. Let’s see why PWAs are better than native mobile apps.</p>
<h2 id="heading-6-why-pwas-are-better-than-native-mobile-apps">6. Why PWAs Are Better Than Native Mobile Apps</h2>
<p>To use a native mobile application, you have to find it in the App Store or Google Play catalogs. Then, you have to wait for some time to download it. You might not have enough free space on your device, so you'll need to find some room.</p>
<p>In turn, PWAs are installed and ready to use in seconds. The file size is small (although it's bound to increase while you are using the app and it is caching). Still, the size of the cached data depends on the amount of free storage you have on your device.</p>
<p>As you can see, PWAs seem better than a web or native mobile app. But you should understand that this solution is not universal. Check out my recent article on <a target="_blank" href="https://keenethics.com/blog/progressive-web-apps-vs-native-which-to-choose-and-when">PWA vs Native</a> to learn when a native app is a better choice than a PWA.</p>
<h2 id="heading-7-why-amps-are-better-than-web-pages">7. Why AMPs Are Better Than Web Pages</h2>
<p>As mentioned above, web apps are slow and inconvenient, especially when accessed on mobile devices with small screen size or underpowered hardware.</p>
<p>By getting rid of all the web components that are unnecessary for a good user experience, accelerated mobile pages solve this problem. AMPs perform 4 times faster and use 10 times less data than regular web pages.</p>
<h2 id="heading-8-why-amps-are-not-better-than-native-mobile">8. Why AMPs Are NOT Better Than Native Mobile</h2>
<p>Unfortunately, AMPs cannot be a complete substitute for native mobile apps. They cannot be installed on the home screen, they still include browser tabs, and their functionality is limited to some basic stuff.</p>
<p>But for an online newspaper or for an informational website, such as <a target="_blank" href="https://www.webmd.com/">WebMD</a>, it is better to use AMPs rather than a native mobile app. These do not require any additional functionality for displaying page content.</p>
<h2 id="heading-to-wrap-up">To Wrap Up</h2>
<p>Both AMPs and PWAs are powerful technologies. To sum up the results of our initial question - PWAs vs AMPs: </p>
<ul>
<li>AMPs will be easier, faster, and cheaper for you to develop </li>
<li>PWA will offer more benefits.</li>
</ul>
<p>Just remember - neither of them is a universal solution, and neither is a panacea. Even using AMPs and PWAs together may not meet all your demands. Sometimes you may need to choose more conventional types of software. </p>
<p>If you are still not sure what to choose, our specialists share <a target="_blank" href="https://www.freecodecamp.org/news/four-questions-to-understand-if-you-need-pwa/">four questions to understand if you need PWA</a>. In short: <em>We believe that progressive web apps are the future. Accelerated mobile pages are just too simple and limited in functionality to compete.</em></p>
<h2 id="heading-do-you-have-an-idea-for-a-project">Do you have an idea for a project?</h2>
<p>My company KeenEthics is experienced in both AMP and <a target="_blank" href="https://keenethics.com/tech-apps-progressive-web-apps">progressive web app development</a>. If you are ready to change the game and start your project, feel free to <a target="_blank" href="https://keenethics.com/contacts">get in touch</a><em>.</em></p>
<h2 id="heading-ps">P.S.</h2>
<p>The original article posted on KeenEthics blog can be found here: <a target="_blank" href="https://keenethics.com/blog/pwa-vs-amp">PWA vs AMP:  What Is the Difference and How Do You Choose?</a></p>
 ]]>
                </content:encoded>
            </item>
        
            <item>
                <title>
                    <![CDATA[ Do you really need a PWA? Here are four questions to help you decide. ]]>
                </title>
                <description>
                    <![CDATA[ Why you need a PWA is not in question. Let’s see why you may NOT need it My inbox has been filled with questions regarding PWAs after my last two articles.  My “Practical Tips on Progressive Web App Development” shared the actual advantages of the PW... ]]>
                </description>
                <link>https://www.freecodecamp.org/news/four-questions-to-understand-if-you-need-pwa/</link>
                <guid isPermaLink="false">66be14b49f6cdc38e7dec50d</guid>
                
                    <category>
                        <![CDATA[ mobile app development ]]>
                    </category>
                
                    <category>
                        <![CDATA[ progressive web app ]]>
                    </category>
                
                    <category>
                        <![CDATA[ PWA ]]>
                    </category>
                
                    <category>
                        <![CDATA[ Web Development ]]>
                    </category>
                
                <dc:creator>
                    <![CDATA[ Oleh Romanyuk ]]>
                </dc:creator>
                <pubDate>Thu, 26 Sep 2019 14:00:00 +0000</pubDate>
                <media:content url="https://www.freecodecamp.org/news/content/images/2019/09/Do-you-need-PWA.png" medium="image" />
                <content:encoded>
                    <![CDATA[ <h2 id="heading-why-you-need-a-pwa-is-not-in-question-lets-see-why-you-may-not-need-it">Why you need a PWA is not in question. Let’s see why you may NOT need it</h2>
<p>My inbox has been filled with questions regarding PWAs after my last two articles. </p>
<p>My “<a target="_blank" href="https://www.freecodecamp.org/news/practical-tips-on-progressive-web-app-development/">Practical Tips on Progressive Web App Development</a>” shared the actual advantages of the PWA technology, and my “<a target="_blank" href="https://keenethics.com/blog/progressive-web-apps-vs-native-which-to-choose-and-when">Progressive Web Apps vs Native</a>” further strengthened many people's belief that a progressive web app is exactly what they need.</p>
<p>I am exceptionally happy that I managed to convince them that PWAs are the future of web apps. I support the PWA technology as a path that the software development industry should take in the future. But progressive web apps are not a panacea, not a one-size-fits-all solution.</p>
<p>Since I have already explained <em>what PWA means</em> and <em>why you need</em> a <em>PWA</em>, today, I am going to finish the job and show <em>why you may NOT need</em> a <em>PWA</em>.</p>
<p>Making the choice between using and not using PWA is easier than you assume. Just answer four simple questions and you will see the right path yourself.</p>
<h2 id="heading-1-do-you-have-any-ready-made-developments">1. Do you have any ready-made developments?</h2>
<p>The speed and simplicity of the progressive web app development have been mentioned among the major benefits of PWAs. However, this advantage can be negated by the fact that you already have some ready-made developments for a mobile app. </p>
<p>Converting a ready-made mobile app to a progressive web app cannot be done with a few simple clicks. Instead, a progressive web app has to be developed from scratch. If you had already gone through the lengthy and complicated process of native app development, why would you throw it out and start everything from the beginning?</p>
<p>Surely, your mobile app can be outdated or inefficient. In this case, you have to choose whether to revive it with the help of a mobile app development company or to build an entirely new application using the PWA technology.</p>
<p>For instance, <a target="_blank" href="https://www.pinterest.ca/">Pinterest</a> – one of the brightest examples of companies that implemented PWA – decided to choose both options. They have developed a progressive web app but did not forget about the traditional mobile application either.</p>
<p><img src="https://www.freecodecamp.org/news/content/images/2019/09/Progressive-web-app.png" alt="Image" width="600" height="400" loading="lazy"></p>
<p><em><strong>Key idea</strong>: The PWA approach is a great solution if you have no ready-made developments. If you already have a native or cross-platform mobile version of your app, it will be easier and more productive to finish those rather than build a new application from scratch.</em></p>
<h2 id="heading-2-do-you-plan-to-focus-on-mobile-devices-only">2. Do you plan to focus on mobile devices only?</h2>
<p>The idea of PWA features is to improve the experience of a user accessing a web page from a mobile device. Until the PWA technology was introduced, the businesses willing to target both web and mobile had two options:</p>
<ul>
<li>to adapt a web page to mobile devices</li>
<li>to develop a mobile app from scratch</li>
</ul>
<p>The first option is clumsy and inconvenient from the perspective of a user. The second option is resource-consuming from the perspective of a business. </p>
<p>The advancement of PWAs brought the Golden Mean: convenience for the user and efficiency for the business. If you do not plan to enter the web or desktop niche but choose to focus solely on mobile apps, there is no need to build a PWA. </p>
<p>Surely, when developing progressive web apps, you win because you're developing one application for both Android and iOS. However, this is exactly what you can achieve with cross-platform development with Ionic, Cordova, or React Native.  </p>
<p>Meanwhile, Pinterest has developed a progressive web app to augment their already existent web platform and mobile app.</p>
<p><img src="https://www.freecodecamp.org/news/content/images/2019/09/Progressive-web-app--4-.png" alt="Image" width="600" height="400" loading="lazy"></p>
<p><em><strong>Key idea</strong>: PWAs work best for the businesses targeting both web and mobile platforms. If you choose to focus on only one of those, there is no reason to choose progressive web app development.</em></p>
<h2 id="heading-3-are-you-reluctant-to-let-your-users-access-the-app-via-a-web-browser">3. Are you reluctant to let your users access the app via a web browser?</h2>
<p>Another essential difference between mobile apps and progressive web apps rests in the fact that the former are downloaded from the store, while the latter – via a shared link or directly from a website as it is illustrated below.</p>
<p><img src="https://www.freecodecamp.org/news/content/images/2019/09/Progressive-web-app--3-.png" alt="Image" width="600" height="400" loading="lazy"></p>
<p>Tastes differ. If you think that working with App Store or Google Play is easier than fighting the SEO competition on Google or inspiring users to share your links, you should feel free to choose mobile app development. </p>
<p>If you think that it is more difficult to be featured in top search results in Stores comparing to Google search results, go with PWA. Besides, if your website already has a sufficiently large target audience of devoted users, progressive web apps would work best for you.</p>
<hr>
<p><em><strong>Key idea</strong>: If you want your users to access the application via the App Store or Google Play rather than through a browser, or if app store optimization (ASO) sounds simpler than search engine optimization (SEO), you should go with a mobile app.</em></p>
<h2 id="heading-4-do-you-need-advanced-mobile-device-features">4. Do you need advanced mobile device features?</h2>
<p>While PWAs can work with geolocation, a lot of advanced mobile device features remain beyond their reach. Progressive web apps do not work with proximity sensors, advanced camera controls, audio or video recording, fingerprint scanning, NFC, or even Bluetooth. </p>
<p>Ask your development team for the PWA checklist of features that they will not be able to implement. If you cannot do without at least one of these features, you should opt in favor of mobile development.</p>
<p>For instance, the progressive web app of Pinterest does not feature the function of taking photos, which is present in their mobile app.</p>
<p><img src="https://www.freecodecamp.org/news/content/images/2019/09/Progressive-web-app--2--1.png" alt="Image" width="600" height="400" loading="lazy"></p>
<p><em><strong>Key idea</strong>: Incompatibility with native device features can hinder you from developing a PWA. While this issue may be solved in the future, for now, you should go with mobile apps instead.</em></p>
<h2 id="heading-to-wrap-up">To Wrap Up</h2>
<p>Before asking for a PWA, you need to decide whether you really need the PWA approach. Start by answering four simple questions:</p>
<ol>
<li>Do you have any ready-made developments, such as a native mobile app?</li>
<li>Do you want to focus on mobile devices only? No desktop or web platforms?</li>
<li>Are you reluctant to let your users access the app via a web browser?</li>
<li>Do you need advanced mobile device features? Are you going to use fingerprint scanning, NFC, or Bluetooth?</li>
</ol>
<p>If you answer "yes" to the majority of these questions about progressive web apps, then, probably, you do not need a progressive web app. A mobile app will fully cover all your needs.</p>
<h2 id="heading-do-you-have-an-idea-for-a-pwa"><strong>Do you have an idea for a PWA?</strong></h2>
<p>My company KeenEthics is an experienced <a target="_blank" href="https://keenethics.com/tech-apps-progressive-web-apps">progressive web app development company</a>. In case you need the following services, feel free to <a target="_blank" href="https://keenethics.com/contacts">get in touch</a><em>.</em></p>
<p>You can read more of my articles on the KeenEthics blog: <a target="_blank" href="https://keenethics.com/blog/four-questions-to-understand-if-you-need-pwa">Four Questions to Understand If You Need PWA</a>.</p>
<h2 id="heading-ps"><strong>P.S.</strong></h2>
<p>Also, I would like to say "thank you" to <a target="_blank" href="https://www.linkedin.com/in/tania-matviiok-222b9b16a/">Tetiana Matviiok</a> for helping me edit this article.</p>
<p>The original article posted on KeenEthics blog can be found here: <a target="_blank" href="https://keenethics.com/blog/progressive-web-apps-vs-native-which-to-choose-and-when">Progressive Web Apps vs Native: Which to Choose and When?</a>.</p>
 ]]>
                </content:encoded>
            </item>
        
            <item>
                <title>
                    <![CDATA[ An explanation of Progressive Web Apps for the non-PWA crowd ]]>
                </title>
                <description>
                    <![CDATA[ The world of applications was classified into two categories not too long ago. You were either creating an application for Android devices or for iOS. Enter PWAs, or elongated, Progressive Web Applications. You have probably been hearing all about th... ]]>
                </description>
                <link>https://www.freecodecamp.org/news/an-explanation-of-progressive-web-apps-for-the-non-pwa-crowd-8a400e275ea1/</link>
                <guid isPermaLink="false">66ba4fc98e44e0cdf1281248</guid>
                
                    <category>
                        <![CDATA[ coding ]]>
                    </category>
                
                    <category>
                        <![CDATA[ General Programming ]]>
                    </category>
                
                    <category>
                        <![CDATA[ PWA ]]>
                    </category>
                
                    <category>
                        <![CDATA[ tech  ]]>
                    </category>
                
                    <category>
                        <![CDATA[ Web Development ]]>
                    </category>
                
                <dc:creator>
                    <![CDATA[ Tomer ]]>
                </dc:creator>
                <pubDate>Mon, 13 May 2019 16:02:17 +0000</pubDate>
                <media:content url="https://cdn-media-1.freecodecamp.org/images/0*bFT4XZ6spjmElUly" medium="image" />
                <content:encoded>
                    <![CDATA[ <p>The world of applications was classified into two categories not too long ago. You were either creating an application for Android devices or for iOS. Enter PWAs, or elongated, <strong>P</strong>rogressive <strong>W</strong>eb <strong>A</strong>pplications. You have probably been hearing all about them for the past couple of years, but besides a nice acronym, you have no idea what a PWA is. As their popularity increases, it might be a good idea to get to know what all the fuss is about.</p>
<p>In this article, I’ll take you on a tour of what a PWA is, what components it is built from, and show you how you can make one on your own.</p>
<h4 id="heading-the-basics">The Basics</h4>
<p>A progressive web application is a website turned into an application. What this means is, that instead of having to code either in Java or Objective-C (or more recent mobile coding languages), you can write the code for the application, like you would for a website. You have your html files, your stylesheets, and your scripts.</p>
<p>Why would you build a PWA instead of a native application? For starters, imagine that once you release a PWA, you can change it constantly without having to republish your application. Since all the code is hosted on a server and not part of the APK/IPA, any change you make happens in real time.</p>
<p>If you have ever used an application that relies on a network connection, you are familiar with the frustration of not being able to do anything. With PWAs, you have the ability to offer an offline experience to your users in case of network issues.</p>
<p>And to add the cherry on top, there is an ability to prompt the user to add your PWA to their home screen. Something that native applications do not possess.</p>
<h4 id="heading-components">Components</h4>
<p>There is a standard regarding a PWA, and you must adhere to it if you want to publish one. Each PWA is built from the following components:</p>
<ul>
<li>A web app manifest</li>
<li>A service worker</li>
<li>Install experience</li>
<li>HTTPS</li>
<li>Creating an APK</li>
<li>Lighthouse audit</li>
</ul>
<h4 id="heading-the-manifest">The Manifest</h4>
<p>This is purely a configuration file (<strong><em>.JSON</em></strong>), enabling you to change various settings of your PWA and how it will appear to the user. Below is an example of one:</p>
<p>You must set either a name/short name key. When setting both, short name will be used on the home screen and the launcher. The name value will be used in the Add to Home Screen experience (or, application install prompt).</p>
<p>Display can have four different values:</p>
<ul>
<li><strong>fullscreen</strong> - this allows your application to take up the whole screen when it is opened</li>
<li><strong>standalone</strong> - your application looks like a native application, hiding browser elements</li>
<li><strong>minimal-ui</strong> - provides some browsing controls (only supported for Chrome mobile)</li>
<li><strong>browser</strong> - like the name says your application’s look will be identical to a browsing experience</li>
</ul>
<p>You can also set the <strong>orientation</strong> of your application and the <strong>scope</strong> of the pages considered to be within your application.</p>
<p>Don’t forget to add the manifest to your main html file by putting the following meta tag inside of your head tag:</p>
<p><img src="https://cdn-media-1.freecodecamp.org/images/-sgj8knyKimbaSIeLGhmo5oflTKZzHunce4V" alt="Image" width="800" height="534" loading="lazy">
_Photo by [Unsplash](https://unsplash.com/@solimonster?utm_source=medium&amp;utm_medium=referral" rel="noopener" target="_blank" title=""&gt;sol on &lt;a href="https://unsplash.com?utm_source=medium&amp;utm_medium=referral" rel="noopener" target="<em>blank" title=")</em></p>
<h4 id="heading-the-service-worker">The Service Worker</h4>
<p>A service worker is a component running in the background of your website on the browser. It has a wide set of functionalities including, push notifications, caching assets and providing them for an offline experience and the ability to defer actions until the user has a stable connection to the internet. A service worker can be a whole Medium article on its own, so I won’t delve into the <a target="_blank" href="https://developer.mozilla.org/en-US/docs/Web/API/Service_Worker_API">inner details</a> of how it works. But, I will supply a vanilla example of one for you to use in your PWA.</p>
<p>It is customary to save the code related to the service worker in a file called <strong><em>sw.js</em></strong>.</p>
<blockquote>
<p>✋ The location of the service worker is important since it can only access files that are in the same directory or subdirectory as itself.</p>
</blockquote>
<p>A service worker has a lifecycle that can be summed up to the following phases:</p>
<ul>
<li>Registration</li>
<li>Installation/Activation</li>
<li>Responding to various events</li>
</ul>
<h4 id="heading-install-experience">Install Experience</h4>
<p>One of the unique features of a PWA is its install experience. What this translates to is prompting the user to install your application. To allow us to present this ability to the user, we will need to listen in on an event called <strong><em>beforeinstallprompt</em></strong>. Below is a code sample demonstrating the flow from presenting the user with the option to add the application to activating logic based on their choice.</p>
<p><img src="https://cdn-media-1.freecodecamp.org/images/LG4XqHneeagI9dGNOJ28F2oYInSR6vjQRTvy" alt="Image" width="800" height="533" loading="lazy">
_Photo by [Unsplash](https://unsplash.com/@jamessutton_photography?utm_source=medium&amp;utm_medium=referral" rel="noopener" target="_blank" title=""&gt;James Sutton on &lt;a href="https://unsplash.com?utm_source=medium&amp;utm_medium=referral" rel="noopener" target="<em>blank" title=")</em></p>
<h4 id="heading-https">HTTPS</h4>
<p>Not too long ago, websites could still use the all too common <a target="_blank" href="https://www.w3schools.com/whatis/whatis_http.asp">http</a> protocol. Due to recent changes in security and in <a target="_blank" href="https://searchengineland.com/effective-july-2018-googles-chrome-browser-will-mark-non-https-sites-as-not-secure-291623">Chrome</a>, all websites that do not operate under the https protocol will be marked as not secured. Even if your website does not handle user data or sensitive communication, it is still good practice to switch over to https.</p>
<p>And like I mentioned earlier, if you want to be able to release a PWA, it has to use the https protocol. If you don’t want to get into the hassle of acquiring a domain, finding a proper host for it and then enabling SSL, you can go for the easy option of Github. If you have an account, you can open a repository and set up a <a target="_blank" href="https://pages.github.com/">GitHub Page</a>. This process is fairly simple and straightforward and the bonus is getting the HTTPS built in as part of Github.</p>
<h4 id="heading-creating-an-apk">Creating An APK</h4>
<p>In order for our PWA to be available inside the Google Play Store, we need to create an APK. You can use the popular tool, <a target="_blank" href="https://pwa2apk.com/?ref=steemhunt">PWA2APK</a>, which will do the hard work for you. But if you prefer to learn how to do it yourself, keep reading.</p>
<p>Google has introduced a new way to integrate your PWA into the Play store using what is called a <strong><em>T</em></strong>rusted <strong><em>W</em></strong>eb <strong><em>A</em></strong>ctivity, or TWA. With just a few simple steps you will learn how to create a TWA, which you can then upload to the Play store.</p>
<ol>
<li>Open Android Studio and create an empty activity</li>
<li><p>Go to the project’s build.gradle file and add the jitpack repository</p>
</li>
<li><p>Go to the <strong><em>module level</em></strong> build.gradle file and add the following lines to enable Java8 compatibility</p>
</li>
<li><p>Add the TWA support library as a dependency</p>
</li>
<li><p>Add the activity XML inside your AndroidManifest file between the application tags</p>
</li>
<li><p>We need to create an association from the application to the website using a digital assets link. Paste the following inside your <strong><em>strings.xml</em></strong> file</p>
</li>
<li><p>Add the next meta tag as a child to your application tag inside the AndroidManifest.xml</p>
</li>
<li><p><a target="_blank" href="https://developer.android.com/studio/publish/app-signing#generate-key">Create an upload key and keystore</a></p>
</li>
<li><p>Use the following command to extract the SHA-256</p>
</li>
<li><p>Go to the <a target="_blank" href="https://developers.google.com/digital-asset-links/tools/generator">assets link generator</a>, supply the SHA-256 fingerprint, the package of your application and the web site's domain</p>
</li>
<li><p>Place the result in a file named <strong><em>assetlinks.json</em></strong> under the location <strong><em>/.well-known</em></strong> in your website’s directory. Chrome will look for this destination specifically.</p>
</li>
<li><p><a target="_blank" href="https://medium.freecodecamp.org/how-to-publish-an-application-in-the-play-store-8ddcc6dc3587">Generate a signed APK and upload to the Play store</a></p>
</li>
</ol>
<p><img src="https://cdn-media-1.freecodecamp.org/images/mp3eDdZW9F9StMhoajqbVozrN3FPeyDgQw8s" alt="Image" width="800" height="599" loading="lazy">
_Photo by [Unsplash](https://unsplash.com/@aaronburden?utm_source=medium&amp;utm_medium=referral" rel="noopener" target="_blank" title=""&gt;Aaron Burden on &lt;a href="https://unsplash.com?utm_source=medium&amp;utm_medium=referral" rel="noopener" target="<em>blank" title=")</em></p>
<h4 id="heading-lighthouse">Lighthouse</h4>
<p>By now, I am sure you have already lost track of what is required from your PWA so it will be valid for publishing. There are so many things to take into consideration, that it is easy to lose track of what the requirements are.</p>
<p>Luckily for us, Google has created <a target="_blank" href="https://developers.google.com/web/tools/lighthouse/#devtools">Lighthouse</a>. It can be found in the Chrome Developer Tools (from Chrome version 60). It can be accessed easily by right-clicking inside the browser and selecting inspect. When the new pane opens, you will see an <strong><em>Audits</em></strong> tab at the far right corner.</p>
<p><img src="https://cdn-media-1.freecodecamp.org/images/iUXU9aPKpNWuJnHTDj6gfjsMpDewFzo4Zvy4" alt="Image" width="800" height="155" loading="lazy">
<em>The Audits Tab</em></p>
<p>Leaving the settings in this tab as they are, you can now run an audit by clicking on the “Run audits” button. This will take a minute or two, but by the end of it, you will receive an informative, graphical presentation of where your PWA ranks in respect to three properties:</p>
<ul>
<li>Performance</li>
<li>Accessibility</li>
<li>Best Practices</li>
</ul>
<p>Each property has a breakdown of where your application passed the requirements and where it didn’t. This lets you see where you need to make adjustments and where you are meeting the standard. If you are interested, you can find a breakdown of the checklist <a target="_blank" href="https://developers.google.com/web/progressive-web-apps/checklist#baseline">here</a>.</p>
<h4 id="heading-pwa-it-up">PWA it up</h4>
<p>We are at our journey’s end and hopefully you are feeling better prepared to navigate the world of PWAs. This article was inspired by the process I went through when creating one recently. You can check it out below:</p>
<p><a target="_blank" href="https://play.google.com/store/apps/details?id=com.tomerpacific.androidmenugenerator"><strong>Android Menu XML Generator - Apps on Google Play</strong></a><br><a target="_blank" href="https://play.google.com/store/apps/details?id=com.tomerpacific.androidmenugenerator">_Generate any type of menu you need for your Android application. Choose from an Options, Context or Popup menu and…_play.google.com</a></p>
 ]]>
                </content:encoded>
            </item>
        
            <item>
                <title>
                    <![CDATA[ Why Progressive Web Apps are great and and how to build one ]]>
                </title>
                <description>
                    <![CDATA[ By Ankita Masand In this tutorial, we’re going to build up the fundamentals of Progressive Web Applications (PWAs). I’ll help you understand the pain points of the traditional web and the need for something better to combat native applications. We’ll... ]]>
                </description>
                <link>https://www.freecodecamp.org/news/benefits-of-progressive-web-applications-pwas-and-how-to-build-one-a763e6424717/</link>
                <guid isPermaLink="false">66c345759972b7c5c7624e14</guid>
                
                    <category>
                        <![CDATA[ JavaScript ]]>
                    </category>
                
                    <category>
                        <![CDATA[ General Programming ]]>
                    </category>
                
                    <category>
                        <![CDATA[ PWA ]]>
                    </category>
                
                    <category>
                        <![CDATA[ React ]]>
                    </category>
                
                    <category>
                        <![CDATA[ technology ]]>
                    </category>
                
                <dc:creator>
                    <![CDATA[ freeCodeCamp ]]>
                </dc:creator>
                <pubDate>Wed, 27 Feb 2019 11:27:32 +0000</pubDate>
                <media:content url="https://cdn-media-1.freecodecamp.org/images/1*-2ZTTSgoVBaDoT9s24Bhxg.jpeg" medium="image" />
                <content:encoded>
                    <![CDATA[ <p>By Ankita Masand</p>
<p>In this tutorial, we’re going to build up the fundamentals of Progressive Web Applications (PWAs). I’ll help you understand the pain points of the traditional web and the need for something better to combat native applications. We’ll dive deeper into the components that make up a PWA — <em>Service Workers</em>, <em>IndexedDB</em>, <em>manifest.json</em> &amp; <em>Web Push Notifications</em>. And the most interesting bit — we’ll build a PWA from scratch.</p>
<h3 id="heading-how-i-got-the-idea-to-write-this-tutorial">How I Got the Idea to Write This Tutorial</h3>
<p>I was having dinner with my entire family, and a notification for a new text message popped up on my mobile phone. The message confirmed that I received a new paycheck. While this is regular news, my family gets delighted every time this happens.</p>
<p>Taking advantage of the pleasant environment, my brother exclaimed that he wants a new mobile phone. When I asked him why did he need one, he said that his phone has become very slow and gets low memory warnings every now and then. I was surprised to hear this as his phone is more advanced than mine which is still working perfectly fine.</p>
<p>To satisfy my curiosity, I checked his phone and found that he has installed more than 40 applications for his diverse needs. ?‍♀️ There were two applications for reading blogs on different categories, two of them were for getting news updates, three of them were E-Commerce apps, three for gaming, one for keeping an eye on his mutual funds and another one for handling his bank account transactions and there were a few more that he didn’t use frequently.</p>
<p>I asked him if he has ever tried going to the respective website before taking a bold step of installing the native application. He kept aside his piece of pizza and turned towards me in a mood of having a detailed conversation.</p>
<p>He began by saying that he has always visited a website first and it’s the website that forces him to download the native application by showing fat install banners. He says the experience on the web is so frustrating that it is impossible to get through even a simple task.</p>
<p>His E-Commerce applications are really great at giving him timely updates on his orders and do an amazing job of informing him of discounts by sending <em>push-notifications</em>. The user experience on native applications is simply amazing and the <em>web</em> can’t beat that. He was stern in his opinion about the web. However, he agreed that the size of the native application bloats his phone memory but he cannot do anything in that regard.</p>
<h3 id="heading-misconceptions-people-have-about-the-web">Misconceptions People Have About the Web</h3>
<p>My brother thinks exactly what most users think about the web. The traditional web is slow and ugly. Let’s take a moment and check out <a target="_blank" href="https://mobile.twitter.com/">Twitter on the mobile web</a>, popularly called Twitter Lite, and understand the significance of the word <em>traditional</em> in my last statement.</p>
<p><img src="https://cdn-media-1.freecodecamp.org/images/0*r3OZzl5HrTeyTTaw.gif" alt="Image" width="600" height="1067" loading="lazy"></p>
<p>Is the experience on par with the native app? <em>It loads instantly. There is no janky scrolling. It doesn’t look like an old traditional website</em>. You might’ve noticed a small banner at the bottom asking you to <em>Add Twitter</em> to your home screen. Is it the fancier way of urging users to install native applications? No, it isn’t. It won’t download a native application of megabytes in size. It is asking you to add Twitter Lite on your home screen. It literally means adding a shortcut to access twitter mobile web using that icon on the home screen.</p>
<p>Let’s experiment with this by clicking on <em>Add Twitter to home screen</em> and check out what the newer web has to offer. In case the banner didn’t appear in your case, please click on the three dots on the right side and choose option <em>Add to home screen</em>. Now, click on the twitter icon from your home screen. Isn’t that amazing? Oh yes, this app can also send you real-time push notifications. The web won’t feel like a lost world now. Once you opt-in for push notifications on a web application, it does a great job of engaging users by showing them all the updates.</p>
<p>There is one more important thing that is lacking in traditional old web — the ability to deal with intermittent or no internet connection. The Web behaves quite differently on 2G devices compared to on WIFI. Most of the times, it’s nothing or a loader on the screen while browsing on a 2G connection. This is frustrating to the end user.</p>
<p>The good news is, the modern web can deal with this problem too. You don’t see the dinosaur when your internet goes off. It’s a nice application shell that pops up when you are not connected to the internet. I really like the way <a target="_blank" href="https://www.trivago.in/">Trivago</a> deals with this problem, they show a nice application shell to play around an offline maze.</p>
<p><img src="https://cdn-media-1.freecodecamp.org/images/0*1q--_3sLKx0l8Hz2.png" alt="Image" width="400" height="711" loading="lazy"></p>
<p>Let’s check out one more application of this kind — <a target="_blank" href="https://app.ft.com/">Financial Times</a>. Load <a target="_blank" href="https://app.ft.com/">Financial Times</a> in your browser and now switch off your internet. Reload the page. The experience is still the same. Isn’t this something that makes the web look great? These web applications that solve the pain points of the traditional web are popularly called <strong>Progressive Web Applications</strong>.</p>
<p>In this tutorial, we’re going to explore Progressive Web Applications and also build one from scratch.</p>
<h3 id="heading-benefits-of-progressive-web-applications">Benefits of Progressive Web Applications</h3>
<p>Progressive Web Applications (PWAs) are:</p>
<h4 id="heading-fast">Fast</h4>
<p>They make good use of local caches to store static assets. Caching of static assets reduces the number of rides to the server to fetch these assets on every load. This makes for an incredible user experience similar to that of native applications. They respond quickly to user interactions.</p>
<h4 id="heading-reliable">Reliable</h4>
<p>PWAs load data almost instantly. Every fetch network request from the application goes through Service Workers (more on that later). They operate the cache (IndexedDB or any other local cache). Service Workers can send the response to a network request directly from the cache in case of intermittent or slow internet connections. PWAs work reliably even on 2G connections.</p>
<h4 id="heading-engaging">Engaging</h4>
<p>Native applications leverage the power of operating systems to show important notifications to the users and this is one of the powerful features of an application. Sending timely push notifications helps to retain users for a longer duration. PWAs make use of web push notifications to inform users of relevant updates.</p>
<p><em>Progressive Web Applications</em> is used as terminology for web applications that are fast, reliable and engaging and they provide an experience similar to that of native applications. Applications that are eligible to be called Progressive Web Applications consists of and employ the following:</p>
<p><strong>Service Workers</strong></p>
<p><em>Service Workers</em>, in simple terms, are a few lines of JavaScript code that keep running in the background. However, they go to a dormant state when they are not in use. They operate as an event-driven system. Whenever a particular event (for example, a fetch request to the server) is invoked, service workers come to life.</p>
<p>We can handle the <code>response</code> of the <code>fetch</code> event using the fetch event listener in the Service Worker. For a service worker to start doing its work of handling fetch requests and a few other events, it should be registered, installed and activated on a web application.</p>
<p><strong>IndexedDB or any other local cache</strong></p>
<p>PWAs store the static assets like JavaScript files, stylesheets, and images in the local cache for subsequent visits. Some of the PWAs make use of IndexedDB, which is basically a structured key-value pair data structure. IndexedDB is used for storing large amounts of data as compared to other client-side storage options.</p>
<p>We saw earlier the way <a target="_blank" href="https://app.ft.com/">Financial Times</a> handles the <em>no internet condition</em>. It still displays all the articles on the home page. It makes use of IndexedDB to store data from these articles.</p>
<p>Let’s see this in action. You’ll find IndexedDB in the Chrome DevTools under Applications tab. Under IndexedDB, go to the <em>Articles</em> section.</p>
<p><img src="https://cdn-media-1.freecodecamp.org/images/0*T9BnTEug5M96NXiV.png" alt="Image" width="800" height="419" loading="lazy"></p>
<p><strong>Web Push Notifications</strong></p>
<p>Service Workers also listen to a push event and have a respective push event handler that takes care of showing the push notification to the user. An application must have the user’s permission in order to show them push notifications. Once a user opts-in for receiving push notifications, the browser generates a unique token for them. The server can then communicate with the user using this unique token.</p>
<p><strong>manifest.json File</strong></p>
<p>manifest.json is typically a metadata file of an application. An application includes the manifest.json in index.html as follows</p>
<p><code>&lt;link rel="manifest" href="manifest.jso</code>n"&gt;</p>
<p>manifest.json does the job of telling the browser that the application is a PWA. It tells the browser the Name, Background Color, Theme Color, and Icons to be used for this application. It also tells the mode in which the application should be opened. For example, a <em>standalone mode</em> launches a PWA by giving a feel similar to that of a native application.</p>
<p><strong>Rich User Experience</strong></p>
<p>PWAs are known to have a rich user experience. They access the static assets directly from the cache so there is no delay in responding to user interactions.</p>
<p>Let’s build a Progressive Web Application by making use of the components listed above.</p>
<h3 id="heading-use-case-building-a-treasure-trove-of-books">Use Case — Building a Treasure Trove of Books</h3>
<p>We will be building an application called <em>BooksKeep</em>. It will help in maintaining a systematic record of the books we’ve read and also the ones that are in our pipeline. <em>No wise word learned should go in vain</em>.</p>
<p>The following features will be incorporated in this application:</p>
<ol>
<li>Displaying a list of books (Title, Author, Summary, &amp; Favorite Quotes)</li>
<li>Adding a new book to the list</li>
</ol>
<p><img src="https://cdn-media-1.freecodecamp.org/images/0*YqPX754vSe8ea8iP.gif" alt="Image" width="600" height="253" loading="lazy"></p>
<h4 id="heading-prerequisites-our-technology-stack">Prerequisites — Our Technology Stack</h4>
<ul>
<li><em>React</em> — for building the front-end</li>
<li><em>IndexedDB</em> — for storing the books records (please note, there is no back-end database)</li>
<li><em>WebPack</em> — as a development server and for bundling assets</li>
</ul>
<p>Let get going! To make things simple, I’ve created a <a target="_blank" href="https://github.com/ankita1910/bookskeep-pwa/tree/master/boilerplate">boilerplate</a> to get started with.</p>
<h4 id="heading-understanding-the-boilerplate">Understanding the Boilerplate</h4>
<p><code>package.json</code> - <code>package.json</code> contains the project dependencies. When you do <code>npm install</code>, these dependencies will be downloaded in your system. Since we're using React to build up our front-end, <code>react</code> &amp; <code>react-dom</code> libraries are included in the dependencies section.</p>
<p>In <code>devDependencies</code> object, babel presets and few webpack related plugins are included. <a target="_blank" href="https://babeljs.io/">Babel</a> is a JavaScript compiler that is used for syntax transformation, converting the next-gen JavaScript into a browser compatible version.</p>
<p>The browser doesn't understand the React syntax directly, so we're using <code>babel-preset-react</code> to convert React and JSX into JavaScript that the browser understands. We're using <a target="_blank" href="https://webpack.js.org/">WebPack</a> as a module bundler.</p>
<p><code>webpack.config.js</code> contains the configuration setup required for generating a bundle of static assets. The <code>entry</code> object in <code>module.exports</code> contains the entry point of the application, which in our case is <code>app.js</code>. Webpack generates a dependency graph using this entry point and keeps adding dependencies in the bundle starting from <code>app.js</code>. The <code>output</code> object contains the path of the output folder and <code>filename</code> generates dynamic filenames based on their value in the entry object. In our case, it will be <code>bundle.js</code> as we've mentioned bundle in the entry point.</p>
<p>Next, there are some rules to convert .js &amp; .scss specific files. These files are to be transformed with their respective loaders before adding them to the main bundle.</p>
<ul>
<li><a target="_blank" href="https://webpack.js.org/plugins/html-webpack-plugin/">HTMLWebpackPlugin</a> adds the generated output bundles in the provided <code>index.html</code> template.</li>
<li><a target="_blank" href="https://github.com/webpack-contrib/extract-text-webpack-plugin">ExtractTextPlugin</a> moves the .css modules into a separate file.</li>
<li>CopyWebpackPlugin simply copies the <code>manifest.json</code> file and <code>service-worker.js</code> file from <code>src</code> to <code>dist</code>.</li>
</ul>
<p>We will be building up <code>src</code> along this tutorial. For now, it contains <code>index.html</code> that has one div element with id <code>app</code>. <code>app.js</code> is the root component of the application. It contains simple header &amp; body components for now.</p>
<p>Let’s start building our <em>BooksKeep PWA</em>. We will be building this progressively in the following steps:</p>
<ol>
<li>Building a table component for displaying book records</li>
<li>Provision to add a new book in the table</li>
<li>Storing book records in IndexedDB</li>
<li>Adding Service Worker to cache static assets</li>
<li>Adding manifest.json</li>
</ol>
<h3 id="heading-building-a-table-component-for-displaying-book-records">Building a Table Component for Displaying Book Records</h3>
<p>We’re using <code>react-bootstrap</code> for building the UI. Let's import the Table component from <code>react-bootstrap</code>.</p>
<p>Start the server by using the command <code>npm start</code> in your terminal. Head over to <code>localhost:8080/dist/</code>. We don't have any book records yet so the table is empty. <code>BooksHeaders</code> are being imported from the constants folder. Please add <code>BooksHeaders</code> in <code>books-headers.js</code> file in the constants folder from <a target="_blank" href="https://github.com/ankita1910/bookskeep-pwa/blob/master/app/src/js/constants/books-headers.js">here</a>.</p>
<p><code>BooksHeaders</code> is simply an array of objects that we're displaying in the table. <code>getTableMarkup</code> function builds up the table headers with <code>getTableHeaders</code> &amp; body with <code>getTableData</code> functions. <code>booksData</code> maintains the state of the component. If any new book record is to be added, it should be pushed into <code>booksData</code> array.</p>
<h3 id="heading-provision-to-add-a-new-book-in-the-table">Provision to Add a New Book in the Table</h3>
<p>Let’s take it further and add our first book record in the table. All we have to add is to import the <code>BookForm</code> component from the base folder and provide it a <code>onSubmit</code> prop. <code>onSubmit</code> prop accepts a function that will be called when the user clicks on the submit button in the form, and that will give us the details of the new book. Once you're done with that, your Body Component should look like <a target="_blank" href="https://github.com/ankita1910/bookskeep-pwa/blob/master/mid-steps/body-1.js">this</a>.</p>
<p>Here’s the <code>BookForm</code> component:</p>
<p><code>FieldGroup</code> is just a wrapper for labeled inputs. Please put <a target="_blank" href="https://github.com/ankita1910/bookskeep-pwa/blob/master/app/src/js/utils/field-group.js">this</a> inside the <code>field-group.js</code> file in the <code>utils</code> folder. <code>BookForm</code> component maintains its state in the <code>formData</code> object. Whenever a user enters name, author or summary, it gets saved in the component state. The submit button passes the component state to the parent Body component, which then adds it to its state - <code>booksData</code> array.</p>
<p>After adding a book record, you'll see that your table is now populated with that record. But when you refresh the page, all of this is gone. We've got to fix this.</p>
<h3 id="heading-storing-book-records-in-indexeddb">Storing Book Records in IndexedDB</h3>
<p><em>IndexedDB</em> is a structured client-side storage database. The records in IndexedDB are stored as key-value pairs. We’ll be saving the book records in IndexedDB. IndexedDB provides APIs for adding, deleting and updating the records in a database. Let’s explore these APIs by creating a wrapper in the <code>indexeddb.js</code> file in the <code>utils</code> folder.</p>
<p>Operations performed on IndexedDB are asynchronous in nature. So, the IndexedDB APIs provide appropriate hooks for success and error events.</p>
<p>First, we’ll have to create our database. Let’s write a <code>initialize</code> function that will handle the initialization tasks:</p>
<p>In the above code snippet, <code>BooksKeep</code> is the name of the IndexedDB database and <code>books</code> is an <em>ObjectStore</em>. <em>ObjectStore</em> is analogous to a table in SQL. The statement <code>idb.open(DB, 1)</code> is an asynchronous request to open IndexedDB database <code>BooksKeep</code>, and the second parameter 1 signifies the version of the database. The request variable is of type <code>[IDBOpenDBRequest](https://developer.mozilla.org/en-US/docs/Web/API/IDBOpenDBRequest)</code>.</p>
<p>We've defined <code>onsuccess</code>, <code>onerror</code> and <code>onupgradeneeded</code> functions on the request object to be called at the respective events. For example, <code>onsuccess</code> callback would be called when the database is opened successfully and in <code>onsuccess</code> method, we're caching the instance of the <code>BooksKeep</code> database. <code>onupgradeneeded</code> method is invoked whenever there is a change in the version of the database.</p>
<p>Currently, with version 1, we've added only one ObjectStore called as <code>books</code>. Let's say, at a later stage, when our application grows we decide to add one more ObjectStore. We'll have to upgrade the version of our database to 2 and add the schema of this new ObjectStore in <code>onupgradeneeded</code> method.</p>
<p>We will be writing three important methods — <em>get</em>, <em>update</em> &amp; <em>delete —</em> in our IndexedDB wrapper. The general idea for performing any of these operations is to first get the instance of the store, wrap the operation in a transaction, and then write success and error event handlers for the respective asynchronous requests. A transaction is simply a wrapper around an operation to ensure data integrity. If any of the actions in a transaction fails, then no action is performed on the database.</p>
<p>For example, our put or update method will look something like this:</p>
<p><code>update</code> method takes three parameters:</p>
<p><code>type</code> is the name of the objectStore, <code>data</code> is the book record that we intend to add/update in our objectStore, and <code>callback</code> is of type function that would be called after successfully adding <code>data</code> in the objectStore.</p>
<p><code>transaction</code> is defined on the <code>[IDBOpenDBRequest](https://developer.mozilla.org/en-US/docs/Web/API/IDBOpenDBRequest)</code> instance and it takes the name of the objectStore and the mode with which the operation is to be performed. In this case, the mode is <code>readwrite</code> since we're writing to the objectStore.</p>
<p>As mentioned previously, IndexedDB accepts data in the form of key-value pairs. We're using timestamp to generate a unique identifier for a particular record. <code>store.put(data)</code> asynchronously adds book records into the <code>books</code> objectStore. On the same lines, I've added get &amp; delete methods in our wrapper. Please check the complete code of IndexedDB wrapper <a target="_blank" href="https://github.com/ankita1910/bookskeep-pwa/blob/master/app/src/js/utils/indexeddb.js">here</a>.</p>
<p>Now that our IndexedDB wrapper is all set, it’s time to use the add/update function from our wrapper whenever a user tries to add a new book record. Let’s modify our Body Component to accommodate these changes.</p>
<p>First import <code>IndexedDbWrapper</code> in the Body component. We will be calling the <code>initialize</code> function of <code>IndexedDbWrapper</code> in <code>componentDidMount</code>. The <code>initialize</code> method takes the callback as <code>initializeDB</code> function, which is defined in the Body Component. <code>initializeDB</code> does the work of setting up the initial state of our application by fetching the stored books records from IndexedDB.</p>
<p>One last thing to do with <code>IndexedDbWrapper</code> is to call its <code>update</code> method on submission of a book record. We've to modify the <code>onSubmit</code> method of the Body component as follows:</p>
<p>Now, the new record will be first added to IndexedDB, and once that is done successfully, we’re updating the state of the component. Try adding a new book record and reloading the page. You will still see your book record in the table. Here’s where it is coming from!</p>
<p><img src="https://cdn-media-1.freecodecamp.org/images/0*dnhPB5See2bfAe74.png" alt="Image" width="800" height="357" loading="lazy"></p>
<p>Let’s add one record and refresh the page. Data is preserved and that’s exactly what we wanted. We’ve built up a means to fetch data directly on the client-side. We’re getting closer to our goal of building a Progressive Web Application.</p>
<h3 id="heading-adding-service-worker-to-cache-static-assets">Adding Service Worker to Cache Static Assets</h3>
<p>The next step is to leverage the power of Service Workers by fetching static assets from the cache. A service worker first has to be registered on a web page.</p>
<p><em>Service Worker Registration</em></p>
<p><code>initializeSW</code> function is defined in the Body component, and we'll call it in <code>componentDidMount</code> lifecycle hook. <code>serviceWorker</code> is defined on <a target="_blank" href="https://developer.mozilla.org/en-US/docs/Web/API/Navigator">navigator</a>. According to MDN,</p>
<blockquote>
<p>the Navigator interface represents the state and the identity of the user agent. It allows scripts to query it and to register themselves to carry on some activities.</p>
</blockquote>
<p>A Service Worker is registered using the <code>register</code> method defined on <code>navigator.serviceWorker</code> object. The <code>register</code> method takes the URL of the service worker file. It returns a <code>Promise</code> which resolves when the service worker is registered successfully on the webpage. Once this is done, you'll see a success message in the console. By default, service workers can intercept all the fetch requests coming from the web page.</p>
<p><code>register</code> method also takes an optional second parameter, which defines the <code>scope</code> of the service worker.</p>
<pre><code>navigator.serviceWorker.register(<span class="hljs-string">'./service-worker.js'</span>, { <span class="hljs-attr">scope</span>: <span class="hljs-string">'/products'</span> })
</code></pre><p>The above service worker will intercept only <code>/products/*</code> requests. So, something like <code>/payments</code> is not intercepted by the above service worker.</p>
<p>As said earlier, Service Workers operate as an event-driven system. After successful registration, an <code>install</code> event is triggered. We can make use of the install event handler for initialization tasks. In our case, we will be setting up our cache for storing static assets.</p>
<p>Here’s the install event handler:</p>
<p><code>event.waitUntil</code> makes sure that the service worker is active while the URLS are getting added into the cache.</p>
<p>The service worker hasn’t yet started doing its magic. After getting installed successfully, an <code>activate</code> event is triggered and this is a good place to clear off old unused caches. Let's do our bit:</p>
<p><code>activate</code> event handler takes care of deleting all the caches except <code>bookskeep-cache</code>. When a web page makes a network request to the server, the fetch event of the service worker is triggered. So, if we were to manipulate or modify the response to be sent for a particular request, we'll have to do this in the fetch event handler.</p>
<p><code>event.respondWith</code> method lets us send a modified response back to the client. It returns a Promise that resolves to a valid response. <code>cache.match</code> checks if the request is a valid resource for caching (if you remember, we've added a few specific URLs to <code>urlsToCache</code> variable in the install event handler).</p>
<p>If the response to that request is present in the cache, we send it directly to the client otherwise, we request that resource from the server, put it into the cache for subsequent hits, and send it to the client.</p>
<p><img src="https://cdn-media-1.freecodecamp.org/images/0*srtIpMIoAkzVZZqt.png" alt="Image" width="800" height="295" loading="lazy"></p>
<p><a target="_blank" href="https://github.com/ankita1910/bookskeep-pwa/blob/master/mid-steps/service-worker-1.js">Here’s</a> the service worker file with the three event handlers explained above.</p>
<h3 id="heading-adding-manifestjson">Adding manifest.json</h3>
<p><code>short_name</code> is used on the home screen as the name of the application. In case <code>short_name</code> is not provided, then the <code>name</code> property is used in its place. <code>icons</code> show up as a home screen icon for the application in the app launcher and on the splash screen. <code>start_url</code> tells the browser about the starting page of the app. A user will be directed to this URL when the app is launched. <code>standalone</code> as the display property of the app gives it the look &amp; feel of a native application. The application runs in its own window and hides some of the browser specific elements like URL bar. <code>background_color</code> sets the color of the splash screen when the application is first launched and <code>theme_color</code> tells about the color of the toolbar.</p>
<p>This is it. We’ve set up our <em>BooksKeep PWA</em>. Let’s have a quick recap of the things we learned in this tutorial:</p>
<ol>
<li>The traditional web lacks some of the important features that native applications provide out of the box. Progressive Web Applications help in improving the user experience on the web tremendously. They are fast, reliable, and engaging and provide an experience similar to that of native applications.</li>
<li>PWAs make use of Service Workers, IndexedDB (or any other local cache), manifest.json, and Web Push Notifications.</li>
<li>Service Workers operate as an event-driven system and listen for fetch &amp; push events. <code>fetch</code> event lets us send the response to a network request directly from the cache in case of slow or intermittent connections. <code>push</code> event lets us show push-notifications to the user and helps in engaging user by apprising them of timely updates.</li>
<li>IndexedDB is a key-value structure. It helps in storing a massive amount of data on the client-side. <code>manifest.json</code> informs the browser about some of the important properties of an application.</li>
<li>We learned how to get started building a Progressive Web Application.</li>
</ol>
<p>This was a quick introduction to Progressive Web Applications. If you want to explore more, here are some of the resources:</p>
<ol>
<li><a target="_blank" href="https://www.smashingmagazine.com/2018/11/guide-pwa-progressive-web-applications/">An Extensive Guide To Progressive Web Applications</a></li>
<li>Check out my <a target="_blank" href="https://github.com/ankita1910/bookskeep">BooksKeep</a> application on GitHub. I’ve added a few more features like updating book records, adding quotes &amp; support for web push notifications. I’ll keep adding more!</li>
<li><a target="_blank" href="https://hackernoon.com/service-workers-62a7b14aa63a">Service Workers</a></li>
</ol>
<p><em>Please let me know if you found this tutorial to be helpful and share it with whomever you think might benefit from it.</em></p>
<p><em>Originally published at <a target="_blank" href="https://hashnode.com/post/benefits-of-progressive-web-applications-pwas-and-how-to-build-one-cjqry4q0c00qo8ms1ckbv9xnc">hashnode.com</a>.</em></p>
 ]]>
                </content:encoded>
            </item>
        
            <item>
                <title>
                    <![CDATA[ How to Implement a PWA and Barba.js into internet kiosks ]]>
                </title>
                <description>
                    <![CDATA[ By Nino Mihovilić The project we’ll describe here is an interactive internet kiosk that’s used as an extension for the LikeUs mobile application. LikeUs is a mobile app that makes it easy for users to choose a place to go out, have coffee or listen t... ]]>
                </description>
                <link>https://www.freecodecamp.org/news/how-to-implement-a-pwa-and-barba-js-into-internet-kiosks-854d4895fd04/</link>
                <guid isPermaLink="false">66c352ae0107ba195e79f727</guid>
                
                    <category>
                        <![CDATA[ Android ]]>
                    </category>
                
                    <category>
                        <![CDATA[ JavaScript ]]>
                    </category>
                
                    <category>
                        <![CDATA[ PWA ]]>
                    </category>
                
                    <category>
                        <![CDATA[ tech  ]]>
                    </category>
                
                    <category>
                        <![CDATA[ Web Development ]]>
                    </category>
                
                <dc:creator>
                    <![CDATA[ freeCodeCamp ]]>
                </dc:creator>
                <pubDate>Wed, 09 Jan 2019 18:12:52 +0000</pubDate>
                <media:content url="https://cdn-media-1.freecodecamp.org/images/1*7QocFarKH2FtHu64nQA-JA.jpeg" medium="image" />
                <content:encoded>
                    <![CDATA[ <p>By Nino Mihovilić</p>
<p>The project we’ll describe here is an interactive internet kiosk that’s used as an extension for the <a target="_blank" href="https://likeus.hr/">LikeUs mobile application</a>. LikeUs is a mobile app that makes it easy for users to choose a place to go out, have coffee or listen to a concert. Because Zagreb’s Tkalčićeva street is a place where quite a lot of young people hang out, we decided it was the right place for our offline promotion of the LikeUs app.</p>
<h4 id="heading-implementing-the-kiosk-mode">Implementing the Kiosk Mode</h4>
<p>One of the first challenges we faced was the implementation of the kiosk browser mode on our device. It was an Android Box and we used Chrome as a web browser that will run the app. Kiosk browser mode is a mode in which you run the application in fullscreen and without any browser user interface or, in our case, without any Android user interface.</p>
<p>The intent is to prevent users from running anything other than the browser-based content. As there are hundreds of kiosk mode apps, we decided to use one of the available applications instead of building one from the ground up.</p>
<p>After some research, we decided to use the <a target="_blank" href="https://www.android-kiosk.com/">Kiosk Browser Lockdown</a> app. It has all of the features we were looking for:</p>
<ul>
<li>Locking the device to a single URL</li>
<li>Hiding toolbar options</li>
<li>Hiding the notifications screen</li>
<li>Hiding the Android user interface</li>
</ul>
<p>The next step was to test the PWA in the Android environment and in the Kiosk Browser app. That’s when we discovered that things wouldn’t go as smooth as we planned!</p>
<p>The first problem we encountered was on the frontend side — the final website seemed like an upscaled version of the initial design, and this was due to some screen limitations and a different rendering environment. As the deadline was approaching, we didn’t have enough time to tweak every single item in CSS to fit the initial design outlines, so we decided to downscale the entire document.</p>
<p>It was a reasonable approach considering all the inputs we had. Having to test everything once more was a big downside, but we had to be sure that everything will work in this context.</p>
<p>The second problem was that external scripts like Google Maps weren’t loading in the Kiosk Browser app with the PWA, so we did a little tweak. We started the Kiosk Browser app, which removed the Android user interface, and then exited the Kiosk Browser app and started the PWA outside of the Kiosk Browser app. This way we managed to remove the Android interface with all external scripts loading as they were supposed to.</p>
<p><img src="https://cdn-media-1.freecodecamp.org/images/HpUfeFTjyE05czMLe-X7GgCVO0uacIPiAjrE" alt="Image" width="1200" height="900" loading="lazy"></p>
<h4 id="heading-developing-a-progressive-web-app">Developing a Progressive Web App</h4>
<p>After going through the project brief and specifications, the first thing that came to our mind was… we should do a PWA (Progressive Web App). A Progressive Web App is an application that provides similar capabilities and functionalities as native mobile apps:</p>
<ul>
<li>Service workers allow apps to show content nearly instantly and reliably because they cache every request</li>
<li>There is an ability to add the app to the home screen like a normal native app</li>
<li>Push notifications can be implemented for multipurpose usages</li>
<li>The app is fast and smooth</li>
<li>It uses HTTPS and is easy to implement.</li>
</ul>
<p>After evaluating client’s requests, all features of the PWA were meeting our requests.</p>
<p>Our requests were:</p>
<ul>
<li>To build an app that could be used on an interactive screen</li>
<li>The app should use an existing API we built for our LikeUs mobile app</li>
<li>The device used will be an Android Box</li>
<li>Internet access would be restricted because the app would be connected to a public network (this would change later)</li>
<li>The app should have an additional section for banners and a banner management system</li>
</ul>
<p>We could build a web app with our existing API without having to implement additional functionalities, and we could also build a simple CMS (content management system) for banner management and push notifications for content reloading. As internet access would be restricted and unstable, we could use the PWA feature to cache pages and serve them even when the app is offline.</p>
<p>Be sure to check out this <a target="_blank" href="https://medium.com/@jewbre/service-workers-6a5c13c9a123">in-depth tutorial</a> and explanation for Service Workers.</p>
<h4 id="heading-tweaking-the-banner-management-system">Tweaking the Banner Management System</h4>
<p>The app is divided into two sections. The top section is the banner section, and the bottom part is the main section divided into tabs.</p>
<p>We have two types of banners — Youtube videos and images. Since banners can be changed, we needed to develop a CMS. We developed a simple CMS in which the client can enter Youtube videos and images into a slider.</p>
<p>The problem we encountered here was refreshing the app to reload new banner content. You see, because the app was using Barba.js, it was never refreshing. To make it work, we used a cool feature of our PWA — push notifications. Push notifications are a feature that uses the Notifications API and the Push API to send messages from the server to the client.</p>
<p>How did push notifications help solve our content reloading problem? The solution is pretty simple and straightforward. When the user changes banner content in the CMS, we send a push to the PWA and then the PWA refreshes two times. The PWA needs to be refreshed two times to delete the cache and to reload new content.</p>
<h4 id="heading-dealing-with-external-obstacles">Dealing with External Obstacles</h4>
<p>Internet kiosks are often placed in outdoor environments where internet connection is sometimes unstable and slow. When the internet connection is public and in a pretty crowded street, you face a lot of problems when using real-time communication and external APIs.</p>
<p>A common “hacky” approach is to extend the delay time and hope that everything works well. Even though this is not the preferred way, it can serve as a backup if everything else fails.</p>
<p>Google Maps was one of the external APIs that gave us a lot of headaches. We had to reload and add new pins, but on a slow connection, this was sometimes impossible.</p>
<p><img src="https://cdn-media-1.freecodecamp.org/images/hyvg705AuzUl8rybWXHDDk9stgbs7vsnTmec" alt="Image" width="1200" height="900" loading="lazy"></p>
<h4 id="heading-balancing-between-fixed-and-dynamic-content">Balancing Between Fixed and Dynamic Content</h4>
<p>Optimization is not only applied in the realm of advanced caching techniques and content delivery networks. Smart layout placement and understanding elements that can be “pushed out” of the page reloading flow can reduce the number of requests and speed up the entire navigation flow.</p>
<p>The advertising content in the kiosk was hosted on Youtube — it was a video slider that was repeating through all pages. Below that, we had the main content with inline navigation. When selecting different navigation items, the default browser behaviour was to reload the entire page, including that fixed advertising area. It’s a performance nightmare, especially when having external scripts such as Youtube API.</p>
<p>The question here is — how to reload only one specific part of the page? Well, there will be no browser reloading and the only thing that can be done is changing content in the background without leaving the page.</p>
<p>Because of the implemented analytics, we had to update the URL accordingly. We did this by using the PJAX (Push State Ajax) technology. This technology allows content prefetching and swapping target DOM nodes in the background.</p>
<p>To avoid content flashing, create a simple fade transition that fires when content changes. As it’s time-consuming to manually manage all states of content swapping, we used an external library called Barba.js. This library allows advanced transition management and is compatible with all animation frameworks.</p>
<p><img src="https://cdn-media-1.freecodecamp.org/images/yzC0x5NH-VvltNXi-nOOXA871NO2gcJnlcQy" alt="Image" width="1200" height="900" loading="lazy"></p>
<p>Barba.js has internal state caching that can be used to leverage browser caching and optimize loading time. Barba cache is a global Javascript object where every value is a Promise that has to be resolved.</p>
<h4 id="heading-implementing-analytics-and-virtual-pageviews">Implementing Analytics and Virtual Pageviews</h4>
<p>We wanted to measure user interaction and page views. Because we are using Barba.js, this is basically a one-page app without page reloads, so the trick to measure page views in this type of app is to use Virtual Pageviews. They are page hits sent to Google Analytics, without actually reloading the page.</p>
<p>The first step is to include the <a target="_blank" href="https://developers.google.com/tag-manager/quickstart">Google Tag manager</a> code, and then to actually send Virtual Pageviews to the data layer. We can do it with the next snippet:</p>
<pre><code>dataLayer.push({ <span class="hljs-string">'event'</span>: <span class="hljs-string">'VirtualPageview'</span>, <span class="hljs-string">'virtualPageURL'</span>: currentUrl, <span class="hljs-string">'virtualPageTitle'</span>: title });
</code></pre><p>This snippet needs to be called on each new page. On each user interaction that opens a new “page”, we call this snippet that sends the page URL and the page title to Google Analytics. This way we can track pageviews in single page apps that use Barba.js or any other PJAX technology.</p>
<p><img src="https://cdn-media-1.freecodecamp.org/images/pkJtXRjiySB4CfjP6v8gC1ujMJZFTSGe2qjI" alt="Image" width="1200" height="900" loading="lazy"></p>
<h4 id="heading-to-conclude">To Conclude</h4>
<p>When working in a specific environment, sometimes the “by the book” solution is not your only solution. There is usually an opportunity to innovate and use some common tools and libraries in a not so standard environment with a specific set of challenges.</p>
<p><em>Originally published at <a target="_blank" href="https://www.bornfight.com/blog/how-to-implement-pwa-and-barba-js-into-internet-kiosks/">www.bornfight.com</a>.</em></p>
 ]]>
                </content:encoded>
            </item>
        
            <item>
                <title>
                    <![CDATA[ Why Progressive Web Apps Are The Future Of Web Development ]]>
                </title>
                <description>
                    <![CDATA[ By Tushar Agrawal “The key is to embrace disruption and change early. Don’t react to it decades later. You can’t fight innovation.” — Ryan Kavanaugh Lately, there’s been a lot of buzz around PWAs with many claiming it to be future of web developmen... ]]>
                </description>
                <link>https://www.freecodecamp.org/news/why-progressive-web-apps-are-the-future-of-web-development-13db7dd5f640/</link>
                <guid isPermaLink="false">66c36705b737bb2ce7073219</guid>
                
                    <category>
                        <![CDATA[ Apps ]]>
                    </category>
                
                    <category>
                        <![CDATA[ development ]]>
                    </category>
                
                    <category>
                        <![CDATA[ PWA ]]>
                    </category>
                
                    <category>
                        <![CDATA[ web ]]>
                    </category>
                
                    <category>
                        <![CDATA[ Web Development ]]>
                    </category>
                
                <dc:creator>
                    <![CDATA[ freeCodeCamp ]]>
                </dc:creator>
                <pubDate>Tue, 05 Dec 2017 18:20:16 +0000</pubDate>
                <media:content url="https://cdn-media-1.freecodecamp.org/images/1*oc4pOoEeR_QMrCA6LkF5Kw.jpeg" medium="image" />
                <content:encoded>
                    <![CDATA[ <p>By Tushar Agrawal</p>
<blockquote>
<p>“The key is to embrace disruption and change early. Don’t react to it decades later. You can’t fight innovation.” — Ryan Kavanaugh</p>
</blockquote>
<p>Lately, there’s been a lot of buzz around PWAs with many claiming it to be future of web development, especially in terms of mobile devices. At its core, a Progressive Web App (PWA) is simply a web application that uses modern web techniques to deliver a native app-like experience to users. These are web applications with progressive enhancement to implement features like caching, background sync, and push notifications.</p>
<p>Even though <a target="_blank" href="https://developers.google.com/web/progressive-web-apps/">PWAs</a> have been around for more than two years now, the response is quite underwhelming. Few big players have adopted this philosophy but most haven’t actually embraced it very much. Chrome and Mozilla are perhaps the best browsers to test out your PWAs as Apple is yet to get into this stuff.</p>
<h3 id="heading-pwa-is-it-really-good">PWA — Is it really good ?</h3>
<p>On one hand, we have native apps that are undoubtedly fast and efficient in most of the cases. On the other hand, there are websites that are kinda slow and with the connectivity issues, it only gets worse.</p>
<p>Accelerated Mobile Pages Project (<a target="_blank" href="https://www.ampproject.org/">AMP</a>) spearheaded by Twitter and Google was launched in 2016 to solve those slow connection issues only. PWAs work flawlessly in all the possible scenarios. With a good connection, there is never a problem. The problem is when we have no connection and we are greeted with the error page.</p>
<p><img src="https://cdn-media-1.freecodecamp.org/images/1*0DOKUYA7bHE9COGpJw5q0Q.png" alt="Image" width="620" height="224" loading="lazy"></p>
<p>But this can become most annoying if we have a slow connection. The page seems to be loading and all we see is a blank screen. We just wait, wait and wait but the page never seems to load. This is where PWA comes to our rescue. The best part about PWAs — you get the best user experience possible in slow connectivity as well as no connectivity (yep , you read it right..).</p>
<h3 id="heading-why-it-makes-sense-to-use-pwa">Why it makes sense to use PWA</h3>
<p>According to a study, an average user spends 80% of his total time on apps on only three of his apps (For me its Chrome, Quora and Medium).</p>
<p>The other apps just sit idle for most of this time consuming a precious portion of the memory. Moreover, it costs around ten times to develop an app rather than creating a website for the same. The cost can get much higher if you plan to develop and maintain separate code bases for different platforms like Android, iOS and the web.</p>
<h3 id="heading-native-app-features-that-pwas-can-use">Native App features that PWAs can use</h3>
<ul>
<li>Push notifications</li>
<li>Full Screen</li>
<li>Offline working</li>
<li>Splash screen is supported giving it a more app like feel</li>
</ul>
<p>PWAs can make use of many more such features. The above points are only to give you a hint of what PWAs are capable of. However, there are some traditional features that only native apps enjoy as of now.</p>
<h3 id="heading-native-app-features-that-pwas-cant-use-as-of-now">Native App features that PWAs can’t use as of now</h3>
<ul>
<li>No or highly restrictive access to different hardware sensors</li>
<li>Alarms</li>
<li>Phonebook Access</li>
<li>Modfiying System Settings</li>
</ul>
<p>PWAs are evolving quite fast and we can hope to see these features come to action pretty soon.</p>
<h3 id="heading-two-major-components-of-a-pwa">Two Major Components of a PWA</h3>
<p><strong>App Manifest</strong><br>It’s a JSON file that defines an app icon, how to launch the app (standalone, full-screen, in the browser etc), and any such related information. It’s located in the root of your app. A link to this file is required on each page that has to be rendered.</p>
<p>It is added in the head section of the HTML page:<br><code>&lt;link rel=”manifest" href="/manifest.jso</code>n"&gt;</p>
<p><strong>Service Worker</strong><br>Service worker is where most of the magic of happens. Its nothing but JavaScript code that acts as programmable proxies solely responsible for intercepting and responding to network requests. Since it acts as a proxy and can be easily programmable, the application must be served over HTTPS to keep the data secure.</p>
<p>Its worth noting that the service worker caches the actual response, including all HTTP headers, rather than just the response data. This means that your application can simply make network requests and process the response without any specific code to handle the cache.</p>
<h3 id="heading-how-do-i-get-started">How do I get started?</h3>
<p>The best thing about getting started is that it’s quite easier than it seems. In fact, it’s very much possible to take an existing site and convert into a PWA. I highly suggest you watch this if you intend to develop a PWA.</p>
<h4 id="heading-thanks-for-reading-if-you-liked-it-please-support-by-clapping-and-sharing-the-post">Thanks for reading! If you liked it, please support by clapping and sharing the post.</h4>
 ]]>
                </content:encoded>
            </item>
        
    </channel>
</rss>
