Optimize Your Site’s Speed: Convert Images to WebP Format

upgrading image files

In a recent post I discussed auditing existing websites for potential performance enhancements. In the process I discovered issues with the AntPace.com Lighthouse estimates (a topic I covered briefly in a post about progressive web apps).

Lighthouse performance estimate

My performance only scored 50 out of 100. One “opportunity” (estimated to save 2.16s) was to “serve images in next-gen formats”. AVIF (based on the AV1 video codec) and WebP are considered next generation because of “superior compression and quality characteristics compared to their older JPEG and PNG counterparts“. I chose to upgrade my images WebP (instead of AVIF) mostly due to greater browser support.

Before I upload images to this site, I usually run them through TinyPNG. Sometimes, I’ll also do some manual pre-processing in the GIMP, like cropping or scaling images down. I know that there  must be a better way to do all of that through a proper CI pipeline – but for now that is my low tech solution. Now that I need to come up with a new process for next-gen image formats, it might be a good time to explore a more automated solution.

Properly Size an Image for Your Website

Site speed is important for user experience and SEO. Having an image with unnecessarily large dimensions eats up bandwidth and leads to slow load times. You can open an image file in the GIMP and use the scale tool to reduce the size while maintaining the ratio. Once it is reduced, you can copy and paste it to it’s own canvas and save the file.

I write more about using this image manipulation program in another article about design for programmers

Convert multiple images to next-gen WebP file format

For converting single images, manually, one at a time I could use the GIMP or an online service. I used a command line tool on my mac called webp tools to bulk upgrade all of the files in an image directory.

brew install webp

For a single file I can use this command:

cwebp -q 80 software-education.png -o software-education.webp

To hit all of my image files in that folder I ran loops over the existing file formats. You should change this to include any other file formats used in your project.

for i in *.jpg; do cwebp -q 80 "$i" -o "${i%.jpg}.webp";done
for i in *.png; do cwebp -q 80 "$i" -o "${i%.png}.webp";done

I saved them to a new folder, so that I could easily delete all of the old files at once and then replace them. Alternatively, I can save them in the same directory and then run a command to delete all of the .png files: rm *.png

New folder amongst legacy image files

My website has been around for a while, so there are unused legacy media files. I manually searched my code base (ctrl + shift + f) for the file names listed in the various /image directories throughout my project, and deleted them if I found no references. (Maybe having so many disjointed image folders is part of a bigger problem with this project). If I did find it, I updated the file extension to webp. Later, I wrote a script to automatically find and delete unused image files from a project.

This change increased my Lighthouse performance score by ten points (I gained an additional two but upgrading MariaDB from version 10.2 to 10.4). I did this process is a few other directories of my project to complete the upgrade. I was going to convert my Apple touch icon files to a next-gen format too, but the documentation specify the use of the PNG format (good thing I checked).

WordPress and WebP

Having adopted WebP as the new standard file format for AntPace.com, I decided to upload .webp images to my posts – starting with this one. When I tried, WordPress complained: “This image cannot be processed by the web server. Convert it to JPEG or PNG before uploading.”

error from wordpress when trying to upload a next generation image file

This surprised me. I upgraded WordPress to the latest version (6.3.1 at that time), but it didn’t help. After further investigation, it looked like the problem had to do with the PHP image module gd.

I SSH’d into my EC2 instance to see what I could do. When I tried to install gd for my PHP version 7.4, I got a version mismatch error. It had to due with Amazon Linux 2 (the OS I run on AWS EC2) not supporting PHP 7.4 as it approaching or have passed their end-of-support dates. Every time I tried to installthe gd module, Amazon Linux Extras (the default Amazon Linux 2 package mechanism) would try to pull the version compatible with PHP 7.2. In an attempt to make it work, I manually disabled ‘amazon-linux-extras’, installed the Remi repository and made sure it was prioritized as my package manager. Still, “Packages skipped because of dependency problems”.

Amazon Linux 2 is at EOL.

The same thing happened when I tried using ImageMagick instead. This made me consider my Linux distribution. Not having gd installed what causing other problems when uploading media through WordPress (responsive image sizes are not being generated).

I had been wanting to upgrade the size of my EC2 instance anyway, so this might be the right time. I am considering Amazon Linux 2023 or a Bitnami image.  As you know, I’ll write a blog post about which I choose and the implementation details

Identify Unused Image Files in a Code Project

Searching for image files

I wrote a script to list (and optionally delete) the image files it finds in a directory that are not referenced any where.  I specified a single sub-directory at a time. If you do the entire project it may take long to complete.

#!/bin/bash

PROJECT_DIR="$PWD"
IMAGE_DIR="$PWD"

DELETE_FLAG=false

# Check for -d (delete) flag
if [[ $1 == "-d" ]]; then
    DELETE_FLAG=true
fi

# Find all image files
find $IMAGE_DIR -type f \( -iname \*.png -o -iname \*.jpg -o -iname \*.jpeg -o -iname \*.gif -o -iname \*.webp -o -iname \*.svg -o -iname \*.ico \) | while read img_file; do
    # Extract the basename of the image file
    img_basename=$(basename "$img_file")
    
    # Search for it in the project, excluding .zip files and .git directories
    result=$(grep -ril --exclude=*.zip --exclude-dir=.git "$img_basename" "$PROJECT_DIR")
    
    # If not found, print it or delete it based on flag
    if [ -z "$result" ]; then
        echo "Unused image: $img_file"
        if $DELETE_FLAG; then
            rm "$img_file"
            echo "Deleted: $img_file"
        fi
    fi
done

Be sure to edit the code to include the image file types that you want to target. Make the file executable before trying to use it:

chmod +x find_unused_images.sh

Run the script (without deletion)

./find_unused_images.sh

To run the script with the delete functionality:

./find_unused_images.sh -d

Find unused images in a project

In an early version, my script was leaving behind files that appeared to be completely unreferenced in the code base (I checked manually by searching the project via IDE). To troubleshoot, I ran grep from the command line:

grep -ril "experienceLogo2.png" "$PWD"

It turned out that those files were referenced in a zip file and in git objects (and therefore considered not unused). I fixed this bug by adding the flags –exclude=*.zip –exclude-dir=.git to my grep command in find_unused_images.sh.

grep to find a file reference within a directory
I had lots of unused stock images and old designs that felt good to purge. Now with generative image AI, I know I could create assets easily as I need them in the future.

I used this script during a project to upgrade my website to use next generation file formats. If you liked this post, read another one where I discuss managing graphic assets for a web project.

Update

Identify any unused files in a code project

This code can be changed to search for any kind of file. Here’s an updated version I used to see if some old Bootstrap files were being used any where:

#!/bin/bash

PROJECT_DIR="$PWD"
FILE_DIR="$PWD/css"

DELETE_FLAG=false

# Check for -d (delete) flag
if [[ $1 == "-d" ]]; then
    DELETE_FLAG=true
fi

# Find all image files
# find $FILE_DIR -type f \( -iname \*.png -o -iname \*.jpg -o -iname \*.jpeg -o -iname \*.gif -o -iname \*.webp -o -iname \*.svg -o -iname \*.ico \) | while read my_file; do
find $FILE_DIR -type f \( -iname \*.css -o -iname \*.js \) | while read my_file; do
    # Extract the basename of the image file
    file_basename=$(basename "$my_file")
    
    # Search for it in the project, excluding .zip files and .git directories and .xml directories
    result=$(grep -ril --exclude=*.zip --exclude-dir=.git --exclude=*.xml "$file_basename" "$PROJECT_DIR")
    
    # If not found, print it or delete it based on flag
    if [ -z "$result" ]; then
        echo "Unused file: $my_file"
        if $DELETE_FLAG; then
            rm "$my_file"
            echo "Deleted: $my_file"
        fi
    fi
done

This didn’t work perfectly. I had to add an exclusion condition for .xml files because my WordPress blog archive files were being highlighted in the search. (EOD, I ended up zipping the few .xml archive files anyway. I also keep them on an S3 bucket, but I enjoy redundancy.)

The file name “bootstrap.css” was being found in its own file.

Bootstrap source code

This, at least, gave me enough confidence to just delete the files manually. I can’t call this a perfect tool (and I don’t think it would scale well), but it is a utility for a practical use-case.

I saw other examples of it acting funny. For instance, I had an old stock photo file named ‘5.jpg’. It was coming up as being used in the project because, for some reason, that string was found in another image file – ‘jimmy.webp’.

using grep to search image file names

Debugging Mobile CSS on Chrome for Android

debug mobile css on Android

On this website (this very one you are reading) I have a resume page. On it, I display a timeline styled with CSS. I use the border-radius property to create circles for the years on that timeline. The years connect between <div> elements that represent my work experience.

timeline displayed on a resume webpage
An image can be worth a forty-two words

It looks great (at least,  I think so). Except on mobile. Specifically, on Android running Chrome. The circle appears oblongly squished.

Broken CSS on Android
Broken CSS on Android

I could not recreate this bug on my laptop, even when I used Chrome Developer Tools to simulate mobile. Not being able to reproduce a bug consistently is a very frustrating experience.

Chrome developer tools
I couldn’t recreate the mobile bug on my laptop. *Sigh*

USB debugging

I would have to use remote debugging via Chrome DevTools. The first step was to enable “Developer Options” on my Android device. I was using a S21 Galaxy Ultra. On this device I navigated to “Settings” -> “About Phone” -> “Software information”. Then I triple tapped the “Build number” and received a toast message “Developer mode activated”. This feels familiar, as it has been a similar process on previous Android devices that I have owned. I’d post a picture of my software information screen, but am worried that some of the data could be used maliciously by strangers on the internet.

“Developer options” was revealed in my “Settings” app. From there, I could turn on “USB debugging”

usb debugging in the developer options menu

Now, I could connect my Android to my MacBook. From my MacBook, I opened Chrome and navigated to chrome://inspect/#devices

view usb connected devices running chrome

As long as my Android also had Chrome running, I could see my device listed with any open browser tabs. Clicking “inspect” opened a new window on my laptop that mirrored my cellphone’s screen. When I scrolled on one, it reflected on the other.

Inspect mobile device from desktop

CSS Solution

I could inspect elements from the developer tools panel. This was exactly what I needed to debug the problem. And, the fix ended up being pretty easy. Here’s the original, relevant CSS:

.timeline .year {
  font-size: 1em; 
  line-height: 4;
  border-radius: 50%;
  width: 6em; 
}

The issue was ultimately with the width and height properties. The circles look correct on my desktop browser because the width and height rendered to nearly identical – 64px by 66px. To get a circle with CSS you need an element with the same height and width (the greater the difference, the less even and more stretched the circle appears), and then set the border-radius to 50%. On Android, that same element was being rendered as 64px by 85px. This was most probably happening because the height was not being explicitly defined.

As a remedy, I hard-coded the width and height properties. I changed the unit of measurement to pixels for both.

.timeline .year {
  font-size: 1em; 
  line-height: 4;
  border-radius: 50%;
  width: 65px;
  height: 65px;
}

This fixed the circle from being stretched. But, now the text was not centered that way that it should be.

Fixed CSS displays a circle

That was happening because of the line-height property. Since it was set without a unit of measure, most browsers would interpret it to mean a multiplier of the current font-size. And, the font-size was also using a relative unit of measure, em. Right away, that was a code smell. It was a browser issue, where along the way values were not being calculated as expected.

On mobile, the font-size was being calculated to 20.8px. On desktop, the font-was was being calculated to 16px. I fixed this by changing the line-height value to an absolute unit of measure:

.timeline .year {
  font-size: 1em; 
  line-height: 65px;
  border-radius: 50%;
  width: 65px;
  height: 65px;
}

Online Ordering for a Restaurant Website

online ordering system

A Digital Transformation Case Study: Boosting Restaurant Sales with Custom Web Development and Online Ordering Integration

Client background & challenge

When I was younger I worked as a pizza delivery driver. Years later, the pizzeria where I once worked commissioned me to build their website. They were busier than ever thanks to online ordering (GrubHub, Seamless, UberEats), but were getting hit with high service fees.

They wanted their own website to be able to take orders for food online and send a notification to their iPad. That way they could avoid using apps like GrubHub that charged additional fees.

Project overview & execution

I used a service called GloriaFood that provides ready-made website templates, a secure payment process, and a messaging system. It integrates with Stripe for processing payments. There is an iPad app that receives push notifications when new orders are placed. The website builder required no code, and had a ton of options. I was able to register the pizzeria’s domain name directly though the admin portal, and generate a sales optimized website with hosting all setup.  It was “seamless” – pun intended!

GloriaFood admin panel

There are also options for integrating their ordering UI with an existing website, a Facebook page, or a dine-in QR code. The an option to publish a custom app required an additional cost per month.

pizza website

I even traveled to this restaurant’s physical location, selected and purchased a tablet computer for them, installed the GloriaFood app to receive orders, and connected it to their mobile printer.

gloriafood order received

It’s amazing how much I was able to accomplish without writing a single line of code. The most technical part of this project was setting up a Stripe account and putting the API keys into the GloriaFood admin panel. GloriaFood is a product by Oracle, a company that specializes in providing a wide range of software and hardware products and services.

Print Design

As an extra part of this project, I designed a business card with a QR code linking to the new website. The business owner planned to give this to customers who ordered through other food ordering apps such as GrubHub, Seamless, UberEats, and Slice.

Business card for pizza business website

Results

Sales Increase

Since the launch of the new website, the restaurant has witnessed a notable surge in online orders, marking a 25% increase. This substantial rise not only signifies a successful digital transformation but also illustrates the growing customer preference for a seamless, direct ordering experience. The intuitive interface and easy navigation on the restaurant’s website have played a strong role in attracting and retaining customers, driving a higher volume of online orders and significantly contributing to the restaurant’s revenue growth.

Cost Savings

Transitioning from third-party ordering platforms like GrubHub, Seamless, and UberEats to a self-hosted online ordering system through has led to big cost savings. Third-party platforms usually charge hefty commissions, which eat into the restaurant’s profits and inflate prices for customers. With the new website, the restaurant has eliminated these intermediary costs, ensuring better profitability while also offering customers more competitive pricing.

Customer Feedback

The feedback received from both the restaurant management and its customers has been overwhelmingly positive.  The restaurant staff has praised the streamlined process, which has simplified order management and allowed for a smoother operation during busy hours.

React JS & Yup: only require an input, if another is not empty

React JS and Yup

Typically, I avoid using JS app frameworks, and default to plain vanilla JavaScript. But, in keeping up with what is current – and working on projects as part of a team – React is inevitable: “A JavaScript library for building user interfaces” . Yup is the go-to form validation library in this context. Its GitHub page calls it “Dead simple Object schema validation”.

Yup creates validation schemas for inputs. Create a Yup validation object, and wire it up to a form – easy.

The ask

Setting: An existing React project, with a signup form. The form includes address inputs. The “country” input was not a required field – it could be left blank. My assignment was to make that field be required, only if the “state/territory” input was not empty. Sounds straight forward.

Here is a sample of the original code:

export const apValidateMyAddress = () => {
  name: yup.string().required("Don't leave this blank!!"),
  email: yup.string().email(),
  address: yup:string(),
  city: yup.string(),
  state: yup.string(),
  country: yup.string()
}

At first, I wasn’t sure if I should update this schema code directly. I thought about checking if the state field was blank, or not, and applying a different schema object instead. That would have been the wrong approach.

Doing some research, I discovered that the Yup’s when() method was the solution. It would let me “adjust the schema based on a sibling or sibling children fields”.

My first attempt was wrong, and didn’t work::

export const apValidateMyAddress = () => {
  name: yup.string().required("Don't leave this blank!!"),
  email: yup.string().email(),
  address: yup:string(),
  city: yup.string(),
  state: yup.string(),
  country: yup.string().when('state',{
    is: true,
    then: yup.string().required('This is a required field.')
  })
}

Errors were thrown. Documentation examples were hard to come by, and I was new at this. I wanted the condition to be true if “state” was not blank. Setting the “is” clause as “true” would only work if state was validated as a boolean – state: yup.boolean() . Ultimately, I was able to check that the “state” value existed using the value property:

export const apValidateMyAddress = () => {
  name: yup.string().required("Don't leave this blank!!"),
  email: yup.string().email(),
  address: yup:string(),
  city: yup.string(),
  state: yup.string(),
  country: yup.string().when('state',{
    is: (value: any) => !!value,
    then: yup.string().required('This is a required field.')
  })
}

More Conditional Logic

In another example of validating a field based on the value of another, I leveraged the context attribute. This allows you to pass additional data to the validation schema.

const validOrderQuanitity = await orderQuantitySchema.validate(orderQuantity, {context: previousOrderQuantity, customerType})

Here, for my order quantity to be valid, it needs to be greater than (or equal to) the previous order quantity, but only when the customer type is “existing”. Although the order quantity is what is being validated, I need the context of the previous order quantity and the customer type.

In my validation schema, I use a when condition to check the customer type and to reference the passed argument:

export const myValidationSchema = Yup.number().when('$customerType',{ is: 'existing', then: Yup.number().min(Yup.ref('$previousOrderQuantity'), "Invalid!")})

UI Component Pattern for a Simple PHP website

PHP UI component patterns

Reusable components are a staple of modern front-end web development. On my simple PHP website, I wanted to build user interface pieces, and reuse them across multiple pages. When I was creating a new page for a newsletter signup form, I realized that I was repeating a lot of code for a contact form section that is displayed on almost every page.

Contact form section

This website is so simple, it does not use any modern framework. The contact form itself is powered by AWS SES.  I created a directory in the root folder of the website called “components”. There, I put files containing HTML, CSS, and JavaScript code that would otherwise be repeated. Implementing this pattern will help my code adhere to the DRY (don’t repeat yourself) principle, and make it quicker and easier to make changes in the future. Centralizing code ensures quality and scalability.

UI component directory

Searching the code base for references to this particular HTML revealed ten instances that could be cleaned up.

searching a code base

In the new component file, I copy and paste my HTML and CSS code.  Then, I go through each of the offending files, and replace the markup with a reference:

<?php include $_SERVER["DOCUMENT_ROOT"] . '/components/contact-section.php'; ?>

I also delete any CSS and JavaScript for this section that’s on the page. At first, I tried adding the JavaScript that controls this form’s functionality to that same file. It failed because it relies on a jQuery reference that is not loaded until lower in the document. Separating the JS code into its own file, similarly named as `contact-section-js.php`, and calling it below the library reference solved the issue. That code is responsible for passing the message along to the back-end, handling UI success/error notifications, and implementing CAPTCHA to thwart bots. Since it was a lot of files were morphed, I ran a quality assurance protocol to ensure nothing broke.