Transforming Your React App into a PWA: A Comprehensive Guide
Hussain Pettiwala
Posted on April 11, 2023
Progressive Web Apps (PWAs) are web applications that are designed to provide a native app-like experience to users, including features such as offline accessibility, push notifications, and home screen installation. By implementing PWAs, you can improve user engagement, increase conversions, and deliver a more seamless experience for your users.
In this comprehensive guide, we'll walk through the steps to transform your react app (built using create-react-app) into a PWA. We'll cover everything from generating assets for your PWA, updating your web manifest, creating a service worker, and registering your service worker. We'll also see how to test your PWA using ngrok.
By the end of this guide, you will have converted your react app to a PWA, and that can be easily installed on their home screens for easy access. So let's get started!
Generate Assets for your PWA
1. We will use pwa-asset-generator
npx pwa-asset-generator public/favicons/android-chrome-512x512.png public/pwa-assets -b "#2F3437"
2. CRA by default creates a site.webmanifest
file update its contents by.
- copy icons content for your manifest.json file.
- here make sure that you remove the public prepend from the urls
For example:
your output might have been something like
{
"src": "public/pwa-assets/manifest-icon-192.maskable.png",
"sizes": "192x192",
"type": "image/png",
"purpose": "any"
}
update it to for every URL in the icons array
{
"src": "/pwa-assets/manifest-icon-192.maskable.png",
"sizes": "192x192",
"type": "image/png",
"purpose": "any"
},
3. Incase site.webmanifest
does not exist in your project create one in your public
folder and link it in your index.html
file
- Here I would highly recommend to prepone the URLs with
%PUBLIC_URL%/
For example:
Update
<link
rel="apple-touch-startup-image"
href="public/pwa-assets/apple-splash-2048-2732.jpg"
media="(device-width: 1024px) and (device-height: 1366px) and (-webkit-device-pixel-ratio: 2) and (orientation: portrait)"
/>
to
<link
rel="apple-touch-startup-image"
href="%PUBLIC_URL%/public/pwa-assets/apple-splash-2048-2732.jpg"
media="(device-width: 1024px) and (device-height: 1366px) and (-webkit-device-pixel-ratio: 2) and (orientation: portrait)"
/>
Note: you can do this quickly by using the multicursor feature of your code editor.
<link rel="manifest" href="%PUBLIC_URL%/site.webmanifest" />
4. copy iOS meta tags content for your index.html file
Update webmanifest
This is an example webmanifest. You can get more details regarding the same here
Note that the screenshots field is optional
{
"short_name": "App Name",
"name": "App Name: What it does",
"scope": "/",
"id": "/?source=pwa",
"start_url": "/?source=pwa",
"display": "standalone",
"theme_color": "#b241ff",
"background_color": "#2F3437",
"description": "Description of your app",
"icons": [
{
"src": "public/pwa-assets/manifest-icon-192.maskable.png",
"sizes": "192x192",
"type": "image/png",
"purpose": "any"
},
{
"src": "public/pwa-assets/manifest-icon-192.maskable.png",
"sizes": "192x192",
"type": "image/png",
"purpose": "maskable"
},
{
"src": "public/pwa-assets/manifest-icon-512.maskable.png",
"sizes": "512x512",
"type": "image/png",
"purpose": "any"
},
{
"src": "public/pwa-assets/manifest-icon-512.maskable.png",
"sizes": "512x512",
"type": "image/png",
"purpose": "maskable"
}
],
"screenshots": [
{
"src": "/screenshots/dark-1.png",
"type": "image/png",
"sizes": "417x720"
},
{
"src": "/screenshots/home-screen-dark-1.png",
"type": "image/png",
"sizes": "417x720"
},
{
"src": "/screenshots/light-1.png",
"type": "image/png",
"sizes": "417x720"
},
{
"src": "/screenshots/home-screen-light-1.png",
"type": "image/png",
"sizes": "417x720"
}
]
}
Create a service-worker.ts
file in your src
folder
copy contents from the official create-react-app pwa template
Create a serviceWorkerResgistration.ts
file in your src
folder
copy contents from the official create-react-app pwa template
In your src/index.tsx
file
...
import * as serviceWorkerRegistration from "./serviceWorkerResgistration";
...
...
serviceWorkerRegistration.register();
You can test your PWA using ngrok
build your react app
npm run build
serve that build using serve
serve -s build
Then in another terminal window
ngrok http 3000
(Optional) add custom install prompt
1. As stated here
Users may not be familiar with the PWA install process. As the developer, you will understand when it is the right time to invite the user to install the app.
2. You can add a custom install button that will invite the user to install the app.
3. create a useAddToHomescreenPrompt
hook
credits : https://gist.github.com/rikukissa/cb291a4a82caa670d2e0547c520eae53
import { useEffect, useState } from "react";
export function useAddToHomescreenPrompt(): [
BeforeInstallPromptEvent | null,
() => void
] {
const [prompt, setState] = useState<BeforeInstallPromptEvent | null>(null);
const promptToInstall = () => {
if (prompt) {
return prompt.prompt();
}
return Promise.reject(
new Error(
'Tried installing before browser sent "beforeinstallprompt" event'
)
);
};
useEffect(() => {
const ready = (e: BeforeInstallPromptEvent) => {
e.preventDefault();
setState(e);
};
window.addEventListener("beforeinstallprompt", ready as any);
return () => {
window.removeEventListener("beforeinstallprompt", ready as any);
};
}, []);
return [prompt, promptToInstall];
}
4. If you are using typescript here's the BeforeInstallPromptEvent
type credits
/**
* The BeforeInstallPromptEvent is fired at the Window.onbeforeinstallprompt handler
* before a user is prompted to "install" a web site to a home screen on mobile.
*
*/
interface BeforeInstallPromptEvent extends Event {
/**
* Returns an array of DOMString items containing the platforms on which the event was dispatched.
* This is provided for user agents that want to present a choice of versions to the user such as,
* for example, "web" or "play" which would allow the user to chose between a web version or
* an Android version.
*/
readonly platforms: Array<string>;
/**
* Returns a Promise that resolves to a DOMString containing either "accepted" or "dismissed".
*/
readonly userChoice: Promise<{
outcome: "accepted" | "dismissed";
platform: string;
}>;
/**
* Allows a developer to show the install prompt at a time of their own choosing.
* This method returns a Promise.
*/
prompt(): Promise<void>;
}
5. You can use it like so
const IntallPwaComponent = () => {
const [prompt, promptToInstall] = useAddToHomescreenPrompt();
const [isPromptVisible, setIsPromptVisible] = useState(false);
useEffect(() => {
if (prompt) {
setIsPromptVisible(true);
}
}, [prompt]);
return (
<>{isPromptVisible && <Button onClick={promptToInstall}>Install</Button>}</>
);
};
Attribution
Thank you Pargat Dhanjal for the amazing cover image!
Posted on April 11, 2023
Join Our Newsletter. No Spam, Only the good stuff.
Sign up to receive the latest update from our blog.