Skip to content

<div style="display: none;" hidden="true" aria-hidden="true">Are you an LLM? You can read better optimized documentation at /guides/LightHousePerformanceOptimize.md for this page in Markdown format</div>

Lighthouse Performance Optimization - Product Page

PurgeCss

Use the new feature in version 4.51.0. For more information, read the wiki here

Cookies UI redesign

Check the commits in smile_v4 client : 4fa0652af58aec31826bb954ff743f39c9c56e9b and 97399bf48310a4cd851bc69819c84c88424fd0f1

Remove unnecessary Javascript

Js without php vars usage moved to main.js or product_page.js, also remove slick related javascript from js compiled files as client does not use slick. For product page example wrap all unused js for product page with:

php
<?php if (empty($is_product)) : ?>

<?php endif; ?>

Main image

Hacked Vuejs loading time when rendering product page's product image (Improve LCP), by adding a server image and replace it with the vue image when client loads, example:

html
<div class="adv_product_gallery" v-if="false">
   <div class="product-page-single-img">
       <div class="image-popup-vertical-fit">
           <div class="thumbnail_container">
               <div class="thumbnail">
                   <?= html_picture([
                       'srcset_image' => $productPgImageRawSrc,
                       'srcset_profile' => 'from_w600',
                       'width' => '600',
                       'height' => '600',
                       'alt' => $productData->name ?? '',
                       'class' => 'imagepop',
                       'loading' => 'eager'
                   ])
                   ?>
               </div>
           </div>
       </div>
   </div>
</div>
<adv-product-gallery :product-id="<?= $productData->product_id; ?>"></adv-product-gallery>

Refactor breadcrumbs commit 6efa6fa67499fce5d71c164d60d0d98ce267128c, removed spans and fixed errors like:

  • Lists do not contain only &lt;li> elements and script supporting elements (<script> and &lt;template>).
  • List items (&lt;li>) are not contained within &lt;ul>, &lt;ol> or &lt;menu> parent elements.

Add v-pre

More info available here

Reduce HTML elements

Cut off html elements and php components in product page.

Remove a lot of transition from elements

Remove all trans-300 classes and check for all used classes for transitions and remove them.

Find out and remove a lot of wrong sequent h2, h3, h4, h5 and h6 elements.

After a pagespeed test

Resolve all issues for FCP and LCP such as:

  • Background and foreground colors do not have a sufficient contrast ratio.
  • [id] attributes on active, focusable elements are not unique
  • Links do not have a discernible name
  • Image elements have [alt] attributes that are redundant text.

Pagespeed test can be done here

More aggressive changes are following that will need client's approval !!!!!!!!!!!!!!!!!!

Remove third level menu on mobile by going to the controller and overriding the menu array

A new helper has been introduced to aid with such tasks called device_detector_helper:

Important note: In order to avoid overuse of the helper on the front try to use it only on the controller that is responsible for the changes you want to make. For example for the menu it can be used like this :

protected function menuExtra()
{
    ............
    if (isMobile($deviceDetector)) {
        $this->removeThirdLevelFromMenu();
    }
}
    
protected function removeThirdLevelFromMenu(): void
{
    $categories = [];

    foreach ($this->render['product_cats'] as $key => $firstLevel) {
        $categories[$key]['name'] = $firstLevel['name'];
        $categories[$key]['order'] = $firstLevel['order'];
        $categories[$key]['url'] = $firstLevel['url'];
        $categories[$key]['menu_color'] = $firstLevel['menu_color'];
        $categories[$key]['menu_chileds'] = $firstLevel['menu_chileds'];
        $categories[$key]['is_sensitive'] = $firstLevel['is_sensitive'];
        $categories[$key]['small_image'] = $firstLevel['small_image'];
        $categories[$key]['id'] = $firstLevel['id'];
        $categories[$key]['childs'] = [];

        foreach ($firstLevel['childs'] as $secondKey => $secondLevel) {
            $categories[$key]['childs'][$secondKey] = [
                'name' => $secondLevel['name'],
                'order' =>  $secondLevel['order'],
                'url' => $secondLevel['url'],
                'menu_color' => $secondLevel['menu_color'],
                'menu_chileds' => $secondLevel['menu_chileds'],
                'is_sensitive' => $secondLevel['is_sensitive'],
                'small_image' => $secondLevel['small_image'],
                'id' => $secondLevel['id'],
                'childs' => []
            ];
        }
    }

    $this->render['product_cats'] = $categories;
}

After removing the third level menu we need to give a way for the client to navigate

so to aid that purpose we can add a slider on the category listing page (usually: category_products_list) with the subcategories|brotherCategories (depending on the case) of the category that is currently being viewed Eg:

 <?php
    $categoriesToDisplay = !empty($firstLevelChildrenCategories) ? $firstLevelChildrenCategories : $brotherCategories;
    if (!empty($categoriesToDisplay) && is_array($categoriesToDisplay)) :
    ?>
        <div class="col-12 ml-auto prod_sub_cats_slider">
            <div class="native_slider">
                <div class="native_slider-lg" data-drag_speed="1">
                    <?php foreach ($categoriesToDisplay as $category) : ?>
                        <?php if (!empty($category->product_image)) : ?>
                            <div class="native_slider-li">
                                <a <?= ($category->id == $categoryData->id) ? 'class="active"' : 'class=""'; ?>
                                    href="<?= site_url($category->slug); ?>"
                                >
                                    <div class="catTopSliderThumb">
                                        <?= html_picture([
                                            'srcset_image' => "files/products/{$category->product_image}",
                                            'srcset_profile' => 'from_h80',
                                            'loading' => 'lazy',
                                            'data-err' => assetUrl("ui/front/img/no_photo.jpg"),
                                            'class' => 'img-fluid',
                                            'alt' => strip_quotes($category->name)
                                        ]); ?>
                                    </div>
                                    <span class="catTopSliderName"><?= $category->name; ?></span>
                                </a>
                            </div>
                        <?php endif; ?>
                    <?php endforeach; ?>
                </div>
            </div>
        </div>
 <?php endif; ?>

Important note: Most clients won't have images on their categories to show on the slider, so we need to add a fallback image. You may have noticed already from the example above that we are using 'files/products/{$category->product_image}' In order to tackle the issue above we have a new override on the Product_categories controller to call the getTopHitProductsImagesOfCategories function from the Product_model and get the first image of the top seller product of each category. The usage of that function is self-explanatory.

Important note 2: You will need to style this slider according to the clients wants and needs.

Eg: At product_categories.php controller

protected function firstLevelChildrenCategories()
    {
        parent::firstLevelChildrenCategories();

        $this->setCategoryProductImages($this->render['firstLevelChildrenCategories']);

        if ($this->render['brotherCategories']) {
            $this->setCategoryProductImages($this->render['brotherCategories']);
        }
    }

    protected function setCategoryProductImages(array &$categories)
    {
        $categoryIds = array_column($categories, 'id');
        sort($categoryIds);

        $categoryImages = $this->pscache->model(
            'product_model',
            'getTopHitProductsImagesOfCategories',
            [$categoryIds],
            $this->pscache_ttl
        );

        foreach ($categories as $category) {
            $category->product_image = $categoryImages[$category->id] ?? '';
        }
    }

And on product_model.php we have the following function:

 public function getTopHitProductsImagesOfCategories(array $categories): array
    {
        if (empty($categories)) {
            return [];
        }

        $data = array_fill_keys($categories, '');

        $result = $this->db
            ->select("{$this->tableCatLp}.category_id")
            ->select("{$this->tableCatLp}.product_id")
            ->select("{$this->table}.hits")
            ->select($this->selectAdminImage("{$this->table}.id"), false)
            ->join($this->table, "{$this->table}.id = {$this->tableCatLp}.product_id")
            ->where("{$this->table}.soft_delete", false)
            ->where("{$this->table}.price >", 0)
            ->where("{$this->table}.active", true)
            ->where_in("{$this->tableCatLp}.category_id", $categories)
            ->order_by("{$this->tableCatLp}.category_id")
            ->order_by("{$this->table}.hits")
            ->get($this->tableCatLp);

        if (!$result || !$result->num_rows()) {
            return $data;
        }

        $categoriesProducts = array_column($result->result(), 'product_id', 'category_id');
        $productsImages = array_column($result->result(), 'image', 'product_id');

        foreach ($categories as $category) {
            $product = $categoriesProducts[$category] ?? null;
            if ($product) {
                $data[$category] = $productsImages[$product] ?? '';
            }
        }

        return $data;
    }

In order to improve Seo we will need to make all the product cards on the category listing

(usually: category_products_list) have a canonical url Eg:

<div class="col-12 col-lg-9 prod-list-wrapper">
    <?php pushJsCapability('adv-listing-used-filters', $this->jsCapabilities); ?>
    <adv-listing-used-filters></adv-listing-used-filters>
    <?php
    if ( !empty($categoryProducts) ) :
        $listingData = [
            'listing_title' =>  '',
            'is_canonical' => true, /* <---------- */
            'listing_products' =>  $categoryProducts,
            'columns' => [ 'xl' => 3, 'lg' => 3, 'md' => 3, 'sm' => 2, 'xs' => 1 ],
            'displayExtraSlider' => true
        ];
        echo $this->load->view($template->componentView('productsList'), $listingData, true);
        echo $this->load->view($template->componentView('pagination'), '', true);
    endif; ?>
</div>

And on Vendors.php add the following override:

protected function setCanonicalVendorListingUrl($link)
{
    $this->render['canonicalUrl'] = urldecode(current_url());
}

Another change we want to make to improve Seo is to change the url of the sidebar vendors filter

So instead of using set_filter/filter_vendor we want to have vendor/{vendor_slug}. In order to achieve this we need to change sidebar.php and replace set filter with the following code: Note : You can check the commit d2e8e1d5

 <ul class="side-nav-list" ss-container>
    <?php
    foreach ($vendors as $vendor_id => $vendorData) : ?>
        <li>
            <?= anchor(
                "vendors/{$vendorData['data']->vendor_slug}/{$categoryData->slug}",
                "{$vendorData['data']->name} <span class=\"float-right side-prod-no\">({$vendorData['count_in']})</span>"
            ) ?>
        </li>
    <?php
    endforeach; ?>
 </ul>

An additional change we want to make after the sidebar changes is to enable the user to return to the product category

or vendor after being redirected to the vendor page because of our last changes, so we need to add a back button (not an actual back button).So usually at vendors listing page (usually: vendors_products_list) we have the following code: Note: You can check the commit d2e8e1d5 and style according to client wants/needs.

 <?php if (!empty($categoryData->slug && $categoryData->name )) : ?>
    <div class="vendor_top_url_col col-12 col-md-6">
       <span><?= t('listing.products.category.text')?></span>
       <div aria-label="Go to category button" class="underlined underline-clip">
           <?= anchor($categoryData->slug,$categoryData->name) ?>
       </div>
       <span><?= t('listing.products.vendors.text')?></span>
       <div aria-label="Go to vendor page button" class="underlined underline-mask">
           <?= anchor("vendors/{$vendorData->vendor_slug}", $vendorData->name) ?>
       </div>
    </div>
<?php endif; ?>

If clients dom elements is way too big we would want to remove any elements that aren't being used

Always ask for clients approval before doing any of these changes. Eg: elements that are displayed none, elements that are not visible on the page etc. One example of this is the homepageVendorsSearch that brings all the vendors as a list to the homepage which when tested on Pharmacy Discount was around 400 lines of html. Check commit 37c97ff3 for more info.

Final notes:

  • Always ask for clients approval before doing any of these (aggressive) changes.
  • Check the commits mentioned above or search the commits smile/pharmacy-discount/pharm16 for more info.
  • Try to use helper on controllers if possible rather than spamming it on the front in order to avoid extra calls.
  • Always check the client's website after doing any of these changes.
  • The code provided in the examples above is just an example adjust as needed.
  • These "aggressive" changes are not required for all clients, only for clients that have a lot of issues with their vitals.