Remix PWA is a lightweight, standalone npm package that adds full Progressive Web App support to Remix 💿.
- Integrates Progressive Web App (PWA) features into Remix including offline support, caching, installability on Native devices and more.
- Automatic caching for
build
files and static files. - Implements a network-first strategy for loader & action calls (i.e Gets a fresh request each time when online, and proceeds to an older fallback when offline)
- Safely handles uncached loader calls without affecting other sections of the site (i.e Throws user to nearest Error boundary without disrupting Service Workers)
- PWA client-side utilities that comes bundled with your App to give you more options and freedom while building the PWA of tomorrow.
- Server-side utilities to simplify a lot of PWA backend for you. Developing would be more exciting!
v1 is officially here 🎉🎉!! Check out the release changelog for a summary of the journey so far.
- Getting Started
- Setting Up your PWA
- Deployment
- API Documentation
- Installation Manual Guide
- Going Deeper
- Remix PWA Roadmap
- Contributing Doc
- Help Support
- Authors
- License
remix-pwa is a quick an easy way to get a standardized API to interact with service workers with your application.
remix-pwa will:
- Generate a route to store your applications manifest data
- Generate a route to handle service worker subscription
- Generate standardized messaging protocol between service workers and your application.
To integrate PWA features into your Remix App with remix-pwa
, run the following command:
npx remix-pwa@latest
During installation, you would be required to answer a few questions:
- The language you are using for the Remix project (TypeScript or JavaScript)
- The caching strategy you are using. (
Precaching
orJust-In-Time Caching
) - What services of Remix PWA you need. (It is finally here 🥳)
- The location of your Remix
app
directory. - Do you want to install Remix PWA dependencies? (Default:
yes
)
Refer to this section for a detailed explanation of the above questions.
To upgrade to a newer version of remix-pwa
, simply run this command
# Use npx to integrate PWA features without modifying your dependencies
npx remix-pwa@latest
and you can continue with your PWA
After installing remix-pwa
, link the manifest
file in order to get installability feature of PWA as well as app characteristics and other features. To do that, simply add the following block of code to the head in your root
file above the <Links />
tag:
<link rel="manifest" href="/resources/manifest.webmanifest" />
To run your app, simply run the command:
npm run dev
And voila! You are now ready to use your PWA!
If you want to lay you hands on demo icons and favicons for your PWA, remix-pwa
got you covered with sample icons. Simply delete the favicon.ico
in your public
folder and add the following links to your root
file, above the <Links />
tag.
To build and deploy your Remix PWA App, simply run the command:
npm run build
at build time and then, you can host it on any hosting providers you prefer.
Client APIs are a set of asynchronous functions that are to be run on the client only and never on the server.
They can be triggered by DOM events (click, hover, keypress, etc.) like other functions, but in order to trigger window events that happen at the start of a page lifecycle, e.g page "load" event, it is highly recommended to use these functions in a React's useEffect
hook.
Almost all Client APIs return a promise object (type ReturnObject
) that consists of two properties: status
and message
. The status
key is a string that would either be "success" or "bad". remix-pwa
is set up by default, with error-catching procedures for these APIs. You can still set up your custom responses (to display a particular UI for example, if the particular API isn't supported in the user's browser) in case of an error or a successful request with the status
response. The message
key is a default message string that accompanies the status in case of a pass or fail.
interface ReturnObject {
status: "success" | "bad",
message: string;
}
This function is used to check wether a user is online or offline and execute a function accordingly. It could be used to update a state, display a particular UI or send a particular response to the server.
import { checkConnectivity } from "~/utils/client/pwa-utils.client";
const online = () => {
//..Do something for online state
}
const offline = () => {
//..Do something for offline state
}
useEffect(() => {
// The `console.log` method returns an object with a status of "success" if online and a pass message or a status of "bad" and a fail message if offline
checkConnectivity(online, offline).then(data => console.log(data))
}, [])
The Clipboard API is a method used to access the clipboard of a device, native or web, and write to it. This function can be triggered by DOM events, i.e "click", "hover", etc. or window events i.e "load", "scroll", etc.
import { copyText } from "~/utils/client/pwa-utils.client";
<button onClick={() => copyText("Test String")}>Copy to Clipboard</button>
🚨 This is still an experimental feature! Some browsers like FireFox would not work with this feature! 🚨
The WakeLock API is a function that when fired, is used to keep the screen of a device on at all times even when idle. It is usually fired when an app is started or when a particular route that needs screen-time is loaded (e.g A video app that has a watch-video
route)
import { WakeLock } from "~/utils/client/pwa-utils.client";
useEffect(() => {
WakeLock() // triggers the Wakelock api
}, [])
The badge API is used by installed web apps to set an application-wide badge, shown in an "operating-system-specific" place associated with the application (such as the shelf or home screen or taskbar).
The badge API makes it easy to silently notify the user that there is new activity that might require their attention, or to indicate a small amount of information, such as an unread count (e.g unread messages).
The addBadge
function takes a number as an argument (displays the number of notification) while the removeBadge
function doesn't take any argument.
import { addBadge, removeBadge } from "~/utils/client/pwa-utils.client";
// used to clear away all notification badges
removeBadge()
//used to add a badge to the installed App
addBadge(3); // sets a new notification badge with 3 indicated notifications
The Full Screen feature is an additional utility you can integrate into your app while building your PWA, EnableFullScreenMode()
enables an App to cover the entire breadth and width of the scree at the tap of a button and the ExitFullScreenMode()
exits full-screen mode. They both don't take any arguments and can be invoked like any other normal function.
import { EnableFullScreenMode, ExitFullScreenMode } from "~/utils/client/pwa-utils.client";
// Enable full-screen at the click of a button
<button onClick={EnableFullScreenMode}>Enable Full-Screen mode</button>
//Exit full-screen mode
<button onClick={ExitFullScreenMode}>Exit Full-Screen Mode</button>
// Interface `NotificationOptions`
interface NotificationOptions {
body: string | "Notification body";
badge?: string;
icon?: string;
silent: boolean | false;
image?: string
}
The SendNotification
API is a client-only function driven only by the Notifications API, it is different from the Push API which is another API handled and executed by the server (arriving to remix-pwa
soon). The SendNotification
function is executed by the client and takes in two arguments, one is the title of the notification and that's the top header (Title) of the notification your user would see. The second option is an object that would contain additional options for the API.
The first key for the NotificationsObject
argument is the body
and that is a required argument. The body is the main content of your notification that would contain the details of what you want to pass to the user. The badge
argument is an optional argument and it's the image URL string of the Notification badge, and it's what the user would see when there is no space for the Notification content to show. It is recommended to use a 96px by 96px square image for the badge. The next argument is the icon
argument which is the image that would be displayed alongside your Notification. The image
parameter is a string argument (url of your image) and is used to display an image along with your notification. The final argument is the silent parameter and it's a boolean argument (true or false) that is required, it is used to determine wether a notification should be sent silently regardless of the device's settings, it is by default set to false.
The Notification API can take values from the server (e.g loader
) or from the client but it must be called and executed on the client side. We are working on adding the Push API that allows you to execute a Notification API together with the Push API on the server side in response to anything (for example, when a message is sent to a user in a messaging App).
This API is fully stable and is setup completely for your use cases including Notification permissions, however, we are working on adding more API options so that you can have the maximum custom experience!
import { SendNotification } from "~/utils/client/pwa-utils.client";
const options = {
body: "Hello, take a break and drink some water! 💧", // required!
badge: "/icons/notification-badge.png", // not required
icon: "/icons/app-icon.png", // not required
silent: false, // required
image: "/images/Nature.png" // NEW! Not required
}
let minutes = 30
// executed in several ways
setTimeout(() => {
SendNotification("Workout App", options)
}, minutes * 60 * 1000)
// another method of execution
<button onClick={() => SendNotification("Exercise Tracker App", options)}>Take a break!</button>
This utility is used to get wether a document is visible or is minimized (or in the background, etc). It takes two functions as its parameter, one for a visible state, and the other for an invisible state. A common use case for this is to stop a process (e.g video playing, downloading process, etc) when the app is minimized or to run a process only when the App is minimized.
import { Visibility } from "~/utils/client/pwa-utils.client";
const documentVisible = () => {
//..do something
}
const documentInvisible = () => {
//..do something else
}
const state = document.visibilityState
// Monitor visibility and keep firing the function on visibility change
useEffect(() => {
Visibiliy(documentVisible, documentInvisible)
}, [state])
This is a modified version of the Copy Text to Clipboard API. It is used to easily copy images to your device's clipboard, it starts by taking the url of the image (e.g Passed through the src
of an image tag) then fetching the image behind-scenes and finally copying the blob
to your device's clipboard.
import { copyImage } from "~/utils/client/pwa-utils.client";
// Getting an image frame via React's ref hook.
const frameRef = useRef<HTMLImageElement>(null!)
// Accessing the `src` attribute and copying it to the clipboard.
<button onClick={() => copyImage(frameRef.current.src)}>📋 Copy Image to Clipboard</button>
The File Web Share API is an asynchronous method used to share files from your PWA to other apps. It takes in 3 arguments, title
seen as the title of your shared response, text
which is the body of your shared message (You can set a default value in app/utils/client/pwa-utils.client.ts
) and finally the data
which an array that contains the files to share. It returns a ResponseObject
promise.
import { WebShareFile } from "~/utils/client/pwa-utils.client";
const files = ["File1.txt", "File2.svg"]
const SendFile = async () => {
await WebShareFile("My Remix PWA", files, "A text file and an SVG file")
// Do something or Trigger something else
}
This is a modified Web Share method used to serve links from your file (e.g A blog app) to other supported apps on your device. It is similar to the File Web Share API
except it accepts a url instead of files.
import { WebShareLink } from "~/utils/client/pwa-utils.client";
const data = useLoaderData()
<button onClick={() => WebShareLink(data.param, data.title, data.description)}>Share Post</button>
The Miscellaneous Webs Share Api is intended to be a simple Web Share Api that should be used if you intend to send something a bit more loose and simple compared to files & URLs.
I would advise against using this API except when the other two don't meet your data criteria. As this is loosely typed and
couldcause errors when used with heavy, technical data.
import { WebShare } from "~/utils/client/pwa-utils.client";
<button onClick={() => WebShare("Hello, I am a volatile API!")}>Share loose data</button>
interface PushObject {
title: string;
body: string;
icon?: string;
badge?: string;
dir?: string;
image?: string;
silent?: boolean;
}
This is a server API. All Server APIs are to be run in the server (i.e Loaders, Actions &
.server
files)
Push Notification API is finally here (with some updates)! Push Notification API is used to by the server to generate Notifications for the user. It is used when you want to push a notification that is triggered by events from outside the App (e.g A message is sent to your user from another user's app, in a messaging app). It takes in quite a lot of arguments, a content argument which is an object containing methods to style your notification. The title property is the title of your Notification, the body is for the main text of the notification and the delay is an optional argument used to set the delay (in seconds) between the event happening and the server triggering the notification. It has a default value of zero.
To reference the other properties of
PushObject
, check out the properties section of this MDN doc.
Setting up and using remix-pwa
first-ever server utility requires some steps to be functional, let's break them down:
- After installing
remix-pwa
, if you intend to use the Push API, run the command:
npx web-push generate-vapid-keys
You would get two keys in your console, a PRIVATE key and a PUBLIC key. Keep your PRIVATE key safe!
- Create a
.env
file, and save your keys using the variable nameVAPID_PUBLIC_KEY
for the public key andVAPID_PRIVATE_KEY
for the private key.
Add the following line of code in entry.server.ts
:
import "dotenv/config"
Don't forget to add the
.env
file to your.gitignore
file
- You can finally use the basic Web Push API in your server!
import { PushNotification } from "~/utils/server/pwa-utils.server.ts
export const action: ActionFunction = async ({ request }) => {
// Get request and perform other operations
await PushNotification({
title: "Remix PWA",
body: "A server generated text body."
}, 1)
}
⚠ Hang On tight! We are working on bringing more awesome features to you amazing folks. ⚠
Ahh! What a mouthful for a section topic 🤭. This section is to explain the various prompts that comes up during remix-pwa
installation.
This is quite self-explanatory. It is the language you used for your Remix project, possible options are:
- TypeScript
- JavaScript
This is a new one 🎉🎉! There are two possible options:
- Just-In-Time Caching
- Precaching
Just-In-Time Caching:
Also known as jit
. This is the default caching strategy remix-pwa has been using for a while. It is a good choice for most projects. It caches only pages and responses the user has navigated to, and updates the cache when the page information changes and the user is online. This would be preferred when building a large, dynamic application with notable parametized routes.
Precaching Strategy:
This is the new one 🥁! It is a caching strategy that is used when you want to cache all the pages and responses of your application on first load. It is a good choice for small, static applications. It caches all the pages and responses of your application, and updates the cache when the page information changes and the user is online. This would be preferred when building a small, static application with few parametized routes.
Thanks to mokhtar for his contribution!
Another new feature and a way to install the minimal things you need automatically. Remix PWA comes with five major features:
- Service Workers
- Web Manifest
- Push API Services
- Client Utilities (APIs to help build a PWA)
- Development Icons
Service Workers are the standard and a vital part of every Progressive Web Application. Handles the caching and a lot more in Remix PWA.
Web Manifests are the main file that is used to generate the Progressive Web App. It is a JSON file that provides information and characteristics for your application. Needed if you want to deploy on App Stores.
Push API is a server API to integrate Push Notifications into your application. It was made standalone feature due to the amount of work needed to setup successfully and not all apps need it.
Client Utilities are a set of APIs that help with getting that native feel of your PWA quickly. They consist of functions to toggle fullscreen, manage App badges, copy images, send files and much more.
Development Icons are a set of Remix icons you can use to quickly get icons for your PWA. They need to be linked though! Get the link
from here
This is the location where your app
folder is located. By default, remix uses the /app
directory in your project root. But lots of users change the location of the app root folder (e.g. /src/app
). This now allows app directory of all kinds to be used.
Note that the directory input must not have a trailing or leading slash!! (e.g. app
for /app
and src/app
for /src/app/
)
This one is straightforward. Do you want to install Remix PWA dependencies right now and continue developing your app or do you want to skip it and continue your work with lots of errors due to missing dependencies?
Setting up and using remix-pwa
's server APIs requires some steps to be functional, let's break them down:
- After installing
remix-pwa
, if you intend to use the Push API, run the command:
npx web-push generate-vapid-keys
You would get two keys in your console, a PRIVATE key and a PUBLIC key. Keep your PRIVATE key safe!
- Create a
.env
file, and save your keys using the variable nameVAPID_PUBLIC_KEY
for the public key andVAPID_PRIVATE_KEY
for the private key.
Add the following line of code in entry.server.ts
:
import "dotenv/config"
Don't forget to add the
.env
file to your.gitignore
file
- You can finally use the basic Web Push API in your server!
There is currently an issue you may run into with the CLI, if you have comments in your root.tsx file you may get an error when running:
npx remix-pwa@latest
Simply removing the comments and reattempting installation seems to fix any such errors.
Want to upgrade your PWA utilities? Or need a bit of tips and hints on how to customize your PWA? Want to make a production-ready PWA with Remix? This is your section! Let's get down to the nitty-gritty of remix-pwa
.
One thing you might have noticed is that your manifest.json file is stored as a route and not a public, static file. This is due to an advantage of Remix and Remix PWA. By making our manifest a route, we allow developers to customise a PWA based on a user-to-user preference!
Allow me to break that down. If you open your manifest
file located in app/routes/resources
, you would notice that the manifest is stored inside a loader function that returns the manifest as a json object. Now if you wanted to customise the manifest based on user preference instead of a static file, how would we do it? Let's look at a simple scenario to answer that question:
We have a database hosted on an external platform and we are using Prisma as our database ORM, we save our user's color preferences in a table called User
and we want to give the PWA the same feel as the website. The last thing we want to do is give our user's default color scheme for the app and custom schemes for the site. That's poor UX.
To solve this issue, we can simply import our PrismaClient
into our manifest route's loader and interact with our db from there. Allowing us to get the preferences and set them as our PWA's theme and background color (for more info on themes and background-color, refer to MDN Docs)
// A mock implementation
export let loader: LoaderFunction = async () => {
const preference = await prismaClient().user.findUnique({
where: {
username: "example@gmail.com"
},
include: {
themePreference,
bgPreference
}
})
return json(
{
background_color: preference.bgPreference,
theme_color: preference.themePreference,
// ..Rest of the manifest
}
)
}
We got our user, we got their preference, we make them happy!
Our manifest is quite simple and default, the name
of the app is something that you would change, same as the theme color and other json fields depending on your taste and App's needs. But how about we add more to our manifest instead?
In v0.6.0, we created a new field in our manifest known as shortcuts
and they do exactly what they say. They allow us to navigate to certain, specified pages (routes) directly from outside our App. I won't cover the fields used in the shortcuts
and what they are meant to do, I would however, drop this wonderful article by Microsoft Azure that explains the shortcut
aspect of the manifest well.
Another thing you would love to edit is the icons
field in the manifest, we used default Remix icons for those but you would not want to. Instead replace the icons in /icons
folder and specify their sizes. You could use Sketch or Figma to design and resize icons. You could also delete the default favicon.ico
that comes with Remix.
This section is till WIP 🚧
Want to see proposed features and bug fixes? Or do you want to propose an idea/bug fix for remix-pwa
and want to view the current roadmap? Check out remix-pwa
Roadmap and see what lies in wait for us!
Thank you for your interest in contributing 🙂. The contribution guidelines and process of submitting Pull Requests are available in the CONTRIBUTING.md. Awaiting your PR 😉!
If you want to get help on an issue or have a question, you could either open an issue or you could ask your questions in the Official Remix's Discord Server where there are a lot of helpful people to help you out.
-
Abdur-Rahman (aka @ShafSpecs)
-
Douglas Muhone (theeomm)
-
Mokhtar (mokhtar)
-
Tom (pumpitbetter)
-
Brock Donahue (Brocktho)
-
Special thanks to jacob-ebey for his contribution and help with the creation of
remix-pwa
!
This project is licensed under the MIT License - see the LICENSE.md file for details.