PWA Support

Astro supports hosting Progressive Web Apps (PWA) which are basically single page apps (SPA) as implemented in the Mobify platform. The PWA has special logic to detect that it is in an Astro app. By coordinating with Astro we can achieve native-like navigations, reduce memory usage, and maintain fast back navigations.


Mobify PWAs provide a utility module for facilitating interactions with Astro. astro-integrations.js provides utility functions to determine if the PWA is running inside an Astro app and helper functions used when sending events or rpc calls over the JS-Native bridge.

Integrating a PWA into an Astro App

NavigationPlugins have built in support for PWAs. This support allows for the NavigationPlugin to support stacking web view for a PWA. Stacking web views in the NavigationPlugin is the standard behaviour for non-PWA sites. This stacking behaviour gives the website navigation the feeling that it is an actual native app navigation.

In order to integrate a PWA into a NavigationPlugin the PWA has to inform Astro when a PWA navigation is occuring and when the PWA has finished rendering. The "navigations" of a PWA are all done by javascript and not done by GET requests that a traditional website would use, thus the web view (and NavigationPlugin) have no way of knowing a navigation is occuring unless they are informed by the PWA.

Astro provides two RPC calls to enable integrating PWA navigations.

pwa-navigate informs Astro when a PWA navigation is starting. When Astro receives the pwa-navigate call from a web view contained by a navigation plugin, the navigation plugin stacks a new navigation item and moves the web view plugin to the top navigation item. It hides the web view with a loader image.

pwa-rendered informs Astro that the PWA has finished rendering the new components. When Astro receives the pwa-rendered call from a web view contained by a navigation plugin, the navigation plugin removes the loader image to reveal the newly rendered components.

astro-integrations.js provides two functions to facilitate calling pwa-navigate and pwa-rendered RPC calls. The functions in astro-integrations.js are pwaNavigate and pwaRendered.

pwaNavigate should be called when the route changes in the PWA. Inside a Mobify PWA it can be called from the for /. The onChange method for this <Route> should be defined as

let onChange = () => {

if (isRunningInAstro) {
    // Redefine onChange to enable Astro integration
    onChange = (prevState, nextState, replace, callback) => {
        if (nextState.location.action === 'POP') {
        } else if (nextState.location.action === 'REPLACE') {

        pwaNavigate().then(() => {

pwaRendered should be called when the components in the PWA have finished rendering. In Mobify PWAs pwaRendered is called from the componentDidMount function of the App component. As a backup Astro removes the placeholder image after one second if pwaRendered has not previously been called.

Updating Headers during PWA Navigation

pwa-navigate accepts an optional header parameter to allow the header to update during the PWA navigation. The header parameter accepts a structure defining what the associated header bar should contain when the navigation completes. The header parameter uses the same structure as the header option which is passed into NavigationPlugin.navigateToUrl.

const pwaNavigate = Astro.jsRpcMethod('pwa-navigate', ['header'])

pwaNavigate({centerIcon: {id: 'center', title: 'Hello'}})

Customizing Back Behaviour

In order to navigate back the native code (iOS or Android code) calls window.history.back() while it is manipulating the navigation stack. Unfortunately the Astro team has come across issues when manipulating the state using window.history.pushState() and window.history.replaceState(). In order to work around these issues Astro allows customizing the back behaviour during PWA navigation. Astro expects the back behaviour to be customized by extending Mobify's Progressive Web SDK compoenent ExposeApiBase and exposing window.Progressive.api.navigateBack().

Here is an example of the necessary customization:

import ExposeApiBase from 'progressive-web-sdk/dist/components/expose-api-base/'
import {browserHistory} from 'progressive-web-sdk/dist/routing'

class ExposeApi extends ExposeApiBase {
    buildProgressiveApi() {
        return {
            // Keeping the navigate function
            navigateBack: () => {
                // Removing mysterious null state and then navigating back
                // Null state is caused by a bug in the native implementation 
                // of window.history
                if (history.state === null) {
                    window.setTimeout(browserHistory.goBack, 0)
                } else {

export default ExposeApi