Bootstrap Achilles

search box

Getting a 100 score in PageSpeed for shopware 6

In this post i will go through the steps i have made to get a shopware store to get a 100 score on googols developer tool PageSpeed.


Introduction

This is the final result, off course 100% is not really accurate but it's close enough, usually the website gets a score between 90 and a 100.

Desktop Score

As for the mobile version the best score I got was 89, usually when testing the site the result is somewhere between 79 and 89.

Mobile Score

Remove EXIF data and create webp versions for each image on the server.

Here we are utilizing the Imagick software hence I wrote a php script that is executed once per week. We start by using rglob() to search the server for all files with the same pattern passed to it. an example of using this function is $getJPG = rglob('*.jpg'); this will return an array with all JPG photos on the server.

PHP

            function rglob($pattern, $flags = 0) {
              $files = glob($pattern, $flags); 
              foreach (glob(dirname($pattern).'/*', GLOB_ONLYDIR|GLOB_NOSORT) as $dir) {
                  $files = array_merge($files, rglob($dir.'/'.basename($pattern), $flags));
              }
              return $files;
            }
          

Then we copy the name of the image that we want to make a webp version of and replace the extension to webp.

PHP

            function getName($path){
              if(strpos($path, ".jpg") == true)
                return str_replace(".jpg",".webp",$path);
              elseif(strpos($path, ".jpeg") == true)
                return str_replace(".jpeg",".webp",$path);
              elseif(strpos($path, ".png") == true)
                return str_replace(".png",".webp",$path);
              else
                return null;
            }
          

This function takes in the source image as the source and the modified string from the pervious function as the destination, then spits out a webp file at the same path as the original photo.

PHP

            function convertImageToWebP($source, $destination, $quality=80) {
              $extension = pathinfo($source, PATHINFO_EXTENSION);
              if ($extension == 'jpeg' || $extension == 'jpg') 
                $image = imagecreatefromjpeg($source);
              elseif ($extension == 'gif') 
                $image = imagecreatefromgif($source);
              elseif ($extension == 'png') 
                $image = imagecreatefrompng($source);
              return imagewebp($image, $destination, $quality);
            }
          

So here is where the magic happens, in this code snippet we have an array of all .png photos on the server, we loop over them and for each photo using the Imagick software we strip the image of the EXIF data and clean up when done.

PHP

              $getPNG = rglob('*.png');
              foreach($getPNG as $png) {
                $img = new Imagick($png);
                $img->stripImage();
                $img->writeImage($png);
                $img->clear();
                $img->destroy();
                echo "Removed EXIF data from $png.";
                $webp = getName($png);
                convertImageToWebP($png, $webp, 80);
                echo "Webp copy has been made for $webp.";
              }
            

To redirect users and search engines to the webp version of the photos add this to the .htaccess file and you should be good.

Other

              <IfModule mod_rewrite.c>
                RewriteEngine On
                # Check if browser supports WebP images
                RewriteCond %{HTTP_ACCEPT} image/webp
                # Check if Picture and WebP replacement image exists
                RewriteCond %{REQUEST_FILENAME} (.*).(jpe?g|png|PNG)$
                RewriteCond %1.webp -f
                # Serve WebP image instead
                RewriteRule (.+).(jpe?g|png|PNG)$ $1.webp [T=image/webp,E=accept:1]
                </IfModule>
              
                <IfModule mod_headers.c>
                Header append Vary Accept env=REDIRECT_accept
                </IfModule>
              
                <IfModule mod_mime.c>
                AddType image/webp .webp
                </IfModule>
            

All text remains visible during web font loads.

Here we will need to add some lines in the css files of your theme. Navigate to the .scss files in your theme directory. Find the _fonts.scss (or wherever you import the fonts for your theme) and look for @font-face here we will need to add the following font-display: swap; this will let the browser know that it should display whatever text is displayed on the page and when the font is loaded it will switch to that font.

css

            @font-face {
              font-family: 'Founders Grotesk Regular';
              src: url('#{$font-directory}/FoundersGrotesk/founders-grotesk-web-regular.eot') format('eot'),
              url('#{$font-directory}/FoundersGrotesk/founders-grotesk-web-regular.woff2') format('woff2');
              font-weight: 300;
              font-style: normal;
              font-stretch: normal;
              font-display: swap;
          }
          

Lazy Loading

This would be the easiest part thanks to this github repo from Stefan Poensgen. Still by the time of writing this the plugin does not work on Shopware 6.3.5.4 which is the latest version at the time of writting. To fix the compatibility i have forked the plugin and fixed it for ya'll, you can find it here.

Good Cache Policy

The idea behind a cache is instead of each time a user visits the website, the server sends pictures, font's etc to the users device, it's send once upon the initial visit then the server can tell the device browser to save the files for a specific amount of time. When the user load the website again, all assets will be loaded from the local device. to implement this just add the following to the .htaccess in your root path.

Other

              <IfModule mod_expires.c>
              ## Enable expiration control
              ExpiresActive On
          
              ## CSS and JS expiration: 1 week after request
              ExpiresByType text/css "access plus 1 week"
              ExpiresByType application/javascript "access plus 1 week"
              ExpiresByType application/x-javascript "access plus 1 week"
          
              ## Image files expiration: 1 month after request
              ExpiresByType image/bmp "access plus 1 month"
              ExpiresByType image/gif "access plus 1 month"
              ExpiresByType image/jpeg "access plus 1 month"
              ExpiresByType image/jpg "access plus 1 month"
              ExpiresByType image/jp2 "access plus 1 month"
              ExpiresByType image/pipeg "access plus 1 month"
              ExpiresByType image/png "access plus 1 month"
              ExpiresByType image/svg+xml "access plus 1 month"
              ExpiresByType image/tiff "access plus 1 month"
              ExpiresByType image/x-icon "access plus 1 month"
              ExpiresByType image/ico "access plus 1 month"
              ExpiresByType image/icon "access plus 1 month"
              ExpiresByType text/ico "access plus 1 month"
              ExpiresByType application/ico "access plus 1 month"
              ExpiresByType image/vnd.wap.wbmp "access plus 1 month"
          
              ## Font files expiration: 1 week after request
              ExpiresByType application/x-font-ttf "access plus 1 month"
              ExpiresByType application/x-font-opentype "access plus 1 month"
              ExpiresByType application/x-font-woff "access plus 1 month"
              ExpiresByType font/woff2 "access plus 1 month"
              ExpiresByType image/svg+xml "access plus 1 month"
          
              ## Audio files expiration: 1 month after request
              ExpiresByType audio/ogg "access plus 1 month"
              ExpiresByType application/ogg "access plus 1 month"
              ExpiresByType audio/basic "access plus 1 month"
              ExpiresByType audio/mid "access plus 1 month"
              ExpiresByType audio/midi "access plus 1 month"
              ExpiresByType audio/mpeg "access plus 1 month"
              ExpiresByType audio/mp3 "access plus 1 month"
              ExpiresByType audio/x-aiff "access plus 1 month"
              ExpiresByType audio/x-mpegurl "access plus 1 month"
              ExpiresByType audio/x-pn-realaudio "access plus 1 month"
              ExpiresByType audio/x-wav "access plus 1 month"
          
              ## Movie files expiration: 1 month after request
              ExpiresByType application/x-shockwave-flash "access plus 1 month"
              ExpiresByType x-world/x-vrml "access plus 1 month"
              ExpiresByType video/x-msvideo "access plus 1 month"
              ExpiresByType video/mpeg "access plus 1 month"
              ExpiresByType video/mp4 "access plus 1 month"
              ExpiresByType video/quicktime "access plus 1 month"
              ExpiresByType video/x-la-asf "access plus 1 month"
              ExpiresByType video/x-ms-asf "access plus 1 month"
              </IfModule>
            

Alternatively this format can be used which is shorter.

Other

              <filesMatch ".(ico|pdf|flv|woff|woof2|jpg|jpg|jpeg|png|gif|js|css|swf)$">
              Header set Cache-Control "max-age=31536000, public"
              </filesMatch>
            

Block YouTube videos

In the store that I'm working on there is a YouTube video being played on the main page, but the video needs to play only when the user clicks on it, so from Shopware 6 go to the admin dashboard then content then go to the layout where the video is and make sure the setting are as follows. This is done because at the initial loading of the page the YouTube player is also loaded which in turn means more stuff to load for the user.

video settings