Improving Client-Side Performance
This document discusses techniques that you can use to build a highly performant Progressive Web App (PWA). We will focus on client-side performance: how the PWA loads and runs in the user’s browser. We’ll also show you how to identify and resolve common client-side performance problems.
Building a performant PWA
This guide will focus on recommendations for optimizing network performance. Issues with network performance have a much greater impact on PWA performance than any other issues. There are a variety of factors that influence the network performance of server-side rendered PWAs.
Ordering of network requests
Often, the main culprits for poor network performance include making:
- Non-essential network requests before critical network requests
- Critical network requests too late
- Critical network requests in series instead of in parallel
- Extra network requests
As a result, it’s critical to consider not only what you are requesting, but how you are requesting it, and how it relates to other requests happening on the page. If your request ordering is optimized, you can load the same number of requests in much less time.
To do this, you will first need to review all of the requests that you are making when the page loads. What are the dependencies between these requests? What is the relative importance of each request?
Next, order these requests so that the most critical ones are made up front. Whenever possible, make requests in parallel.
To identify requests that are made in series, look for cases where promises are chained like so:
return connector.getBasket().then(() => connector.getCustomer()).then(() => connector.getContent());
When you find code like this, it’s important to ask: do all of these requests need to be made one after another? You might find that you need to wait for all of the promises to complete, but the order that they execute in doesn’t actually matter. In that case, you may be able to use Promise.all instead. Promise.all allows you to wait for the result of several promises, without making the requests in series.
Lazy loading allows content that is not critical for the initial page load to be loaded later on, at the time it becomes important or visible. Lazy loading helps improve your PWA’s performance by reducing the amount of data that is initially being requested and loaded, allowing the page to load faster while saving on bandwidth. A great example of a component that uses lazy loading is the Image component from the Mobify SDK. The Image component uses a placeholder graphic created with CSS. This means that the browser doesn’t need to request an image file to render the placeholder. When the image comes into view, the placeholder is replaced with the real image. For a look at lazy loading from a design perspective, please review our guide to Performant Product Loading Strategy.
You can also apply the concept of lazy loading to third-party scripts. Scripts such as product reviews provide a good opportunity for this. Product reviews are often hidden from view when the product detail page first loads. The reviews might be below the fold, or they might be hidden inside an accordion. In either case, you can delay downloading and executing product review scripts until the reviews would be visible. This improves performance in two ways: it reduces the amount of data you need to load in order to render the page, and it means that the CPU isn’t busy executing the product review script when it could be executing scripts more critical to loading the page.
When analyzing your network traffic, look for duplicated network requests. One way of eliminating these requests is by caching the response within your connector or within your state management system. (For more information about connectors, visit our Commerce Integrations docs.)
For example, imagine that you wanted to reduce the number of requests to a
getProduct API endpoint. Currently, whenever the user navigates to a product details page, the PWA makes a request to
getProduct, even if the user was simply returning to a page they had already visited. In this case, you may want to cache the response for a particular product for a certain period of time. That way, if the user navigates back to the product while the response is still fresh in the cache, you avoid making another network request.
Avoid web fonts
Web fonts are custom fonts that must be downloaded by your PWA prior to being used. Generally, web fonts are loaded at the same time the app is being loaded. This means that the font is competing with other critical resources for bandwidth. Depending on the implementation, text may not render until the web font has loaded.
On slower networks, custom fonts can leave your users waiting. On slow 3G networks, removing custom web fonts can improve your page load time by 1-2 seconds. Whenever possible, use standard web fonts instead. These are fonts that are already supported on your users’ systems and don’t require an additional download.
Performance goes beyond your site’s measured load and transition times. How the shopper perceives the speed of your site is critical for engagement and conversion. Follow the principles discussed in the article, Designing for the Appearance of Speed, in order to improve the perceived performance of your PWA.
Providing the optimal image size for the device in use can help to reduce the amount of unnecessary information in the request. For example, when mobile devices request images at a higher resolution than they need, it adds additional request time and data which can slow down the PWA’s overall performance. Some content delivery networks (CDNs) allow for image resizing, which can help ensure images are well suited for the user’s device, cutting down on redundant request time and data. Always leverage the CDN to serve an image that is appropriately sized for the user’s device.
SVG icons can sometimes contain unnecessary metadata, comments, and hidden elements. Removing any unnecessary metadata or comments from SVG icons can help reduce the file size of your SVG images. The Icon component is designed to work with an SVG spritesheet, and the tools for generating and optimizing your spritesheet are built into your project’s starting point by default. To use it,
- Place any SVGs you’d like to include in
- Run the command
npm run build-spritesto optimize the SVGs and include them in a single spritesheet.
- You’re done! Now those optimized SVGs can be rendered with the Icon component.
For more information, refer to the Lighthouse guide, Optimize Images.
Evaluating PWA performance
A variety of tools can help you troubleshoot your PWA’s performance. This section will describe our recommended tools and their role in troubleshooting PWA performance issues.
Lighthouse is an auditing tool from Google that grades your PWA on performance, among other factors.
When running a performance audit, review the Performance section of the Lighthouse report. Typically, this section will contain a list of factors that are negatively impacting the performance of the PWA.
Generally, Lighthouse will tell you whether there is a problem or not. You’ll need other tools to dig into why there is a problem.
In order to get stable results, we recommend running Lighthouse as part of the continuous integration on a project. This can be set up using Lighthouse CI. If you must use Lighthouse without continuous integration, it’s a good idea to use the following settings under the Audits tab in Chrome Developer Tools:
Web Page Test
Web Page Test is a synthetic testing tool you can use to test how the PWA will load on a variety of devices.
Testing a server-side rendered PWA bundle is as simple as pointing Web Page Test to the PWA’s domain. However, if the PWA target is configured with IP Whitelisting, then a few extra steps are required. If that’s the case for your project, follow these instructions:
Reach out to your Mobify Support Team and ask them to add
22.214.171.124/32to the whitelisted IP addresses for your project.
- This IP is the static address for the devices available from Dulles, VA. If you want to test in other regions, you will have to figure out those IPs. One way to find out an IP is to point Web Page Test at this Google search.
In Web Page Test…
- Copy the URL
- Paste that into the Website URL field on Web Page Test
- Run the test
Web Page Test will now be able to run its tests on the domain without any issues.
Note: At this time, it is not possible to test bundles that are not published to a target. Bundles must be published to a target and then tested. For more information on publishing bundles to a target, read our guide, Pushing and Publishing Bundles.
Evaluating the Results
Generally, most performance problems will arise from the network. This will be orders of magnitude slower than any issues arising from the device.
As you review the results, keep an eye out for the following sources of poor performance:
- Early network requests for non-critical resources
- Critical network requests that are made late
- Critical network requests that are made in sequence (rather than in parallel)
- Too many network requests
- Requests for things that are either really big or that load slowly. In terms of size, look out for JS files that are 500 Kb or larger. In terms of speed, look for a time to first byte that’s greater than 2 seconds on a slow 3G network.
Webpack Bundle Analyzer can help you identify which modules are responsible for increasing the size of your project bundles. You can use it to examine which files and modules have been included in each of the files in your bundle.
By default, Webpack Bundle Analyzer is a command line tool that’s built into your project starting point. In most projects, you can run the tool using the following command:
npm run analyze-build
If that doesn’t work, check your project’s
package.json and verify that
analyze-build is defined in your scripts. If it is not, reach out to Mobify Support for guidance on adding it.
You will see something like this:
Once you’re up and running, focus your attention on the files
main.js. Within those files, you can use the tool to look for dependencies that are either duplicated, located in the wrong place, or those that can be lazy loaded. Below we’ll discuss each type of dependency in more detail.
If you see two or more dependencies of the same name inside
vendor.js, you’ve found a duplicated dependency.
One method for fixing this issue is updating your webpack configuration to use an alias. Use this alias to point all references made to the dependency to the same module in
node_modules. This should look like
For example, both
lodash-es might be used by different dependencies of a project. This would mean that both versions would appear within
vendor.js. You can use an alias to point all references made to
lodash to the
lodash-es version. This means that only
lodash-es will be included in the project.
Dependencies that can be lazy loaded
Some dependencies are only used for some parts of the PWA. If this is the case, consider lazy loading the dependency.
After reading this guide, we hope you can identify, investigate, and resolve performance issues with your PWA.