The state of PWA deep linking
One of the best features of native apps is that they are able to capture links within their scope to enable deep linking.
For example, when I have the Instagram app installed on my phone and someone sends me a link to an Instagram post and I click it, it will open the Instagram app instead of a browser.
PWAs also have this capability, but unfortunately, the story of where this works and when is a bit complicated. It differs on various OSes and browsers, so it’s not always easy to know when deep linking will work.
To help understand deep linking into PWAs, I tested all options and here are my findings.
Web App link handling
Chromium-based browsers like Chrome and Edge support the handle_links
manifest.json member that enables PWAs to capture links within their scope, which simply means that the URL is within the scope
as defined in manifest.json.
So when the scope of a PWA is “https://www.example.com/” and the URL of the clicked link is “https://www.example.com/foo”, then it will be captured by the PWA.
When a PWA wants to use this behavior, the handle_links
member should be added to manifest.json:
"handle_links": "preferred"
The handle_links
member can hold the following values:
“preferred”: the user agent should handle links using matching PWAs and may promote link handling behavior.
“not-preferred”: the user agent should not handle links using matching PWAs and may not promote link handling behavior.
“auto”: default value if
handle_links
is not found in the manifest. The user agent may choose between “preferred” and “not-preferred”.
See the explainer for details.
I tested the behavior of this on all platforms in all major browsers by sending a link in an email to myself and then clicking that link.
Android
PWA installed with Chrome
When I click the link in the GMail app or in Chrome, it’s always opened in the PWA, even when Chrome is not the default browser.
When the email is opened in Edge through the GMail website and I click the link, it opens in Edge but also offers to open it in the PWA:
PWA installed with Edge
When I click the link in the GMail app, the link is not opened in the PWA but in an in-app browser powered by the default browser:
When the email is opened in any browser through the GMail website and I click it, it’s opened in the same browser the GMail website was opened with.
PWA installed with Firefox
Here, the behaviour is the same as when the PWA is installed with Edge: when I click the link in the GMail app, the link is not opened in the PWA but in an in-app browser powered by the default browser.
Conclusion for Android: deep linking only works reliably when the PWA is installed with Chrome.
iOS
On iOS, there is unfortunately no support for deep linking. All clicked links open in the default browser, regardless of whether a PWA is installed and with which browser.
ChromeOS
On ChromeOS, the PWA can only be installed with Chrome and deep linking works reliably.
macOS
PWA installed with Chrome
When I click the link in the Mail app, it opens in the default browser. When it’s opened in Chrome, it offers to open it in the PWA instead:
When the email is opened in any browser through the GMail website and I click it, it’s opened in the same browser the GMail website was opened with.
PWA installed with Edge
When I click the link in the Mail app, it opens in the default browser. When it’s opened in Edge, it offers to open it in the PWA instead:
Web app added to Dock with Safari
When I click the link in the Mail app, it opens in the default browser. When it’s opened in Safari, it offers to open it in the web app added to the Dock instead:
But when the web app that was added to the Dock has been interacted with for a while, most notably, when it has been opened directly from the Dock at least once, clicked links open in the web app consistently.
On macOS, PWAs can’t be installed with Firefox.
Conclusion for macOS: deep linking only works reliably when the web app is added to the Dock with Safari.
Windows
Unfortunately, deep linking is not supported on Windows as all clicked links open in the default browser. Just like on macOS, Chrome and Edge offer to open the PWA instead when the PWA was installed through them.
On Windows, PWAs can’t be installed with Firefox.
Push notifications
Another important feature for PWAs is to open the PWA when a push notification is clicked.
On iOS, the installed PWA is consistently opened when a push notification is tapped, even when the PWA is not running at that moment. The notificationclick
event is not supported on iOS, so its configuration is ignored.
On Android, the installed PWA is consistently opened when a push notification is tapped if this is configured in the notificationclick
event. If this is not the case, nothing happens when the notification is tapped.
In the following example, the URL to open is specified in the data
property of the notification:
self.addEventListener('notificationclick', async (e) => {
//read the notification object from the event
const {notification} = e;
// read the data property of the notification
const {data} = notification;
// close the notification
notification.close();
// open the url specified in the "url" member of the data property
// this needs to be wrapped in the "waitUntil" method of the event
// to make sure the service worker stays active long enough
e.waitUntil(clients.openWindow(data.url))
})
Check https://whatpwacando.today/notifications for an example of the notification payload.
PWA Audit: on your way to a great PWA
Do you already have a PWA, but are you running into issues with performance, security, or functionality? Or are you not sure how to make your PWA better?
I can help you by running an audit of your PWA
I will evaluate it on more than 35 criteria and provide you with clear and actionable instructions on how to improve it. No generic stuff that you can get anywhere, but an in-depth quality checkup to get you on your way to a great PWA.
Some of the criteria I will evaluate it on are:
Installability
Cross-device and cross-platform compatibility
Offline support
Usability
Effective use of modern web APIs
Performance
Security
Your investment in the improvement of your PWA through the audit is €799
(excluding VAT where applicable).
If you want to request an audit or first would like to know more, you can
fill out the form or book a call with me.
Screen Wake Lock API now supported for PWAs on iOS
The Screen Wake Lock API was already supported in all major browsers but not yet for installed web apps on iOS. In Safari 18.4, this is now finally fixed so support is now universal.
The Screen Wake Lock API enables web apps to prevent devices from dimming or locking the screen which is very handy for recipe websites for example. I cook regularly and I like to try new recipes, so I’m regularly in the kitchen, looking at my phone to follow the recipe.
But most recipe sites don’t use this feature, so depending on your device’s settings, the screen will dim and lock after a while, which is very annoying when your hands are full or dirty.
Using the Screen Wake Lock API is very simple and a great improvement of user experience if this makes sense for your web app. A lot of web apps don’t use it yet, so it’s a simple way to delight your users and distinguish your web app from the competition.
Check the demo and sample code at https://whatpwacando.today/wake-lock
New in Chrome 135: speech recognition for audio files and Invoker Commands
Chrome 135 now ships with support for speech recognition on a MediaStreamTrack, which means you can now recognize speech from an audio stream coming from an audio file or any other audio stream. Before, you could only recognize speech from the microphone.
For example, web apps can now recognize speech from the audio track of a video stream and provide live subtitles.
Check the demo.
Invoker Commands enable web apps to assign behavior to buttons without JavaScript. For example, you can now open and close popovers and dialogs using only the command and commandfor attributes.
For example, you can now show a popover like this:
<button commandfor="popover" command="show-popover">
Show popover
</button>
<div id="popover" popover></div>
The list of built-in commands (“show-popover” in the example) is currently limited but will be expanded.
Check the demo.
Declarative Web Push in Safari 18.4
Safari 18.4 on iOS now supports Declarative Web Push for installed PWAs, which enables PWAs to receive and display push notifications without a service worker.
The benefit of this is that the PWA doesn’t have to provide any code to display the notification. This saves battery power and also prevents that a notification is not shown when there is an error in the code that handles the delivery and displaying of the notification.
When a service worker handles the display of the notification through the push event but fails to do so because of an error in its code, Safari will revoke the push subscription after a few attempts. This is clearly undesirable and a bad user experience.
When using Declarative Web Push, the browser implicitly handles the delivery and display of the notification, so it’s always shown.
On the client side, the only difference is that the PushManager
object is now accessible on the window
object besides the ServiceWorkerRegistration
object. The code to subscribe to push notifications remains the same.
On the server side, the notification payload for a declarative push notification needs to contain the following required member (a homage to RFC 8030):
"web_push": "8030"
If the payload contains this member, the browser will take care of displaying the notification. If it does not, it will be handled by the push event handler of the service worker.
The notification member of the payload is largely the same:
{
web_push: 8030,
notification: {
title: 'Declarative Web Push',
lang: "en-US",
dir: "ltr",
body: message,
navigate: "https://whatpwacando.today/declarative-web-push",
silent: false,
requireInteraction: true,
vibrate: [100, 50, 100],
app_badge: "1",
actions: [
{
action: "confirm",
title: "Confirm",
navigate: "https://whatpwacando.today/declarative-web-push"
},
{
action: "deny",
title: "Deny",
navigate: "https://whatpwacando.today"
}
],
data: {
name: "What PWA Can Do Today"
}
}
}
The navigate
member contains the URL that will be opened in the PWA when the notification is tapped. The actions
array specifies the buttons that can be shown in the notification, but this is not (yet) supported on iOS, just like requireInteraction
and vibrate
.
Check out the demo and sample code at https://whatpwacando.today/declarative-web-push