Progressive Web Applications (PWAs) have been one of the hottest topics in the tech community. They bring the best of both worlds — the reach of the web with the user experience of native apps.

A few months ago, WWWID launched a challenge: build a web app that can load and become usable in under 5 seconds on a slow 3G connection. The task was to create a simple RSS reader app based on WWWID’s Medium publication.

In this article, I’ll walk through the methods I used to build a PWA for that challenge using React JS (powered by Create React App). CRA comes pre-configured with a service worker, which is a great starting point for building PWAs.

Make sure your service worker is properly registered.

// CRA v1
import registerServiceWorker from './registerServiceWorker';
registerServiceWorker();
// CRA v2
import * as serviceWorker from './serviceWorker';
serviceWorker.register();

src/index.js


Starting Loader

Starting Loader dalam aplikasi web React JS

React JS is a powerful framework, but it’s relatively “heavy” due to its rich features. This can cause a delay in the initial load, leaving users staring at a blank white screen before the DOM renders — not a great user experience.

To fix this, we can add a Starting Loader, which appears during the first second while the app is loading.

Starting Loader muncul pada detik pertama

You can add this loader directly in your index.html, then remove it in React’s componentDidMount() lifecycle method. You can also style it with CSS for a nicer look.

<head>
 <style type="text/css">
  #startingLoader {
    ...
  }
 </style>
</head>
<body>
<div id="root"></div>
<div id="startingLoader">
  Wait a second…
 </div>
</body>

src/components/App.jsx
componentDidMount() {
 const elem = document.getElementById('startingLoader');
 window.onload = () => {
  if (elem) {
   elem.remove();
  }
 };
}

public/index.html


PWA Configuration on Android and iOS

Android

When you create a React app using CRA, it automatically generates a manifest.json file inside the public folder. This file controls how your PWA appears to users — splash screen, app icon, start URL, etc.

However, when testing with Lighthouse, you might see some warnings like these:

  • Does not redirect HTTP traffic to HTTPS
  • User will not be prompted to install the Web App
  • Not configured for a custom splash screen
lighthouse tests

How to fix them:

  1. Redirect HTTP to HTTPS on your server.
  2. Add app icons of 192×192 and 512×512 pixels in addition to the smaller default ones.
  3. Change the start_url from "./index.html" to "/", since "./index.html" might route to a 404 page in your app.
{
  "name": "React PWA WWWID",
  "short_name": "REACT WWWID",
  "icons": [
      {
        "src": "favicon.ico",
        "sizes": "64x64 32x32 24x24 16x16",
        "type": "image/x-icon"
      },
      {
          "src": "/android-chrome-192x192.png",
          "sizes": "192x192",
          "type": "image/png"
      },
      {
          "src": "/android-chrome-512x512.png",
          "sizes": "512x512",
          "type": "image/png"
      }
  ],
  "start_url": "/",
  "theme_color": "#171717",
  "background_color": "#171717",
  "display": "standalone",
  "orientation": "portrait"
}

You can learn more about Web Manifest here.

iOS

For iOS, PWA setup requires adding several meta and link tags inside the <head> tag.

<meta name="apple-mobile-web-app-title" content="React PWA WWWID">
<meta name="apple-mobile-web-app-status-bar-style" content="black">
<meta name="apple-mobile-web-app-capable" content="yes">
<link rel="apple-touch-icon" sizes="180x180" href="%PUBLIC_URL%/apple-touch-icon.png">
<link rel="apple-touch-startup-image" href="%PUBLIC_URL%/assets/splash/launch-640x1136.png" media="(device-width: 320px) and (device-height: 568px) and (-webkit-device-pixel-ratio: 2) and (orientation: portrait)">
<link rel="apple-touch-startup-image" href="%PUBLIC_URL%/assets/splash/launch-750x1294.png" media="(device-width: 375px) and (device-height: 667px) and (-webkit-device-pixel-ratio: 2) and (orientation: portrait)">
<link rel="apple-touch-startup-image" href="%PUBLIC_URL%/assets/splash/launch-1242x2148.png" media="(device-width: 414px) and (device-height: 736px) and (-webkit-device-pixel-ratio: 3) and (orientation: portrait)">
<link rel="apple-touch-startup-image" href="%PUBLIC_URL%/assets/splash/launch-1125x2436.png" media="(device-width: 375px) and (device-height: 812px) and (-webkit-device-pixel-ratio: 3) and (orientation: portrait)">
<link rel="apple-touch-startup-image" href="%PUBLIC_URL%/assets/splash/launch-1536x2048.png" media="(min-device-width: 768px) and (max-device-width: 1024px) and (-webkit-min-device-pixel-ratio: 2) and (orientation: portrait)">
<link rel="apple-touch-startup-image" href="%PUBLIC_URL%/assets/splash/launch-1668x2224.png" media="(min-device-width: 834px) and (max-device-width: 834px) and (-webkit-min-device-pixel-ratio: 2) and (orientation: portrait)">
<link rel="apple-touch-startup-image" href="%PUBLIC_URL%/assets/splash/launch-2048x2732.png" media="(min-device-width: 1024px) and (max-device-width: 1024px) and (-webkit-min-device-pixel-ratio: 2) and (orientation: portrait)">

<link rel=”apple-touch-startup-image” /> defines the splash screen image for iOS devices. You can use media queries to adapt it to different screen sizes.

You can learn more about PWA configuration for iOS here.


Code Splitting

As web apps grow, so does their bundle size — and long load times can frustrate users, especially on slow mobile networks.

Code splitting helps by breaking your JavaScript into smaller chunks, so users only download the code needed for the current route or feature.

I used react-loadable, a library that supports preloading, delay, timeout, and loading components.
Other alternatives include loadable-components, react-async-component, and react-code-splitting.

Example of route-based code splitting with react-loadable:

Code splitting with react-loadable

Each JavaScript file is split into smaller chunks per route.
You can also use it on hidden components, like modals, to defer loading until needed.

Javascript splitted to chunk files

Image Optimization

Images often make up the majority of a webpage’s size. Optimizing them reduces page weight, load time, and bandwidth — crucial for users on mobile data.

Lazy Loading Images

Like code splitting, lazy loading ensures that images are only loaded when they appear in the viewport.

I used the react-lazy library because it’s simple and uses the IntersectionObserver API (don’t forget to include a polyfill if needed).

import { Lazy } from 'react-lazy';

<Lazy ltIE9>
 <img src="https://..." alt="..." />
</Lazy>

WebP Support

WebP is a modern image format that provides high compression with minimal quality loss —

  • Lossless WebP images are ~26% smaller than PNGs
  • Lossy WebP images are ~25–34% smaller than JPEGs

You can easily convert images to WebP using services like Cloudinary.
However, note that WebP isn’t supported on iOS browsers and Firefox (as of writing).


Resource Hints (Preload, Prefetch, Preconnect)

These are HTML hints that tell the browser which resources to load early.

Preload

Used for high-priority assets like fonts, CSS, or critical JavaScript.

<link rel=”preload” href=”https://fonts.googleapis.com/icon?family=Material+Icons" as=”style”>

Prefetch

Used for low-priority assets that may be needed soon.
Browsers download them in the background and cache them.

  1. Link Prefetching

Prefetching can make apps feel faster, but overusing it can slow down the current page.

<link rel="prefetch" href="/assets/image.png">
“This technique has the potential to speed up many interactive sites, but won’t work everywhere. For some sites, it’s just too difficult to guess what the user might do next. For others, the data might get stale if it’s fetched too soon. It’s also important to be careful not to prefetch files too soon, or you can slow down the page the user is already looking at. — Google Developers“

2. DNS Prefetching

Resolves external domain DNS ahead of time.

<link rel=”dns-prefetch” href=”https://res.cloudinary.com">
“DNS requests are very small in terms of bandwidth, but latency can be quite high, especially on mobile networks. By speculatively prefetching DNS results, latency can be reduced significantly at certain times, such as when the user clicks the link. In some cases, latency can be reduced by a second. — Mozilla Developer Network

3. Prerendering

Preloads and renders an entire next page in the background.

<link rel="prerender" href="https://www.keycdn.com">
“The prerender hint can be used by the application to indicate the next likely HTML navigation target: the user agent will fetch and process the specified resource as an HTML response. To fetch other content-types with appropriate request headers, or if HTML preprocessing is not desired, the application can use the prefetch hint. – W3C

Preconnect

Opens network connections (DNS, TCP, TLS) early to reduce latency.

<link rel="preconnect" href="https://api.rss2json.com" crossorigin>

Time to Audit!

1. Deploy the App

I used Firebase Hosting because it’s simple, supports HTTPS, HTTP/2, and Cache-Control.
Alternatives: Heroku, Netlify, GitHub Pages, Vercel, Surge, etc.

2. Audit with WebPageTest.org

You can test performance using WebPageTest and Lighthouse.

Hasil audit performa dengan webpagetest.org

In my test using WebPageTest (Dulles, VA, Slow 3G mode),
the app loaded in 3.16 seconds, achieved a PWA score of 100, and a Performance score of 95.


React PWA WWWID Project

If you’re interested, feel free to explore or contribute to the project:

GitHub Repository :

GitHub - rizalibnu/react-pwa-wwwid: An example of a PWA built in React Redux based on Create React App for WWWID Performance Challenge
An example of a PWA built in React Redux based on Create React App for WWWID Performance Challenge - rizalibnu/react-pwa-wwwid

Demo :

React PWA WWWID
Thanks to the WWWIDPWA community — everyone’s collaboration and shared insights made this challenge both fun and educational.
I learned a lot from the process and hope this write-up helps you build faster, more reliable PWAs too.

Optimizing `Progressive Web App (PWA)` Load Time with `React JS` — Under 5 Seconds on a Slow 3G Connection

This article is part of the #WWWIDChallenge

Optimizing `Progressive Web App (PWA)` Load Time with `React JS` — Under 5 Seconds on a Slow 3G Connection
Hasil audit lighthouse dengan webpagetest.org