Optimizing Progressive Web Apps with Google Lighthouse and .htaccess

Google Lighthouse can be helpful in detecting if your web page is ready to be a Progressive Web App. Beware, as it warns us that the “Progressive Web App” portion will be deprecated in a future release.

Google Lighthouse Icon

Open up your dev tools and view the lighthouse tab.

Generate a Lighthouse report

The main things to look at are Mobile and Progressive Web App. Everything else can be left as-is. Click on the button labeled Analyzer page load. It may take a few moments, but you’ll end up with a nice report.

Lighthouse Report
Score Breakdown

We’ve already confirmed that the page installs as a PWA recently, so having a PWA pass is not a surprise. However, it could helped in troubleshooting earlier. What strikes me as interesting is that a simple “Hello World” application has a very low speed index. If we don’t address it now, it can only get worse as we add more content. What’s going on? Is this specific to a local development server? Does it have something to do with HTTPS validating our self signed certificates? Let’s take a look at a few more details.

Hmm… compression, minification, unused javascript, duplicate modules… all of this sounds familiar. Our build process already does compression, minification and tree shaking. I think I need to deploy to a production site and re-run the test. Before we do that, let’s review what else it is reporting.

It took 6.9 seconds to display the Hello World! header. That seems excessive. The bulk of that time is for a Render Delay. We’ll have to come back to that. It could be related to how long it took to download the other files.

Page prevented back/forward cache restoration. I suspect this has to do with my background webworker that was setup when I started using the Vite PWA plugin. Pending browser support… so, the browser is testing for something that it doesn’t support? That doesn’t make sense. Why tell me about a problem of your own making if I can’t do anything about it?

Time to learn about bfcache. Is this related to workbox?

Actually, let’s go ahead and deploy to a remote site.

Something is off. I uploaded my files. I see index.html is in the root folder, but its giving me 404 errors.

Four oh foured!

I modified .htaccess so that the directory points to index.html

<IfModule mome_module>
  AddHandler application/x-httpd-ea-php83___lsphp .php .php8 .phtml
</IfModule>
DirectoryIndex index.html

I created a custom 404.html page, but that file wasn’t found either.

It works now… I suppose I just needed to wait for the dns to synchronize with the correct server maybe? One thing I’m noticing is that every url displays the contents of index.html. If I go to 404.html – I see Hello World. I tried renaming .htaccess in case the problem was there, but all pages still lead to index.html.

Let’s forget about that for now. Back to lighthouse.

Well, now it’s giving the expected behavior for 404 pages.

Maybe not?

Ok – it looks like once Lighthouse is done with its report on a bad url, I can go back to index.html. Once I do that, I can’t go to any other page without showing the content from index.html. This seems to be something related to progressive web apps.

Lets go back to the root domain and test the progressive web app normally.

Fireworks! 100% across the board. I’m still seeing a few things to address with the diagnostics.

Odd. It seems that my browser extensions are showing up in the reports.

Let’s see if things change if I disable the AdblockPlus and AdBlock extensions. Nope. Let’s go ahead and disable all extensions.

Yes. That fixed a lot of stuff. I now have a red item

Let’s see what that’s about.

The JavaScript for the index page is 100% needed. So are the React-DOM and React scripts. Why is it flagging these files as unused? Serve static assets with an efficient cache policy.

Apache Hypertext Access

As suggested, I added a cache control header to .htaccess to cache most static assets for a year.

<FilesMatch "\.(css|js|png|svg|ogg|jpg|jpeg|gif|ico)$">
  Header set Cache-Control "max-age=31536000, public"
</FilesMatch>

Yes! Another problem addressed.

We are down to one issue now. Reduce unused JavaScript. The recommendation for React is to split JavaScript bundles with React.lazy(). I’m already doing that. …and defer loading scripts. Did I mark the index as deferred? No. Let’s fix that, build and redeploy.

<script src="src/index.tsx" type="module" defer></script>

Nope. Same problem. The compiled HTML removed the defer flag. Maybe the defer flag is a red herring.

Red Herring in The Secret of Monkey Island

Let’s go ahead and manually edit the server file to see if it makes a difference. Yea, it doesn’t make an impact.

Maybe we can get rid of the warning if the size is reduced. I’m noticing that the .br compressed files are not being served. Hopefully it’s as simple as setting up gzip in the .htaccess file.

<IfModule mod_rewrite.c>
RewriteEngine On
RewriteCond %{HTTP:Accept-Encoding} br
RewriteCond %{REQUEST_FILENAME}.br -f
RewriteRule ^(.+)\.(css|js|html|svg|xml)$ $1.$2.br [L]
</IfModule>
<IfModule mod_mime.c>
AddType application/javascript .js.br
AddType text/css .css.br
AddType text/html .html.br
AddType image/svg+xml .svg.br
AddType application/xml .xml.br
</IfModule>
Index with Brotli

Well… something changed. Why didn’t the browser decompress it?

Let’s look at the requested Accept-Encoding header: gzip, deflate, br, zstd. What is zstd? Hmm… it looks like Facebook has their own Zstandard (Zstd) compression. Brotli is Googles. Zstandard compresses faster, but Brotli compresses better. Okay, let’s stop looking at the squirrel and figure out what’s going on.

Squirrel!!!

Was the correct mime type header returned for index.html.br? Yes. Content-Type was text/html. Was a content-encoding header sent? Yes… uh… yes, but it’s gzip. Let’s add content encoding for our different file types. Here are two of them for HTML and JavaScript.

<IfModule mode_headers.c>
  <FilesMatch "\.html\.br$">
    Header set Content-Encoding br
  </FilesMatch>
  <FilesMatch "\.js\.br$">
    Header set Content-Encoding br
  </FilesMatch>
</IfModule>

And now I’m getting a blank page. The browser says that it failed to decode. Were the files encoded correctly to begin with? Hmm… we have only confirmed in the past that the files were created.

Every tool that I’m finding online wants Brotly as a text with latin characters. Some appear to be base64 encoded. When I open the file myself, I see special non-printable characters.

Text version of Brotli

That doesn’t look like it’s base64 encoded. That’s binary data. b64 is Upper/Lowercase letters, digits, plus, and a forward sign (64 characters), in addition to no more than two equal signs at the end for padding.

I’m starting to wonder something about that initial gzip header. Is my website trying to gzip the Brotly file? I logged into my website cPanel and found an app called Optimize Website grouped under Software. The compression is currently disabled.

As an added measure, I looked at the multi-php ini editor. The home folder, domain, and sub domain all have compression disabled.

So why did it initially set the Content-Encoding to gzip? Why can’t the browser decode what it has received? If I remove encoding all together, its still gzip encoded. Maybe I need to remove the encoding before I send Brotli.

<IfModule mod_deflate.c>
  <FilesMatch "\.(html|css|js|xml|svg)$">
    RemoveOutputFilter gzip
    RemoveOutputFilter DEFLATE
  </FilesMatch>
</IfModule>

Well… that’s not doing it. The files are still encoded as gzip.

I think the host may be trying to force gzip as a means to cut down on bandwidth. If that’s the case, it seems odd that they haven’t moved onto another compression algorithm to lower that bandwidth further.

BlueHost Blues

Perhaps its time to look at another host. Over the years, it’s always felt that they’ve been lagging. MySQL hasn’t been updated since 5.7, and that version reached the end of its life in October of last year (no support, no bug fixes). The current version is 8. I hear that BlueHost only offers later database versions if you pay for a different plan. The fact that they are using unsupported software may expose them (and myself) up to liability/negligence issues if something were to happen. Given how they lag behind in versions, I’m uncertain if its worth it to upgrade to a different plan since they could easily become outdated. Are there other plans using the latest version of MySQL, or is it stuck with an older version (but newer than mine). The same thing happened with their PHP support for years. Only recently we got the ability to change our PHP version ourselves via a new cPanel feature. The sites often felt slow – although they were just small sites. Given that BlueHost seems to have clamped down on Gzip, it’s just adding another incentive to move away from a service that is often outdated or behind in tech. The only thing they have going for them is that my experience with customer support has been phenomenal.

Cloud hosting is a bit intimidating with its cost structure. I was paying a lot of money over on Amazon every month until I disabled one of their default features (Canary) that they installed as they walked you through an orientation of setting up a static website. I now pay about a dollar a month.

I’ll need a domain name, ssl, static content, and multiple databases. I currently pay $240/year. That lets me setup unlimited sub domains, email addresses, php and multiple mysql databases.

It’s getting late. Perhaps the hunt for a new host should be left for another post.

Content Negotiations

Well… wait a minute.

ChatGPT to the rescue. Somehow it offered more configuration options in .httaccess in regards to negotiation and proxy servers. The browser was able read the compressed Brotli files. And… Lighthouse no longer complains about the Index JavaScript, React, or React-DOM files!

Here are the new additions to .htaccess

<IfModule mod_negotiation.c>
  Options -MultiViews
  AddEncoding br br
  AddType text/html .html
  AddType text/html .html.br
  AddType text/css .css
  AddType text/css .css.br
  AddType application/javascript .js
  AddType application/javascript .js.br
  <IfModule mod_mime.c>
    AddCharset utf-8 .html .css .js
  </IfModule>
</IfModule>
<IfModule mod_headers.c>
  <FilesMatch "\.(css|js|html|svg|xml)\.br$">
     Header set Vary "Accept-Encoding"
  </FilesMatch>
</IfModule>

Scrolling down, I see one more issue to resolve. Manifest doesn’t have a maskable icon.

The help page also links to Maskable.app to test your icon.

None
Minimum Safe Area
Circle
Rounded Rectangle
Square
Drop
Cylinder
Squircle
Pebble
Cylinder
Hexagon
Flower

They all look fairly well put together except for the odd shapes at the end. Although the shape is round, I made sure to leave some padding on the sides since some websites like to cut that area out. I’m assuming the image can be marked as maskable for all icons.

I made all icons purpose maskable, but now the report says that I don’t have any suitable icons of 144px or more. If set – the purpose must be set to ‘any’. Okay, let’s set them all to any.

Nope. Back to the maskable error. The any value doesn’t seem to include maskable. Let’s assign the largest icon (512×512) as maskable.

Huzzah!

Application

Next, we move onto our developer tools Application tab where we can view the manifest. We see new warnings that Lighthouse didn’t pickup on.

  • Richer PWA Install UI won’t be available on desktop. Please add at least one screenshot with the form_factor set to wide.
  • Richer PWA Install UI won’t be available on mobile. Please add at least one screenshot for which form_factor is not set or set to a value other than wide.
  • id is not specified in the manifest, start_url is used instead. To specify an App ID that matches the current identity, set the id field to /
  • Define protocol handlers in the manifest to register your app as a handler for custom protocols when your app is installed.
  • Define display-override in the manifest to use the Window Controls Overlay API and customize your app’s title bar.

Wrap Up

It’s getting late. We’ve got a few more things to do with the PWA before we are done. What did we do today?

  • Use Lighthouse to review issues with the code in general, and the PWA quality
  • Deploy build files to a host for better results (minification, load time, tree shaking, etc.)
  • Disabled extensions that were appearing in report results
  • Configured webserver to serve Brotli compressed files
  • Reviewed icon in different maskable states, and flagged it as a maskable icon
  • Got Lighthouse to pass all reviews with flying colors
  • Found that the Applications tab had a few warnings and notes regarding our manifest file in terms of Richer PWA Install UI, App ID, protocol handling, and window controls overlay

One response to “Optimizing Progressive Web Apps with Google Lighthouse and .htaccess”

Discover more from Lewis Moten

Subscribe now to keep reading and get access to the full archive.

Continue reading