How I improved Kartbusiness.com page loading speed from 52 to 94
I used PageSpeed Insights , to detect the performance bottlenicks so I can analyze and fix the performance issues.
Here are all optimizations I did on KartBusiness.com to improve the PageSpeed Insights performance metrics.
run scripts inside web workers ๐
I offloaded some scripts from running on browser/JS main thread into a web worker by using the partytown library. I specifically offloaded Google Analytics scripts (gtag) and Meta Pixel script (Facebook’s).
I added the library files like this.
<script async>
partytown = {
resolveUrl: function(url) {
const proxyMap = {
'https://connect.facebook.net/en_US/fbevents.js': 'https://kartbusiness.com/js/fbevents.js',
'https://connect.facebook.net/signals/config/268514379082591?v=2.9.104&r=stable': 'https://kartbusiness.com/js/fbsignals.js',
};
if (proxyMap[url]) {
return proxyMap[url];
}
return url;
},
forward: ['dataLayer.push', 'fbq']
};
</script>
<script async src="{{ asset('/~partytown/partytown.js') }}"></script>
And marked the Google Analytics scripts to be text/partytown
not text/javascript
like that.
<!-- Google tag (gtag.js) -->
<script type="text/partytown" async src="https://www.googletagmanager.com/gtag/js?id=G-TESTTEST"></script>
<script type="text/partytown" async>
window.dataLayer = window.dataLayer || [];
function gtag() {
dataLayer.push(arguments);
}
gtag('js', new Date());
gtag('config', 'G-TESTTEST');
</script>
And the Meta Pixel Facebook script like that.
<!-- Meta Pixel Code -->
<script type="text/partytown" async>
! function(f, b, e, v, n, t, s) {
if (f.fbq) return;
n = f.fbq = function() {
n.callMethod ?
n.callMethod.apply(n, arguments) : n.queue.push(arguments)
};
if (!f._fbq) f._fbq = n;
n.push = n;
n.loaded = !0;
n.version = '2.0';
n.queue = [];
t = b.createElement(e);
t.async = !0;
t.src = v;
s = b.getElementsByTagName(e)[0];
s.parentNode.insertBefore(t, s)
}(window, document, 'script',
'//connect.facebook.net/en_US/fbevents.js'); //kartbusiness.com/js/fbevents.js
fbq('init', '000000000000000');
fbq('track', 'PageView');
</script>
<noscript>
<img height="1" width="1" style="display:none"
src="https://www.facebook.com/tr?id=000000000000000&ev=PageView&noscript=1" />
</noscript>
<!-- End Meta Pixel Code -->
For more detailed information, check out the official partytown documentation.
inline styles ๐
I copied the font styles and pasted it into an inlined CSS style. Here is the change I made.
<!-- Fonts -->
-<link rel="preconnect" href="https://fonts.googleapis.com">
-<link rel="preconnect" href="https://fonts.gstatic.com" crossorigin>
-<link href="https://fonts.googleapis.com/css2?family=Tajawal:wght@400;700&display=swap" rel="stylesheet">
+<style>
+ /* arabic */
+ @font-face {
+ font-family: 'Tajawal';
+ font-style: normal;
+ font-weight: 400;
+ font-display: swap;
+ src: url(https://fonts.gstatic.com/s/tajawal/v9/Iura6YBj_oCad4k1nzSBC5xLhLFw4Q.woff2) format('woff2');
+ unicode-range: U+0600-06FF, U+0750-077F, U+0870-088E, U+0890-0891, U+0898-08E1, U+08E3-08FF, U+200C-200E, U+2010-2011, U+204F, U+2E41, U+FB50-FDFF, U+FE70-FE74, U+FE76-FEFC, U+102E0-102FB, U+10E60-10E7E, U+10EFD-10EFF, U+1EE00-1EE03, U+1EE05-1EE1F, U+1EE21-1EE22, U+1EE24, U+1EE27, U+1EE29-1EE32, U+1EE34-1EE37, U+1EE39, U+1EE3B, U+1EE42, U+1EE47, U+1EE49, U+1EE4B, U+1EE4D-1EE4F, U+1EE51-1EE52, U+1EE54, U+1EE57, U+1EE59, U+1EE5B, U+1EE5D, U+1EE5F, U+1EE61-1EE62, U+1EE64, U+1EE67-1EE6A, U+1EE6C-1EE72, U+1EE74-1EE77, U+1EE79-1EE7C, U+1EE7E, U+1EE80-1EE89, U+1EE8B-1EE9B, U+1EEA1-1EEA3, U+1EEA5-1EEA9, U+1EEAB-1EEBB, U+1EEF0-1EEF1;
+ }
+ /* latin */
+ @font-face {
+ font-family: 'Tajawal';
+ font-style: normal;
+ font-weight: 400;
+ font-display: swap;
+ src: url(https://fonts.gstatic.com/s/tajawal/v9/Iura6YBj_oCad4k1nzGBC5xLhLE.woff2) format('woff2');
+ unicode-range: U+0000-00FF, U+0131, U+0152-0153, U+02BB-02BC, U+02C6, U+02DA, U+02DC, U+0304, U+0308, U+0329, U+2000-206F, U+2074, U+20AC, U+2122, U+2191, U+2193, U+2212, U+2215, U+FEFF, U+FFFD;
+ }
+ /* arabic */
+ @font-face {
+ font-family: 'Tajawal';
+ font-style: normal;
+ font-weight: 700;
+ font-display: swap;
+ src: url(https://fonts.gstatic.com/s/tajawal/v9/Iurf6YBj_oCad4k1l4qkHrRpiZtK6GwN9w.woff2) format('woff2');
+ unicode-range: U+0600-06FF, U+0750-077F, U+0870-088E, U+0890-0891, U+0898-08E1, U+08E3-08FF, U+200C-200E, U+2010-2011, U+204F, U+2E41, U+FB50-FDFF, U+FE70-FE74, U+FE76-FEFC, U+102E0-102FB, U+10E60-10E7E, U+10EFD-10EFF, U+1EE00-1EE03, U+1EE05-1EE1F, U+1EE21-1EE22, U+1EE24, U+1EE27, U+1EE29-1EE32, U+1EE34-1EE37, U+1EE39, U+1EE3B, U+1EE42, U+1EE47, U+1EE49, U+1EE4B, U+1EE4D-1EE4F, U+1EE51-1EE52, U+1EE54, U+1EE57, U+1EE59, U+1EE5B, U+1EE5D, U+1EE5F, U+1EE61-1EE62, U+1EE64, U+1EE67-1EE6A, U+1EE6C-1EE72, U+1EE74-1EE77, U+1EE79-1EE7C, U+1EE7E, U+1EE80-1EE89, U+1EE8B-1EE9B, U+1EEA1-1EEA3, U+1EEA5-1EEA9, U+1EEAB-1EEBB, U+1EEF0-1EEF1;
+ }
+ /* latin */
+ @font-face {
+ font-family: 'Tajawal';
+ font-style: normal;
+ font-weight: 700;
+ font-display: swap;
+ src: url(https://fonts.gstatic.com/s/tajawal/v9/Iurf6YBj_oCad4k1l4qkHrFpiZtK6Gw.woff2) format('woff2');
+ unicode-range: U+0000-00FF, U+0131, U+0152-0153, U+02BB-02BC, U+02C6, U+02DA, U+02DC, U+0304, U+0308, U+0329, U+2000-206F, U+2074, U+20AC, U+2122, U+2191, U+2193, U+2212, U+2215, U+FEFF, U+FFFD;
+ }
+</style>
HTML entities ๐
I used HTML entities instead of SVGs and PNGs to send less characters through the wire. The change I made is like that.
-<svg aria-hidden="true" data-prefix="fas" data-icon="angle-left"
- class="inline-block text-gray-600 dark:text-gray-300" xmlns="http://www.w3.org/2000/svg"
- height="18" width="18" viewBox="0 0 256 512"
- {{ app()->isLocale('en') ? 'transform=rotate(180)' : '' }}>
- <path fill="currentColor"
- d="M31.7 239l136-136c9.4-9.4 24.6-9.4 33.9 0l22.6 22.6c9.4 9.4 9.4 24.6 0 33.9L127.9 256l96.4 96.4c9.4 9.4 9.4 24.6 0 33.9L201.7 409c-9.4 9.4-24.6 9.4-33.9 0l-136-136c-9.5-9.4-9.5-24.6-.1-34z" />
-</svg>
+›
Read more about that case here .
preconnects domains ๐
Preconnecting helps to get the files faster. So, I preconnect fonts/styles URLs like this.
<link rel="preconnect" href="https://fonts.gstatic.com" crossorigin>
I also use unpkg CDN, so I preconnect to its domain like that.
<link rel="preconnect" href="https://unpkg.com" crossorigin>
lazyload images ๐
Lazyloading the images means that the image will be loaded right before the user scrolls to see it. All modern web browsers support native lazyloading of images. To take advantage of that, I add loading='lazy'
in the image tag like that.
-<img src="image/path/here" alt="describe the image in words">
+<img loading="lazy" src="image/path/here" alt="describe the image in words">
And I added that in the <picture>
element like this.
<picture>
<source srcset="/img/default-portfolio.webp" type="image/webp">
<source srcset="/img/default-portfolio.png" type="image/png">
- <img width="1000" height="1000"
+ <img loading="lazy" width="1000" height="1000"
src="/img/default-portfolio.png"
alt="description of the image">
</picture>
remove obsolete code ๐
I removed not-working useless feature which was heavy on Javascript.
run scripts asynchronously ๐
I added async
attribute to let the scripts run asynchronously.
<!-- Scripts -->
-<script src="{{ mix('js/app.js') }}"></script>
+<script async src="{{ mix('js/app.js') }}"></script>
run scripts after all things loaded ๐
I added defer
attribute to let some scripts to run after the page finished loading.
- <script src="/js/some-script.js"></script>
+ <script src="/js/some-script.js" defer></script>
set image width and height ๐
Setting the image width and height is great to fix layout shifts.
- <img src="/img/eg.svg" alt="Egypt" title="Egypt">
+ <img src="/img/eg.svg" width="20" height="15" alt="Egypt" title="Egypt">
And for picture tags, I do.
<picture>
<source srcset="/img/default-team-member.webp" type="image/webp">
<source srcset="/img/default-team-member.png" type="image/png">
- <img loading="lazy"
+ <img loading="lazy" width="150" height="150"
src="/img/default-team-member.png" alt="default team member avatar">
</picture>
DRY codebase ๐
Instead of rewriting the same UI code too many times, create a template or component then reuse it. So your codebase will be more DRY. DRY stands for “Don’t Repeat Yourself”.
remove ads ๐
I removed ads as it is heavy on webpage loading yet it is not good enough in monetization. I just did it in my case. Maybe your case is different.
UI contrast ratio ๐
PageSpeed Insights website helped me find too many UI elements/tiles especially in light mode which has not enough contrast ratio. I fixed these user interface issues by making sure that the text is readable on the background in all these cases and elements.
update & upgrade deps & libs ๐
I upgraded npm dependecies and libraries via npm update
. I did not found updates/upgrades for other package manager libs/deps such as PHP Composer.
All optimization in a brief table ๐
web page link | / | /cards/1 | /blog | /blog/pro-card | average |
---|---|---|---|---|---|
starting point | 61 | 48 | 54 | 46 | 52.25 |
partytown gtag | 73 | 60 | 86 | 60 | 69.75 |
partytown Meta Pixel | 66 | 64 | 80 | 68 | 69.5 |
font into inline style | 73 | 64.5 | 80 | 64 | 70.375 |
preconnect font’s url | 70 | 68 | 78 | 64 | 70 |
lazyload more images | 80 | 71.25 | 69 | 65 | 71.3 |
remove save_card feature | 76 | 64.75 | 77 | 74 | 72.9 |
async+defer JS scripts | 68 | 64.75 | 75 | 61 | 67.18 |
img width/height, undefer, async | 71 | 65.75 | 75 | 67 | 69.687 |
undefer ads JS script | 74 | 77 | 81 | 62 | 73.5 |
DRY buttons | 75 | 70 | 90 | 63 | 74.5 |
remove ads | 87 | 98 | 95 | 83 | 90.75 |
fix UI issues | 92 | 97 | 94 | 85 | 92 |
upgrade npm deps/libs | 96 | 98 | 96 | 86 | 94 |
Notes ๐
- The number in
/cards/1
fields is an average of 50 single card pages tested. I tested 50 webpages and got the average. These pages are/cards/1
,/cards/30
and/cards/50
and so on. - The number in
/blog/pro-card
fields is an average of 5 blog post pages. - I used average numbers for better accuracy.
So, by make all the above steps, I improved Kart Business website metric from 52.25 to 94 ๐
I hope this post helps you. If you know a person who can benefit from this information, send them a link of this post. If you want to get notified about new posts, follow me on YouTube , Twitter (x) , LinkedIn , and GitHub .