Six tips to reduce your website's size and boost its performance
Anyone who has been in the web design business knows that it is a constant uphill battle. Just learning the markup, stylesheets, and back end isn’t enough to make your website compete in today’s market of services and applications. Years of experience can provide you with a beautiful, modern website; it can function in all browsers and even pass validation. Satisfied? Unfortunately, you’re not done yet. All intended web development professionals need to be well-versed to optimizing speed.
In a nutshell, the goal of optimization is to decrease page load times. Some steps may decrease loading times by noticeable seconds, providing for a more comfortable user experience; some steps may only decrease it by milliseconds. Nonetheless, every step is important.
“Why should I care about milliseconds?” It’s about more than just the time. The importance of each step will be described within its own section, but to peak your interest, I’ll provide you with a prime example. One goal of web page optimization is to decrease the number of HTTP requests that your webpage makes. Each request takes approximately 50 milliseconds to make the connection to the server, excluding the time it takes to download the requested file. Fifty milliseconds isn’t really important. The goal of decreased requests is to decrease server stress and improve server performance. More on that in the Reducing Server Calls article.
This overarching tutorial is split into these three articles: Reducing File Size, Reducing Server Calls, and Reducing Parse Time. Each tip is based on Yahoo!’s page speed optimization tool, YSlow. This article in particular covers something of which we can all easily understand the advantages: reduced file sizes. Not only does it decrease download time for the client, it also saves bandwidth, and thus money, for the provider. This can be accomplished in five steps, in order of ease: HTML image scaling (the do’s and don’t’s), minifying JavaScript and CSS, making favicons small and cacheable, compressing components with gzip, and configuring entity tags (ETags).
If you want to stay updated on future publications, you can follow me on Medium or on Twitter @CharlesStover.
Do Not Scale Images in HTML 🖼
Are you resizing images in-browser? Don’t do it. It’s that simple. Some web programmers, when desiring a thumbnail image, will include the full-sized image and just use the image’s height and width attributes (via HTML or CSS) to resize the image. This saves time and space, since you don’t have to generate or store a thumbnail image. Great idea, right? Not at all. This practice is accompanied with three major negative factors that need to be considered.
Bandwidth
Any server administrator would be appalled to learn that they’re using the bandwidth cost of a full-sized image to display a thumbnail. Let’s take an extreme example of scaled-image thumbnailing. A wallpaper website is displaying thumbnails of each wallpaper so that they can offer a selection of wallpapers in a small area of space. The full wallpaper (1024×768; 200 KB) is used and simply scaled down to 133×100. A hypothetical 133×100 thumbnail dedicated to its own file would only take up approximately 5 KB. That’s a difference of 195 KB per wallpaper per page load. By scaling the wallpaper in-browser, you save a whopping 5 KB of server space, but you pay for it in bandwidth. You should be willing to trade 5 KB of space for 195 KB bandwidth — especially when multiplied per file — any day. This holds true for even less extreme examples, such as half-sizing an image. The bandwidth cost will add up on high-traffic servers.
Download Time
The full image may not be displayed, but the user still has to wait for it to download. Even a user on a dial-up or mobile connection could download a 133×100 thumbnail in a second; but if you scale a full-size image, especially multiple images per page, there is an unbelievably noticeable difference in page loading times. A collection of 10 wallpapers on the page with legitimate thumbnails would take only 50 KB — a near instantaneous download for a user with average Internet speeds. The same wallpapers scaled in HTML would require a download of 2 MB! That’s a noticeable page loading difference.
Parsing Time
Though also the subject of the third article in this tutorial trilogy, parse time is also affected by inline scaling. The use of an image’s width and height attributes will be covered again, except shown in a different light. By setting them, the browser must calculate for and resize the larger image in order to create the thumbnail. These are precious milliseconds wasted on every image load for every viewer!
But the hassle… you’d have to open each wallpaper in your favorite image editor, scale it down, upload it, link to it, etc. Isn’t the time and effort saved worth the bandwidth cost? Well, the answer is no. Thanks to modern server-side programming languages, this process can be automated — no image editors, no per-image labor, no uploading, no manual link additions. However, that’s another topic for another tutorial. In the meantime, keep in mind the cost of image-scaling and make a habit of not doing it.
Minify JavaScript 🐛
A quick and easy way to save bandwidth and decrease download time is to shorten JavaScript and CSS file lengths. JavaScript, especially, is filled with redundancy and unnecessarily long variable names. Why reference myFavoriteVariable 500 times, when you can just call it x and save 17 bytes with every reference? Why display 100 tab indents when it will run just the same without them? Don’t get me wrong! I’m not advocating hideous code. For the love of all that is good, use yourFavoriteVariableName and indent your code blocks. When programming, readability is important. When processing, readability is useless. But how can you program legibly and still send a compressed file to the client? The answer is to use a minifier!
Similar to thumbnails, you can automate minification. Also similar to thumbnails, the automation tool will not be included in this tutorial for the sake of brevity. However, you may use the forever-useful Closure Compiler Service by Google. This service allows you to filter your legible, commented, indented code into the compiler, then copy its condensed, comment-free, small-bandwidth code into a minified file for you to use on your webpage. There are three options for this service: whitespace only, simple, and advanced.
Whitespace only is self-explanatory. The only compression it does is remove whitespace and comments. Simple is most likely what you will want on your pages. It doesn’t rename public variables that may be called outside of the script. For example, you may have a JavaScript file that contains the function expandCollapse. This file is intended to be referenced externally so that said function can be called elsewhere in your application (such as when a link is clicked). The Simple compiler is the one you would want to use, since it will not rename the function, thus not affecting references to it from outside the script itself. The Advanced compiler, on the other hand, will treat the script as if it were the entirety of the program. It will compress all variables, so outside references to the script will likely be broken when the variables are renamed or unused code is removed.
In order for you to get a first-hand understanding, here are example compressions for a sample script. I added line breaks post-compression for the sake of readability.
Uncompressed Script: 301 bytes
var i_heart_you = function(my_custom_code) {
for (var x = 0; x < 10; x++) {
document.write("I am going to remind you that ");
// why did I even use two document.writes?
document.write("my custom code is " + my_custom_code);
x += 1;
}
}
i_heart_you("copyright Charles Stover");
Whitespace only: 212 bytes
var i_heart_you=function(my_custom_code){for(var x=0;x<10;x++){
document.write("I am going to remind you that ");
document.write("my custom code is "+my_custom_code);
x+=1}};i_heart_you("copyright Charles Stover");
White-space and comments are all that were removed.
Simple: 186 bytes
var i_heart_you=function(b){for(var a=0;10>a;a++)
document.write("I am going to remind you that "),
document.write("my custom code is "+b),a+=1};
i_heart_you("copyright Charles Stover");
It renamed all the private variables (the ones referenced within the function itself), but left the function name the same so that it can be referenced by other programs.
Advanced: 139 bytes
for(var a=0;10>a;a++)document.write("I am going to remind you that "),
document.write("my custom code is copyright Charles Stover"),a+=1;
It removed the i_heart_you function and my_custom_code variable entirely, since they weren’t necessary, having only been called once. I’m surprised it didn’t combine both document.writes, but no minifier is perfect.
The imperfection in Advanced brings me to an important point. After minifying any code, verify that it still works. That can’t be stressed enough. While I’ve personally had a 100% success rate with small scripts such as this, compressing thousand-line projects has been a more complicated issue. Don’t blindly trust that the compiler did a good job. If Simple and Advanced compression algorithms destroy functionality in your complex project, you should at least be able to rely on ol’ Whitespace-only to save you some bandwidth with no errors attached.
Minify CSS 🗜
While JavaScript compression will save a ton of bandwidth, let us not forget about CSS! I have yet to find a CSS compression utility that absolutely trumps the rest, so feel free to use whichever one serves the best user interface for your tastes.
The problem with CSS compression is that there is no one method better than the rest. It’s really a guess-and-check principle. It may result in a smaller file size to group CSS declarations by element:
form {
/* all attributes of the form element */
}
Or by individual attribute:
.all-elements,
.that-are-red {
color: #FF0000;
}
Or by multiple shared attributes:
.all-elements,
.that-are-both,
.red-and-large {
color: #FF0000;
font-size: 2em;
}
.red-and-small {
color: #FF0000;
font-size: 0.5em;
}
There really is no way to know which minification algorithm is most efficient until you’ve tried it, as each CSS file will reduce differently from each method of compression.
Due to the countless combination of compression settings, you may have to try different options with your CSS compression utility until you find one that shows a noticeable difference. Unlike JavaScript compression, you aren’t as likely to save as much bandwidth, and it will take longer to find the appropriate setting, but bandwidth saved is bandwidth earned to put a spin on an old phrase.
Make Favicons Small and Cacheable 🔍
Your favicon is a very important and highly-accessed part of your website. Some browsers render it as 32×32 and many software packages practice the use of a 32×32 favicon by default. Don’t let them fool you. Well over 90% of your viewers are going to be viewing your favicon as 16×16. It is pointless to have the vast majority of your viewers downloading (remember: download time and bandwidth costs) an image that’s four times larger than they’ll be able to see. Use a 16×16 favicon by default, and use modern HTML <link /> attributes so that needy software may opt-in to using larger alternatives.
Most browsers are good at caching favicons without server recommendations, since it’s a file that’s requested on every page load. YSlow insists that you manually make the favicon cacheable anyway, and it wouldn’t hurt to listen. On the off-chance there is some browser out there that doesn’t cache favicons by default, it will save you bandwidth and save your clients loading time. Due to the fact that the majority of browsers cache favicons by default and that caching is covered in part two of this tutorial, I’ll leave it out of this section. Don’t forget, when you learn to set permanent cache, to go back and set it for your favicon!
Compress Components With GZip 🤐
Finally, we get to server-side programming! Ah, the most complicated — but the most fun — area of web programming. gzip, if you aren’t aware, is a method of file compression. Unlike the JavaScript and CSS compression tools, it doesn’t just remove useless characters. It changes the file type altogether: think .zip or .rar files. Modern web browsers support receiving gzip files and are capable of automatically extracting and displaying them. This is the case for any file type. There are two ways to gzip a file: one with static files and one with dynamic files.
To determine if your viewer can accept gzipped files, just check the HTTP_ACCEPT_ENCODING header. An example value for HTTP_ACCEPT_ENCODING is gzip,deflate,sdch.
Dynamic Files
This section will use PHP to describe the process of gzipping contents on the fly. Your development language likely also support gzip compression, so you may apply these principles despite not being able to copy PHP code directly.
For files that change often (such as a homepage, topic list, counter, and pretty much any non-archival HTML page), it will be nearly impossible and pointless to create and save a gzipped copy every time the page changes, which may occur by the second. PHP is capable of doing this using the lovely ob_startfunction. After the headers of a page have sent (assuming you’re sending custom headers), just wrap the ob_start function with the ob_gzhandlerparameter around the content of the page.
For those wondering, the ob in ob_start is an acronym for “output buffer.” It records all the output and manipulates it in whatever way you decide before sending it to the client. In this case, we’re going to gzip the output before actually sending it.
<?php
// get the headers out of the way
header('Content-Language: en-us');
header('Content-Type: text/html; charset=utf-8');
// gzip the following content
ob_start('ob_gzhandler');
// the content itself; everything in the source code goes here
include 'path/to/header.html';
echo '<p>This is all the content on my homepage!</p>';
include 'path/to/footer.html';
// close the buffer and send the data to the client
ob_end_flush();
?>
Simple, right? Just put a line of code above the content (to tell it to start recording what to compress) and a line of code after the content (to tell it to compress and send the data).
But wait! What about the browsers that don’t support gzip? Won’t they get errors when receiving the gzipped content? Nope! PHP’s gzip-handling object buffer is kind enough to check the HTTP_ACCEPT_ENCODING header for us! If the browser supports it, it will send the gzipped content. If the browser doesn’t support it, it will send unaltered content. Easy, huh? Just use the object buffer for all your dynamic, PHP-generated files, and let PHP do the rest!
Bandwidth saved.
Static Files
To save server resources, if you know a file is not going to change very often, you may want to store a gzipped copy on the server instead of having PHP automatically generate one every time the file is accessed. You can either use the gzip program provided by the gzip homepage to compress every file manually, or you can just use PHP’s gzencode function to create any non-existent gzip files. To do this, have users access a PHP file which will determine whether or not their browser supports gzip. If their browser does, send them the gzipped file. If it does not, send them the uncompressed file. I’ve included an example of this, but you may feel free to create your own.
<?php
// e.g. download.php?file=path/to/file.jpg
// This would be a great time to use mod_rewrite. ;)
// Always sanitize your inputs!
if (!preg_match('/^[\d\w]+\.[\d\w]{2,4}$/', $_GET['file']))
exit();
// Can't compress what doesn't exist.
if (!file_exists($_GET['file'])) {
exit('File not found.');
}
// This is where we will store the gzip copy.
$gzip_file = $_GET['file'] . '.gz';
// Worry about the compressed file only if
// gzip is part of the browser's accepted encoding.
if (preg_match('/[^|,]gzip[,|$]/', $_SERVER['HTTP_ACCEPT_ENCODING'])) {
// Check to see if a compressed copy exists.
if (!file_exists($gzip_file))
// Create the compressed file if it doesn't exist.
file_put_contents(
$gzip_file,
gzencode(file_get_contents($_GET['file']))
);
// Tell the browser the content is encoded.
header('Content-Encoding: gzip');
// Send the encoded content!
echo file_get_contents($gzip_file);
}
// gzip is not supported (not in the ACCEPTED_ENCODING header),
// so just return the uncompressed file.
else
echo file_get_contents($_GET['file']);
?>
You will want to cache the gzipped file instead of just gzencode’ing the contents every time it is downloaded. To gzip the contents, the server has to calculate the gzipped contents. This takes time. By caching the gzipped copy, the server doesn’t have to recalculate it with every page load, saving precious seconds of time and CPU usage.
Configure Entity Tags (ETags) 🔖
Last but not least, you’ll want to set up entity tag headers. There’s a very simple analogy that can be used to explain how ETags work.
Two days ago, you asked Medium if you could read this tutorial. Medium responded, saying, sure, here’s the tutorial, and the version number is 123.
Today, you asked Medium if you could read the tutorial again. You mentioned that Medium gave you version 123 last time, and you were wondering if there is a newer version out yet.
Medium responds one of two ways:
- “No, that’s the latest version.” Medium sends you no data. You continue reading your cached copy of version 123. The server sends no data. No bandwidth is used.
- “Yes, here is version 256.” Medium sends you the newest version of the article.
ETags are like a method of caching. Their only downfall compared to caching is that they do require the client connects to the server in the first place, however they will greatly reduce bandwidth. It’s a great idea to combine ETags with cache. Perfecting caching will be covered in the Reducing Server Calls article. For now, you’ll get a lot of mileage out of simply using ETags.
The easiest method to calculate an ETag is to just return the file’s modification time for static content. When you update the file, the modification time automatically changes; thus the next time the client requests the file, the server will know that the client’s copy is outdated. However, if you’re using a dynamic PHP file, filemtime won’t necessarily change just because the content changes. Take for example a simple file that contains <?php echo time(); ?>. Every time you view the page, the content will be different. However, the filemtime will always be the same, since the file itself hasn't been modified; only its output has changed. In these cases, there are different methods you can use to determine an ETag, and it's ultimately up to you to decide the best method. If you have content on the page and are capable of determining the last time the content was updated (e.g. the time of an article posting), you can use that to generate the ETag. Otherwise, you may have to resort to something such as generating an MD5 hash of the output and using it as the ETag. If the output changes, the MD5 hash changes, thus the ETag changes.
Once you have determined how to calculate your ETag — simply any string that will change whenever the page changes — and have generated the string, you can send it using the ETag header.
Simple enough. Unfortunately, there’s more. Whenever the ETag has not been updated since the user last downloaded the file, the server should not send any data, because the user already has the file cached. This situation is not automated. It is up to you to prevent data from being sent. You can do this like so:
<?php
// e.g. download.php?file=path/to/file.jpg
// This would be a great time to use mod_rewrite. ;)
// Always sanitize your inputs!
if (!preg_match('/^[\d\w]+\.[\d\w]{2,4}$/', $_GET['file']))
exit();
// If the file doesn't exist, give it a "404" ETag.
// That way, if it exists in the future, the ETag will change.
// Until then, there's no need to keep resending the 404 error file.
$etag = file_exists($_GET['file']) ? filemtime($_GET['file']) : 404;
// If the client already has a copy and it is the same as the server copy...
if (
array_key_exists('If-Modified-Since', $_SERVER) &&
$_SERVER['If-Modified-Since'] == $etag
) {
// Tell the client that there is no newer version,// and don't send any data.
header('HTTP/1.1 304 Not Modified');
header('Connection: close');
exit();
}
// Either the client has no previous copy, or it is not the latest copy.
// Send the entity tag (unique version ID).
header('ETag: ' . $etag);
// Send the data. (Don't forget to compress it!)
ob_start('ob_gzhandler');
// The file exists:
if (file_exists($_GET['file']))
echo file_get_contents($_GET['file']);
// 404 error document
else
include '404.php';
ob_end_flush();
?>
Viola! The client now has a copy of a file and a version number for future reference. You just saved yourself a ton of bandwidth in repeated file accesses.
Conclusion 🔚
You should now be well-versed in handling scaled images and minifying your JavaScript and CSS components. This introduction to caching and compressing data already makes you a more valuable and resource-savvy developer, but if you want to delve deeper down the rabbit hole that is micro-optimization, check out part two of this trilogy Reducing Server Calls or part three Reducing Parse Time. You can also follow me on LinkedIn, Medium, and Twitter for updates, or check out my portfolio at CharlesStover.com.
CVE IDs reserved. https[::]github.com/noxlumens/Vulnerability-Research/
6yWonderful article. I think this could really come in handy for my work website. Thanks Charles!