Building a hybrid-rendered PWA
When we started to work on PWA Directory, one of our objectives was to create an Exemplary PWA according to the PWA Checklist. The checklist advises that, on a PWA, page transitions should not feel like they block on the network.
One of the reasons for this is that, when a PWA is opened from the home screen, it opens in standalone mode, the browser affordances that indicate to the user that a new page is loading are lost, and the user may feel that the application is frozen or that it didn’t receive the tap on a link.
Although it is possible to improve transitions with SSR, it may be hard to properly coordinate the transitions between routes, and it doesn’t allow for the use of more complex transition strategies, such as FLIP animations.
By moving the implementation to a pure client-side-rendered approach, where an empty App Shell is rendered on the initial load and the content is requested and rendered by the shell, it would be possible to improve the transitions.
But that would result in losing some of the advantages of the SSR approach, such as indexability and progressive rendering, besides being slower to render the content on the initial load, as an extra request has to be made for the content.
On the first load, the App Shell is rendered, but instead of the content being empty, it is inlined by the server in the HTML (the Push in PRPL).
When an internal link is clicked, instead of doing a full navigation, the App Shell that has been loaded in the first load is re-used and the pre-rendered content is retrieved from the server, this time without the App Shell, and the content section of the page is replaced.
After the initial view, the router handles the navigations and, effectively, does Client Side Rendering. This makes it possible to implement better coordination when navigating between different routes, and implement more complex transition strategies.
The Service Worker
The hybrid strategy used by PWA Directory to load the content doesn’t depend on the Service Worker, so even browsers that don’t support Service Workers will benefit from the implementation.
So, when a user returns to the application, by navigating to a full content URL, the response is replaced with the cached shell, which in turn will trigger the client-side rendered flow to load the content. Check the PWA Directory: Loading content faster in the Application Shell for a more complete explanation of the implementation currently being used.
The second main optimisation is that, whenever a user triggers a navigation inside the application, the Service Worker intercepts the request on the fetch event and adds the response to the Cache so that, on the next time a navigation to that same content is triggered the Service Worker can load the content from the cache and delivers it instantly, without going to the network.
Using the Service Worker cache with the regular browser cache gives us more control and allows us to create more complex caching strategies. An example would be a strategy that returns the cached result if the result was added less than 1 hour ago, and tries the network otherwise. But, if the network fails, it can still use the cached result.
By implementing a hybrid rendering strategy it is possible to get the best of both worlds: Pages that load quick on the first load and play well with crawlers, while taking advantage of the Application Shell to speed up further navigations and to create nicer transitions.
As most things, there are a few tradeoffs. When navigating in the Application Shell, the page structure is being downloaded with the data, so the download size is larger than just fetching data from an API.
Also, when doing CSR, the ability to progressively render the page is lost, and long pages may actually take longer to show the content. Jake Archibald has a great blogpost with a hack to improve this. We explored this in PWA Directory, but the pages are small enough (about 4k) that there's no difference.
When viewing a specific page, a limited set of links is presented to users at a given time. In order to have an even faster navigation, it would be possible to pre-cache the internal links from the page, so that, when a user clicks on it, the corresponding page renders instantly. This is something that we are currently working on. Stay tuned for updates!
PWA Directory is an Open Source project. Want to report an issue, contribute or just read the code? Check out the source code on Github.