Getting high Progressive Web App score on React and Material-UI

Denis Degtiarev
Sep 24, 2017 · 5 min read
Image for post
Image for post
Image by Outware

The fast and progressive web application is a must-have standard for recent front-end development. Meet both progressiveness and performance sometimes become not so easy. Single page application usually comes with a heavy JavaScript code bundle. Client’s device has to load it before the application can be interactive.

Google tries to help developers estimate application performance providing Lighthouse utility. It can be found on Audits tab of Google Chrome developer tools panel. Below are some basic techniques I have used to get a high score for example application based on a stack of React, Redux and Material-UI.

Split your code bundle

Many web bundling tools like Grunt, Gulp and so can do that job. Webpack does it in a more elegant and simple way providing many features and techniques.

First, separate heavy vendor code to the vendor file:

To understand which libraries are candidates to re-settle try great webpack-bundle-analyzer package. We also have to say webpack to collect vendor data from assets:

Then, move independent components to the separate chunk files. You can define new webpack entries for them or include them in your code through bundle-loader plugin:

Finally, we can ask webpack to find a common code in our chunks and separate it as well:

Use lazy loading

Lazy loading is one of the beneficial techniques used for performance optimisation. Our chunks, included by bundle-loader, will be loaded on a router initialization. We can use a trick to lazy load them on demand. Instead of pointing out route to chunk component we can point it out to bundle wrapper component:

Then we can use bundle wrapper to initialize router:

import RegLoader from 'bundle-loader?lazy!user/containers/Registration';
import SignInLoader from 'bundle-loader?lazy!user/containers/SignIn';
const AppRoutes = () => (
<Router>
<App>
<Route path='/register' component={bundle(RegLoader)} />
<Route path='/login' component={bundle(SignInLoader)} />
</App>
</Router>
);

It is also a good idea to load component’s content after rendering component itself:

Use JSS

Single page application has to load static CSS before initial rendering. That means we have to load more than 100 Kb, increasing the initial time of interaction. We can do better with JSS including only necessary styles only when they are actually needed:

Implement Service Worker

Service Worker is a light and powerful middleware between application and server. It uses Javascript and allows you to control any request and response. For us, it means we can control which assets can be cached, when and why. As a further application, we can handle requests to a server when a network is not available. Then send these requests when the application goes online.

First of all, let’s define example caching strategy we would like to implement:

  • store only GET requests to our server;
  • exclude requests to API to get actual data;
  • hash static assets (CSS, js, fonts, etc.) by content and store them to permanent storage;
  • store HTML responses to temporary storage until the next build of the application.

Getting static asset names hashed by content can be done by Webpack:

In that case, we haven’t track file updates, if the file is not in the cache we have to cache it:

On first initial loading, service worker has to be installed. To do it right we have to provide a list of resources to cache. Service worker registers its instance only on its success caching. But our assets are hashed and we can get hashed file names only on a webpack building stage. To help us inject hashed assets we can use inject-assets-webpack-plugin:

const webpackConfig = {
...
plugins: [
...
new InjectAssetsWebpackPlugin({
filename: 'public/worker.js',
},[
{ pattern: '{hash}', type: 'hash' },
{ pattern: '{hostname}', type: 'value', value: config.app.hostname },
{ pattern: '{api_hostname}', type: 'value', value: config.app.serverUri },
{
pattern: '{files_to_cache}',
type: 'chunks',
chunks: ['app', 'vendor', 'meta', 'AppBarContent'],
files: ['.js', '.css'],
excludeFiles: ['.map'],
decorator: fileNames => fileNames.join('\', \''),
},
]),
...
],
...
};

Conclusion

Building modern and progressive web applications become simple if we try to do it right. Sticking with techniques observed upper can give us PWA score close to a maximum:

Image for post
Image for post

Hope this article can help someone to save hours of finding a right way to build Progressive Web App. A source code of a complete example available on my GitHub. A working example can be found at altbit-dev.co.uk.

Image for post
Image for post

Appreciate your support. Feel free to write me anytime.

DailyJS

JavaScript news and opinion.

Medium is an open platform where 170 million readers come to find insightful and dynamic thinking. Here, expert and undiscovered voices alike dive into the heart of any topic and bring new ideas to the surface. Learn more

Follow the writers, publications, and topics that matter to you, and you’ll see them on your homepage and in your inbox. Explore

If you have a story to tell, knowledge to share, or a perspective to offer — welcome home. It’s easy and free to post your thinking on any topic. Write on Medium

Get the Medium app

A button that says 'Download on the App Store', and if clicked it will lead you to the iOS App store
A button that says 'Get it on, Google Play', and if clicked it will lead you to the Google Play store