Is your Progressive Web App Lazy? How to read and improve your PWA Performance

Hey, everyone.


Today you will learn if your Progressive Web App is lazy, and if it is, you will learn how to improve its performance.

Heads up, if you're here, you probably are interested in performance in general.

Even though we will talk about Progressive Web Apps, most of what you will see here, it can be implemented on a traditional web App too, so you can stay!

Let's start with the basics.

If you never heard about Progressive Web Apps before, (which I don't think so), PWAs are a type of mobile App delivered through the web.

So it basically builds with the web technologies that most of us love - HTML, CSS, and of course, JavaScript.

The good news is that this Approach brings a native app-like experience to the browser, which means that Progressive Web Apps are cross-platform.

And some of the features that we can mention in a Progressive Web App are: it should be responsive, then it should be adapt to different viewport sizes.

It should be fast because you know, otherwise users won't be happy (and we don't want that), and it should be working regardless of the state of connection.


Offline too.

Like when you're traveling on a plane and you don't have wifi anymore.

Yes, remember those times traveling on a plane?



Going back.

Remember, Progressive Web Apps should be adaptive to different browsers and its versions.

All these features are going to give us an idea about what we should measure in terms of performance.

So we will make sure today that our web app is responsive, fast, and offline.

Technically talking, a Progressive Web App should have: HTTPS, so users can make safe and trusted transactions; a Manifest File, which is going to make our app discoverable and installable; and a Service Worker, that is going to give us a chance to have offline capabilities.

Another reminder: using HTTPS is mandatory to start using service workers.

So keep in mind this as the first step to be your PWA.


We know some of the concepts already.

So now our first step to figure out...

figure out if our PWA is lazy...

is measure!

Measure, because, otherwise we're not going to be able to improve what we cannot measure.

We need numbers to compare and therefore improve our performance.


Well we're going to use our old friend Lighthouse, which is a tool that most of us are familiar.

Wow, that would be a dream score, right?

Legend says that developers that achieve this score have super powers.

Well, not really, but I'm pretty sure that at least they follow Lazy Load conference.

Anyway, we will use some of these metrics to measure the performance of our PWA.

Again, every metric matters, but we will make a focus into the following for PWAs: First one is Cumulative Layout Shift.

Second: First Contentful paint.

And: Time to Interactive.

I will explain the reasons behind this decision coming up next.

I would like to clarify that as you saw before, there's a section in particular for PWA on Lighthouse.

These checks indicate how optimized is your PWA.

Some of them are related to improve its performance, like providing an offline page, and others are related to some of the features that belong to Progressive Web Apps, like being installable or having HTTPS.

Once we know what we should keep in mind in terms of metrics, what we will do is try to improve each of the capabilities of a PWA.

Let's start with the first one: Responsive.

The first metric that we will see here is trying to achieve a responsive design.

So therefore, we're going to have to check Cumulative Layout Shift.

This one is really important because it's a user-centric metric.

It measures visual stability.

For example, how many times have you been on a website and suddenly you're experiencing some changes in the layout?

It's like the page moves by itself!

Let me show you an example so you can see it more clear.

Last year, we started building Samsung Global Goals PWA in collaboration with United Nations.

Our mission was: bring the native app that already exists in Android to other platforms because, you know, if you build for the web, you're probably gonna reach universally to the users.

This web app basically loads dynamic content like cards, images and text.

Here it looks pretty smooth.

But what happens if we test this in a really low connection?


Yes, this happened.

As we had dynamic images loading and we tested how it will look like in a bad connection, our cards changed the size while it completes the download of the resources, and it looks really, really bad.

It truly deserves an apology to the user, and a fix of course.

A friend of mine, a web-hero kind of role told me, "Your Web App shouldn't move a single pixel, not even ONE." He was completely right.

So how do we fix this?

It's pretty simple.

Always set up 'height' and 'width' in your images.

And if you have dymanic content like us on our web app, or for example, you're going to load ads later, what you need to do is use placeholders.

This is what we did.

We used placeholders, and actually this placeholder has a background color so the images will load, but the cards are already fixed.

Nothing is going to move in this web app.


We can tick one metric already, CLS done!

Next up and continuing with responsive design is CSS Optimization.

When we build a responsive design, we usually rely on external CSS libraries or resources.

There are a few things to optimize so we can improve the way that our websites run their styling and especially avoid performance issues.

A few techniques related with CSS Optimization includes: reduce render blocking CSS, eliminate unused CSS rules, and minify CSS.

I will focus on removing unused CSS, since it's the one most common issue that we had when we rely on external resources.

Actually you can check if you have unused CSS on Lighthouse.

For example, here, it's telling us that we are not using around 95% or more of our CSS.

And that's a lot.

The fix is that if you're using, first of all, if you're using an external library check if you have the option to export just the components that you will use instead.

Other option is taking a look at how your web app is using your resources.

Let me show you.

You can try this in Chrome Dev Tools.

You can press 'control', 'shift', 'p' to open the command line and type 'coverage'.

A new tab will appear and here, you will find how much percentage of your resources are being used.

For example, the following CSS library has almost 40% of unused lines, same with other resources.

And then if you click in a particular one, you can even see which lines are used or unused indicated by different colors.


There are even other tools that can help you identify unused CSS.

And not only that, reduce your CSS file size.

I can recommend PurifyCSS, which is a JS library that you can install with npm or directly use PurifyCSS Online.

You copy and paste your CSS, your web app online, and it will not only tell you the amount of code that you're not using, it will also create a new CSS file that you can use later with just the code that you currently need.

And with these, we already have some of the main things covered related with responsive design.

I hope that it was useful.

Now let's get a little bit more spicy and talk about the last features of a PWA.

I decided to group these two together.

Fast, and offline because both are kind of related somehow.

Let's review how a traditional web app loads.

A traditional web app loads like this: First, we have a 'Splash Screen', which is the first visual users see when an application is launched.

Second, there is a First Paint.

This is the time between navigation and when the browser renders the first pixels to the screen.

And finally, our web app is ready because all functionalities are available and have been loaded.

So therefore our web app is fully interactive.

Now for PWAs, even though they work in the same way, there are a few things to keep in mind.

Let's start with the splash screen.

Ideally you will need a splash screen because your PWA will load fast.

But again ideally, because you can never take for granted that you will have good connection - like never, ever take for granted that - the splash screen can help to emulate that something is happening while the rest of the page is loaded.

Therefore to the user it will feel much faster than a site that's just showing a blank page for several seconds.

So to build a splash screen for a PWA for some browsers you will need a web manifest file.

A web manifest is a JSON file with a web manifest extension, and that's it.

It has different properties and values to indicate how your web app is going to launch, like the name, display, and background color.

Between these values, you can find icons.

The web manifest icons property is an array of icon objects with three properties: src, type and sizes.

This is the information that most browsers will use to generate the icon of your web app to launch it and sometimes build a splash screen.

Pretty simple and straightforward.

A JSON with some properties and then the browser is going to take those values to make it installable and now a splash screen.

Something that I need to clarify is that currently Chromium browsers display a splash screen only on Android devices, not on the desktop.

And also if you want to use a splash screen for Apple, that's a different story.

You need to use something different, not the one that we are telling you here with the manifest file.

Going back to our PWA metrics on Lightouse.

If we add a manifest file with the properties mentioned before, we already completed some of these checks.

So let's come back to the traditional performance metrics.

We already covered the splash screen.

So right now we're going to take a look at what happens during First Paint and then when our web app is fully interactive.

Let's start with First Paint.

First Paint is really important for PWAs, because it will measure the impression that a user experience in terms of load speed.

Again, the impression, as the idea of a PWA is not only be a fast web app but also a fast experience, a good experience.

Therefore, to achieve that, we're going to use some techniques.

Then with First Paint, for example, what we can track is how long it takes to show the splash screen.

But be careful - this is not the same metric as the moment when the user can actually interact with the app.

Time to Interactive is the metric that will let us know that because it will give us the real amount of time that the web app needs to become fully interactive.

And here is when we start talking about..


Service Workers.

Service workers are the responsible ones to enable reliability in unpredicted network conditions.

They act as proxy servers between web applications, the browser, and the network (when it's available.) Wait!

You're probably wondering, "but isn't that the same as HTTP caching?" Or, "What's the difference?

Browsers can cache requests already".

But the difference between service workers is that they offer to developers control over exactly what and how caching is done.

Let's take a look.

Here is an example of a service worker with a cache first strategy.

Yes, because there are different strategies.

They intercept network requests and take appropriate action based on whether the network is available or not.

With the cache first strategy, it checks the cache before going to the network.

Let me show you some code to make it even more clear.

Here is an example of the first step in a cycle of a service worker, where we will be caching some specific resources like CSS, images, or even an HTML page.

The first step in a service worker is the event install.

This is really important because there, it will be caching the different files that we provided above.

The magic of a service worker lays on being able to not show that awful offline page when our connection is down, we can now provide a custom offline page when there's no connection using service workers.

Previously, we cached resources during the install events, including one offline HTML page which can be whatever you want.

Once we got that, the next step is tell the service worker that it should manage and do something with that content.

There is an event called 'fetch' that allows you to do the magic.

A featch event runs every time any document in the scope of a service worker is fetched.

For example, when a page is loaded, or when a resource like an image is added or an API call is made, and let's analyze this block of code now.

Here, what we're saying is: "If there's any error trying to make an HTTP request, show the default offline fallback page." And with this, instead of leaving the user boring without any support when we don't have connection, we can show an offline page that can have a message, or there are even examples of websites that implement games, for example, while users are waiting for their connection, so user's don't get bored.

The most important thing here is: Don't leave your users without any support.

Keep your users engaged and happy like this puppy.

You can even go more specific with service workers.

Let's say that you're looking to receive an image from an external API, but you want to use a backup image if there's something wrong with that response.

You can manage this with service worker using HTTP headers to filter if you will receive an image.

And if there's an error, you can even specify what you would do with that.

Here, lets come back to some other block of code...

What we're saying is: "If I'm receiving an image, we're going to fetch.

If there is an error with that request, and that request is related with an image that is - a COVID image is the one that I'm requesting- show my default cached COVID image instead.

And, if it's not a request related with COVID, just show this other default image." I'm telling you, you can go very, very specific with service workers.

And with this, you can cover up many different situations and therefore improve your user experience.

Adding a service worker and an offline page, we also check our PWA Lighthouse marks.

Cool, isn't it?

So right now we have fast and reliable because we add the service worker with an offline page.

Besides that, because we add the service worker, it's going to improve our caching strategy and it's going to be faster in any mobile network.

Besides that we also implemented a manifest file, which are going to make our PWA installable.

And it's already optimized because again, before having a service worker, you need to apply HTTPS.

So it's already optimized.

Now, why not?

It's going to help us to get a better performance score.

Of course, you're going to follow, you're going to follow the tips that you will see during this conference and with these best practices and also going specifically about PWAs, we now achieve this kind of score.

I hope all this information was useful for you and you can follow these tips so your PWA is not that lazy anymore.

I'm going to leave my social media accounts and the link to the slides here.

You can follow me in different social media accounts and also our Samsung internet links.

Thanks for watching.

Is your PWA lazy?
Web Directions


Background images of abstract shapes

>_ Disclaimer

Background graphic of a webpage blank template overlaid on a grid

- What is a Progressive Web App?

Progressive web applications are a type of mobile app delivered through the web, built using common web technologies including HTML, CSS, and JavaScript

Image of the Progressive Web Apps logo, the three initials PWA overlaid on a smartphone screen

Series of three metrics that a Progressive Web App should feature:

Responsive:: Different viewport sizes. Associated nested image icons of a desktop computer, tablet, and a smartphone

Fast: Otherwise users won't be happy. Associated image icon of a rocketship

Offline: Web App is still working regardless of the state of connection. Associated image icon of an airplane in flight

>_ Technically talking

Background graphic of a webpage blank template overlaid on a grid

>- Building your PWA with...

Series of three features Progressive Web Apps are built with:

HTTPS:: Safe and trusted transactions to the user

Manifest File Discoverability. Installable

Service Worker: Offline Capabilities

01 Measure

We are not able to improve what we can not measure


Image of the Lighthouse tool logo

Image of the Lighthouse API Performance view testing a Progressive Web App and showing a range of performance metrics (Performance, Accessibility, Best Practices, SEO) all scoring 100 percent. Fireworks are going off in the background

>_ We will analyze

Image of a row of checkboxes with the following labels:

□ Cumulative Layout Shift
□ Total blocking time
□ First Contentful Paint
□ Time to Interactive
□ Largest Contentful Paint

Of this list, Cumulative Layout Shift, First Contentful Paint, and Time to Interactive are checked in blue

Sidebar image of a Funko Pop vinyl figurine based on the DC Comics Justice League character of The Flash

PWAs Lighthouse

Screenshot image of the section in the Lighthouse interface that is dedicated to Progressive Web Apps.

02 Responsive

Cumulative Layout Shift (CLS)

A PWA with dynamic content and images.

Animated demonstration of how a PWA handles Cumulative Layout Shift responsively. The demo shows a scrolling view of the Samsung Global Goals PWA which was built in collaboration with the United Nations. The web app loads dynamic content like cards, image and text to create a smooth scroll and eliminate layout shifts

I'm so sorry - >

Image showing a shot of what happened when the same PWA was tested on very slow connection. The cards here changed size while resource downloads were being completed, making the screen and its content jumbled and unreadable

Cartoon image of a man speaking into a large chat bubble

“Your web app shouldn’t move a single pixel, not even ONE”

–Kenneth Rohde, friend and web hero

<img src="images/ul_elements/icn_updates.svg" width-"48" height="40" class="menu-icon" alt=""/>

Code demo using image width and height

Fix: add a placeholder.

Animated demo of the same page loading, this time without cards being affected. A finger pressing a button emoji is beside the demo

Cumulative Layout Shift (CLS)

Animation of a large green "tick" appearing beside CLS indicating that metric has been optimized

CSS Optimization

>_ CSS Optimization

  • Reduce render blocking CSS
  • Eliminate unused CSS rules
  • Minify CSS

>_ Removing unused CSS

Screenshot of the Lighthouse interface showing an option to remove unused CSS by removing dead rules from stylesheets and deferring the loading of CSS not used for above-the-fold content to reduce unnecessary bytes consumed by network activity. The interface shows that these fixes will save 95% of unused CSS

Screenshot image of the prior Samsung demo page alongside an animated Chrome Dev Tools interface open in the "Sources" tab showing a list of page resources and corresponding percentages of how many lines of CSS or other resources are being used or not

>_ npm install

Screenshot of the Purify CSS interface which identifies unused CSS code and gives suggestions for modifications to CSS file sizes. Purify CSS is a JS library that you can install with npm or directly use Purify CSS online. It will not only show you the amount of unused code it will also create a new css file with just the code that you currently need

03 Fast and offline

How a traditional web app loads

Image of three colored circle points representing the sequence of a traditional web app load. From left to right: Splash Screen, First Paint, and Fully Interactive

Splash Screen

Helps to emulate that something is happening while the rest of the page load. Better than a blank page!

Image of a smartphone screen showing the Samsung Global Goals Splash Screen, which shows the SGG logo while the rest of the app loads, indicating to the user that the page is active


    "name" : "Samsung Global Goals",
    "short_name": "Samsung Global Goals",
    "description": "Samsung Global Goals Web App",
    "start_url" : "",
    "lang" : "English",
    "display": "standalone",
    "theme_color": "#3d3d3d",
    "background_color": "#f2f2f2",
    "orientation": "portrait

Code demo of a web manifest file

    "icons": [ {
    "src": "/images/icn_goals_colored.png"
    "sizes": "192x192",
    "src": "/images/icn_goals_colored.png"
    "sizes": "512x512",
    "type": "image/png"

Code demo of the 'icons' feature

PWAs Lighthouse

Animated screenshot of the Progressive Web App metrics page in Lighthouse, now with green checked boxes next to the following three metrics:

Web app manifest meets the instability requirements
Configured for a custom splash screen
Sets a theme color for the address bar

How a traditional web app loads

Image of a horizontal timeline representing the sequence of a traditional web app load. At the top left above the line is: Splash Screen. At the top right above the line is: Fully Interactive. Below the line between these two events is: First Paint

First Paint

First Paint, this one is really important for PWAs because it will measure the impression that a user experience in terms of load speed.

Time To Interactive

Service Workers

A service worker enable your web app to be reliable in unpredicted network conditions

Wait, what about HTTP caching mechanisms?

Cartoon image of two figures surrounded by floating question marks

Service worker

Cache first

Flow chart of a service worker with a cache first strategy. The service worker is indicated at the top of the diagram (represented by clock and cog icons), and connects via a downward facing arrow to a Cache at the bottom of the diagram (represented by document icons). The Cache connects to the left with the Page (represented by a paper icon) which in turn connects via reciprocal arrows back to the Service worker. The Cache also connects via reciprocal arrows to the Network icon on the far right of the diagram (represented by a www connectivity icon).


Code demo of how to implement a cache first strategy on service workers. Pink arrows are overlaid on the 'var' and 'self' tags

var urlsToCache[

self.addEventListener('install', function(event) {
// Perform install steps
	event.waitUntil('MYCACHE').then(function(cache) {
			console.log('Opened cache');
			return cache.addAll(urlsToCache);


Offline Fallback Page

Code demo of how to use a service worker to implement a custom offline page when there’s no connection. Pink arrows are overlaid before the 'fetch' and 'return' commands

/* Navigation Request, HTML +/
if (request.headers.get('Accept').includes('text/html')) {
		.catch(error => {
			//fallback page
			return caches.match('offline.html');

Keep your users engaged and happy

Image of pug puppy running with a stick it its mouth. A user touch interactivity icon is overlaid to represent engagement

SW Requests

Code demo of more service worker code solutions with pink arrows overlaid on the 'if' requests

/* Request Image */
if (request.headers.get('Accept').includes('image')) {
		.then(function(response) {
			return response || fetch(request)
		.catch(error => {
			if (request.url.includes('/media/covid')) {
				return caches.match('/images/covid_pandemic.jpg')
			return caches.match('/images/raster/goals_header.jpg')

PWAs Lighthouse

Animated screenshot of the Progressive Web App metrics page in Lighthouse, now with green checked boxes next to the following three metrics:almost all of the metrics

Image of the Lighthouse API Performance view testing a Progressive Web App and showing a range of performance metrics (Performance, Accessibility, Best Practices, SEO) all scoring 100 percent. Fireworks are going off in the background

No more laziness (at least in our PWAs)

Image of a pug puppy laying on its tummy looking sleepy

Image of Laura's avatar wearing a facemask




Laura Moringo