Tutorial: How to build a PWA in Adobe Experience Manager

New Here ,
Jan 17, 2020

Copy link to clipboard

Copied

Hi Adobe community,

 

I have been working on setting up a Progressive Web App (PWA) in AEM recently and since I didn't found any documentation on this process, I thought it might be useful for others to read about how I did it....not claiming it is the perfect way to do so, but it is definitely working 🙂

 

--------------------------------------------------

Setup

The following post describes how to build a PWA in AEM.

 

Prerequisites:

  • Working instance of “Adobe Experience Manager 6.4.0”.
  • Installed demo webapp “weretail” (this is part of AEM default installation).
  • SSL setup for host completed, i.e. the “weretail” webapp can be reached via https.

 

Please make sure that you can access the demo webapp “weretail” and the admin interface of AEM accordingly. For the rest of this post I assume the following URLs:

 

Adjust the URLs as needed for your own system.

 

Start

Open the “weretail” webapp in the browser. https://example.com:8443/content/we-retail/us/en/experience/arctic-surfing-in-lofoten.html

 

It should look like this:


start.png

Test the website with Lighthouse (e.g. use the built-in “Audit” function in Chrome’s DevTools). The “weretail” webapp does not pass the “PWA” audit. 

 

no_pwa.png

 

Turn “weretail” webapp into a PWA

Progressive Web Apps are user experiences that have the reach of the web, and are:

 

  • Reliable - Load instantly and never show the downasaur, even in uncertain network conditions.
  • Fast - Respond quickly to user interactions with silky smooth animations and no janky scrolling.
  • Engaging - Feel like a natural app on the device, with an immersive user experience.

 

From a technical point of view every website can be turned into a PWA if the following additional resources are added:

  1. The Service Worker, a JS script that your browser runs in the background, separate from a web page, opening the door to features that don't need a web page or user interaction.
  2. The Web App Manifest, a simple JSON file that tells the browser about your web application and how it should behave when 'installed' on the user's mobile device or desktop. 
  3. The “offline” webpage, a simple HTML page that will be shown to the user when no network connection is available.
  4. A set of icons, these icons are used in places like the home screen, app launcher, task switcher, splash screen, etc. 

 

The Service Worker

In the following example we are using a very simple Service Worker, based on Workbox.js, that offers the following PWA features:

  • Basic caching
  • Basic offline mode
  • Push Notifications

 

Content of the file “sw.js”:

 

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

const offlinePage = '/content/dam/we-retail/en/experiences/48-hours-of-wilderness/offline.html';

const offlineImage = '/content/dam/we-retail/en/experiences/48-hours-of-wilderness/offline.svg';

/**
 * Pages to precache
 */
workbox.precaching.precacheAndRoute([
  offlinePage,
  offlineImage,
]);

/**
* Enable navigation preload.
*/

workbox.navigationPreload.enable();

/**
 * Basic caching for HTML pages, CSS+JS (caching max. 1 week).
 */
workbox.routing.registerRoute(
  /\.(?:html|htm|js|css)$/,
  new workbox.strategies.NetworkFirst({
    networkTimeoutSeconds: 5,
    cacheName: 'html_css_js',
    plugins: [
      new workbox.expiration.Plugin({
        // Cache files for a week
        maxAgeSeconds: 7 * 24 * 60 * 60,
        // Only cache 30 files.
        maxEntries: 30,
      }),
    ],
  })
);

/**
 * Basic caching for JSON data (caching max. 1 day).
 */
workbox.routing.registerRoute(
  /\.json$/,
  new workbox.strategies.NetworkFirst({
    networkTimeoutSeconds: 30,
    cacheName: 'json',
    plugins: [
      new workbox.expiration.Plugin({
        // Cache pages for a day
        maxAgeSeconds: 24 * 60 * 60,
        // Only cache 10 files.
        maxEntries: 10,
      }),
    ],
  })
);

/**
 * Basic caching for max. 60 images (caching max. 30 days).
 */
workbox.routing.registerRoute(
  /\.(?:png|gif|jpg|jpeg|svg)$/,
  new workbox.strategies.CacheFirst({
    cacheName: 'images',
    plugins: [
      new workbox.expiration.Plugin({
        // Cache images for 30 days
        maxAgeSeconds: 30 * 24 * 60 * 60,
        // Only cache 60 images.
        maxEntries: 60,
      }),
    ],
  })
);

/**
 * Use a stale-while-revalidate strategy for all other requests.
 */
workbox.routing.setDefaultHandler(
  new workbox.strategies.StaleWhileRevalidate()
);

/**
 * Basic "offline page" support
 */
workbox.routing.setCatchHandler(({event}) => {
  switch (event.request.destination) {
    case 'document':
      // Only provide fallback for navigational requests
      if (event.request.mode === 'navigate')
        return caches.match(offlinePage);
      else
        return Response.error();
      break;
    case 'image':
      return caches.match(offlineImage);
      break;
    default:
      // If we don't have a fallback, just return an error response.
      return Response.error();
  }
});

/**
 * Basic "Push notification" functionality.
 */
self.addEventListener('push', (event) => {
  const title = 'Example push notification';
  const options = {
    body: event.data.text()
  };
  event.waitUntil(self.registration.showNotification(title, options));
});

 

 

 

The Web App Manifest

The Web App Manifest is needed for the “Add to homescreen” feature of a PWA. A very simple manifest.json file could look like this:

 

{
  "short_name": "weretail",
  "name": "WE.Retail demo webapp",
  "icons": [
    {
      "src": "/content/dam/we-retail/en/experiences/48-hours-of-wilderness/icons-192.png",
      "type": "image/png",
      "sizes": "192x192"
    },
    {
      "src": "/content/dam/we-retail/en/experiences/48-hours-of-wilderness/icons-512.png",
      "type": "image/png",
      "sizes": "512x512"
    }
  ],
  "start_url": "/content/we-retail/us/en/experience/arctic-surfing-in-lofoten.html",
  "background_color": "#3367D6",
  "display": "standalone",
  "scope": "/content/we-retail",
  "theme_color": "#3367D6"
}

 



The “offline” webpage

The “offline” webpage is shown to the user if the browser has no connection to the network. A very simple example webpage could look like this:

 

<!DOCTYPE html>
<html>
 <head>
  <meta charset="UTF-8">
  <title>Offline Page</title>
  <script>
      window.addEventListener('online', function(e) {
        location.reload();
      }, false);
  </script>
 </head>
 <body>
   <div style="text-align:center; margin-top:40px;">
     <img src="/content/dam/we-retail/en/experiences/48-hours-of-wilderness/offline.svg" height="80" />
     <p>You don't have an internet connection.</p>
     <p>Please check your network connection and try again.</p>
   <div>
  </body>
</html>

 

 

A set of icons

These icons are used in places like the home screen, app launcher, task switcher, splash screen, etc. At least two icons with a resolution of 192x192 pixel and 512x512 pixel are needed for a PWA.

 

Adding everything to AEM

The static resources like manifest.json, icons and the offline page could be uploaded via the “Digital Assets Manager”, e.g. 

 

1.png

The Service Worker should live under “/content/we-retail”, if the Service Worker should control the entire webapp. 

The Service Worker can technically be placed anywhere, but if the Service Worker is not living in the root of the website, then the PWA is limited to work only for a (sub)part of the website.

 

More details about the “scope” of the Service Worker can be found here: https://developers.google.com/web/ilt/pwa/introduction-to-service-worker#registration_and_scope


2.png

 

The last step is to include the sw.js file + manifest.json file into the webapp. In this very simple demo we just modify the file “/apps/weretail/components/structure/page/customheaderlibs.html”.


3.png

 

The following lines of code should be added to the file “customheaderlibs.html”:

 

<link rel="manifest" crossorigin="use-credentials" href="/content/dam/we-retail/en/experiences/48-hours-of-wilderness/manifest.json">

<link rel="apple-touch-icon" href="/apps/weretail/components/structure/page/clientlib/resources/apple-touch-icon.png">

<meta name="theme-color" content="black">
<meta name="viewport" content="width=device-width, initial-scale=1">

<script> 
  if ('serviceWorker' in navigator) 
  {
    window.addEventListener('load', () => {
      navigator.serviceWorker.register('/content/we-retail/sw.js');   
    }); 
  }
</script>

 



Save all changes and reload the webapp. If everything is correct, then the webapp has been turned into a PWA now. Verify the webapp again with Lighthouse. It should now look like this:

 

PWA.png

TOPICS
Community Information

Views

2.0K

Likes

Translate

Translate

Report

Report
Reply
Community Guidelines
Be kind and respectful, give credit to the original source of content, and search for duplicates before posting. Learn more
Most Valuable Participant ,
Jan 17, 2020

Copy link to clipboard

Copied

There is no forum for that product here
https://experienceleaguecommunities.adobe.com/ 

Likes

Translate

Translate

Report

Report
Reply
Community Guidelines
Be kind and respectful, give credit to the original source of content, and search for duplicates before posting. Learn more