And what if we got together a bunch of experts who work on large sites to create a definitive front-end performance guide?
And not just one of those boring guides made for robots, what if we did something fun? What about getting together Briza Bueno (Americanas.com), Davidson Fellipe (Globo.com), Giovanni Keppelen (ex-Peixe Urbano), Jaydson Gomes (Terra), Marcel Duran (Twitter), Mike Taylor (Opera), Renato Mangini (Google), and Sérgio Lopes (Caelum) to make the best reference possible?
That's exactly what we've done! And we'll guide you in this battle to create even faster sites.
— Zeno Rocha, project lead.
Does performance really matter? ∞
Of course it matters and you know it. So why do we keep making slow sites that lead to a bad user experience? This is a community-driven practical guide that will show you how to make websites faster. Let's not waste time showing millionaire performance cases, let's get straight to the point!
HTML
Avoid inline/embedded code ∞
There are three basic ways for you to include CSS or JavaScript on your page:
1) Inline: the CSS is defined inside a style attribute and the JavaScript inside an onclick attribute for example, in any HTML tag;
2) Embedded: the CSS is defined inside a <style> tag and the JavaScript inside a <script> tag;
3) External: the CSS is loaded from a <link> and the JavaScript from the src attribute of the <script> tag.
The first two options, despite reducing the number of HTTP requests, actually increase the size of your HTML document. But this can be useful when you have small assets and the cost of making a request is greater. In this case, run tests to evaluate if there's any actual gains in speed. Also be sure to evaluate the purpose of your page and its audience: if you expect people to only visit it once, for example for some temporary campaign where you never expect return visitors, inlining/embedded code will help in reducing the number of HTTP requests.
> Avoid manually authoring CSS/JS in the middle of your HTML (automating this process with tools is preferred).
The third option not only improves the organization of your code, but also makes it possible for the browser to cache it. This option should be preferred for the majority of cases, especially when working with large files and lots of pages.
Styles up top, scripts down bottom ∞
When we put stylesheets in the <head> we allow the page to render progressively, which gives the impression to our users that the page is loading quickly.
<head>
<meta charset="UTF-8">
<title>Browser Diet</title>
<!-- CSS -->
<link rel="stylesheet" href="style.css" media="all">
</head>
But if we put them at the end of the page, the page will be rendered without styles until the CSS is downloaded and applied.
On the other hand, when dealing with JavaScript, it's important to place scripts at the bottom of the page as they block rendering while they're being loaded and executed.
<body>
<p>Lorem ipsum dolor sit amet.</p>
<!-- JS -->
<script src="script.js"></script>
</body>
Minify your HTML ∞
To maintain readable code, it's a good idea to write comments and use indentation.
<p>Lorem ipsum dolor sit amet.</p>
<!-- My List -->
<ul>
<li><a href="#"></a></li>
<li><a href="#"></a></li>
<li><a href="#"></a></li>
</ul>
But to the browser, none of this actually matters. For this reason, it can be useful to minify your HTML with automated tools.
<p>Lorem ipsum dolor sit amet.</p><ul><li><a href="#"></a></li><li><a href="#"></a></li><li><a href="#"></a></li></ul>
This will shave bytes by removing extra whitespaces, comments and other unneeded characters without breaking the content structure.
As a result, your users will enjoy faster downloads, parsing and execution.
On the right side you see a few samples of HTML Compressor results with default settings.
Try out async ∞
To explain how this attribute is useful for better performance, it's better to understand what happens when we don't use it.
<script src="example.js"></script>
In this form, the page has to wait for the script to be fully downloaded, parsed and executed before being able to parse and render any following HTML. This can significantly add to the load time of your page. Sometimes this behavior might be desired, but generally not.
<script async src="example.js"></script>
The script is downloaded asynchronously while the rest of the page continues to get parsed. The script is guaranteed to be executed as soon as the download is complete. Keep in mind that multiple async scripts will be executed in no specific order.
CSS
Minify your stylesheets ∞
To maintain readable code, it's a good idea to write comments and use indentation:
.center {
width: 960px;
margin: 0 auto;
}
/* --- Structure --- */
.intro {
margin: 100px;
position: relative;
}
But to the browser, none of this actually matters. For this reason, always remember to minify your CSS through automated tools.
.center{width:960px;margin:0 auto}.intro{margin:100px;position:relative}
This will shave bytes from the filesize, which results in faster downloads, parsing and execution.
For those that use pre-processors like Sass, Less, and Stylus, it's possible to configure your compiled CSS output to be minified.
Combining multiple CSS files ∞
Another best practice for organization and maintenance of styles is to separate them into modular components.
<link rel="stylesheet" href="structure.css" media="all">
<link rel="stylesheet" href="banner.css" media="all">
<link rel="stylesheet" href="layout.css" media="all">
<link rel="stylesheet" href="component.css" media="all">
<link rel="stylesheet" href="plugin.css" media="all">
However, an HTTP request is required for each one of these files (and we know that browsers can only download a limited number resources in parallel).
<link rel="stylesheet" href="main.css" media="all">
So combine your CSS. Having a smaller number of files will result in a smaller number of requests and a faster loading page.
Want to have the best of both worlds? Automate this process through a build tool.
Prefer <link> over @import ∞
There's two ways to include an external stylesheet in your page: either via the <link> tag:
<link rel="stylesheet" href="style.css">
Or through the @import directive (inside an external stylesheet or in an inline <style> tag):
@import url('style.css');
When you use the second option through an external stylesheet, the browser is incapable of downloading the asset in parallel, which can block the download of other assets.
JavaScript
Load 3rd party content asynchronously ∞
Who has never loaded a third-party content to embed a Youtube video or like/tweet button?
The big problem is that these codes aren't always delivered efficiently, either by user's connection, or the connection to the server where they are hosted. Or this service might be temporarily down or even be blocked by the user's or his company's firewall.
To avoid it becoming a critical issue when loading a site or worse, lock the entire page load, always load these codes asynchronously (or use Friendly iFrames).
var script = document.createElement('script'),
scripts = document.getElementsByTagName('script')[0];
script.async = true;
script.src = url;
scripts.parentNode.insertBefore(script, scripts);
Alternatively, if you want to load multiple 3rd party widgets, you can asynchronously load them with using this script.
> Video / References
Cache array lengths ∞
The loop is undoubtedly one of the most important parts related to JavaScript performance. Try to optimize the logic inside a loop so that each iteration is done efficiently.
One way to do this is to store the size of the array that will be covered, so it doesn't need to be recalculated every time the loop is iterated.
var arr = new Array(1000),
len, i;
for (i = 0; i < arr.length; i++) {
// Bad - size needs to be recalculated 1000 times
}
for (i = 0, len = arr.length; i < len; i++) {
// Good - size is calculated only 1 time and then stored
}
> Note: Although modern browsers engines automatically optimize this process, remains a good practice to suit the legacy browsers that still linger.
In iterations over collections in HTML as a list of Nodes (NodeList) generated for example by document.getElementsByTagName('a') this is particularly critical. These collections are considered "live", i.e. they are automatically updated when there are changes in the element to which they belong.
var links = document.getElementsByTagName('a'),
len, i;
for (i = 0; i < links.length; i++) {
// Bad - each iteration the list of links will be recalculated to see if there was a change
}
for (i = 0, len = links.length; i < len; i++) {
// Good - the list size is first obtained and stored, then compared each iteration
}
// Terrible: infinite loop example
for (i = 0; i < links.length; i++) {
document.body.appendChild(document.createElement('a'));
// each iteration the list of links increases, never satisfying the termination condition of the loop
// this would not happen if the size of the list was stored and used as a condition
}
Avoid document.write ∞
The use of document.write causes a dependency to the page on its return to be fully loaded.
This (bad) practice has been abolished for years by developers, but there are still cases where its use is still required, as in synchronous fallback for some JavaScript file.
HTML5 Boilerplate, for example, uses this technique to load jQuery locally if Google's CDN doesn't respond.
<script src="//ajax.googleapis.com/ajax/libs/jquery/1.9.0/jquery.min.js"></script>
<script>window.jQuery || document.write('<script src="js/vendor/jquery-1.9.0.min.js"><\/script>')</script>
> Attention: document.write performed during or after window.onload event replaces the entire content of the current page.
<span>foo</span>
<script>
window.onload = function() {
document.write('<span>bar</span>');
};
</script>
The result of the final page will be only bar and not foobar as expected. The same occurs when it runs after window.onload event.
<span>foo</span>
<script>
setTimeout(function() {
document.write('<span>bar</span>');
}, 1000);
window.onload = function() {
// ...
};
</script>
The result will be the same as the previous example if the function scheduled by setTimeout is executed after window.onload event.
Minimize Repaints and Reflows ∞
Repaints and reflows are caused when there's any process of re-rendering the DOM when certain property or element is changed.
Repaints are triggered when the appearance of an element is changed without changing its layout. Nicole Sullivan describes this as a change of styles like changing a background-color.
Reflows are the most costly, since they are caused by changing the page layout, such as change the width of an element.
There is no doubt that excessive reflows and repaints should be avoided, so instead of doing this:
var div = document.getElementById("to-measure"),
lis = document.getElementsByTagName('li'),
i, len;
for (i = 0, len = lis.length; i < len; i++) {
lis[i].style.width = div.offsetWidth + 'px';
}
Do this:
var div = document.getElementById("to-measure"),
lis = document.getElementsByTagName('li'),
widthToSet = div.offsetWidth,
i, len;
for (i = 0, len = lis.length; i < len; i++) {
lis[i].style.width = widthToSet + 'px';
}
When you set style.width, the browser needs to recalculate layout. Usually, changing styles of many elements only results in one reflow, as the browser doesn't need to think about it until it needs to update the screen. However, in the first example, we ask for offsetWidth, which is the layout-width of the element, so the browser needs to recalculate layout.
If you need to read layout data from the page, do it all together before setting anything that changes layout, as in the second example.
> Demo / References
Avoid unnecessary DOM manipulations ∞
Everytime you touch the DOM, unnecessarily, a panda dies.
Seriously, navigating the DOM is costly. Even though JavaScript engines have become increasingly powerful and fast, always try to optimize queries of the DOM.
One of the simplest practices to adopt is, when an element has to be accessed more than once, save it in a variable, so you don't need to query the DOM multiple times.
// really bad!
for (var i = 0; i < 100; i++) {
document.getElementById("myList").innerHTML += "<span>" + i + "</span>";
}
// much better :)
var myList = "";
for (var i = 0; i < 100; i++) {
myList += "<span>" + i + "</span>";
}
document.getElementById("myList").innerHTML = myList;
Minify your script ∞
Just like CSS, to maintain readable code, it's a good idea to write comments and use indentation:
BrowserDiet.app = function() {
var foo = true;
return {
bar: function() {
// do something
}
};
};
But to the browser, none of this actually matters. For this reason, always remember to minify your JavaScript through automated tools.
BrowserDiet.app=function(){var a=!0;return{bar:function(){}}}
This will shave bytes from the filesize, which results in faster downloads, parsing and execution.
Combine multiple JS files into one ∞
Another best practice for organization and maintenance of scripts is to separate them into modular components.
<script src="navbar.js"></script>
<script src="component.js"></script>
<script src="page.js"></script>
<script src="framework.js"></script>
<script src="plugin.js"></script>
However, an HTTP request is required for each one of these files (and we know that browsers can only download a limited number resources in parallel).
<script src="main.js"></script>
So combine your JS. Having a smaller number of files will result in a smaller number of requests and a faster loading page.
Want to have the best of both worlds? Automate this process through a build tool.
jQuery
Always use the latest version of jQuery ∞
The core jQuery team is always looking to bring improvements to the library, through better code readability, new functionality and optimization of existing algorithms.
For this reason, always use the latest version of jQuery, which is always available here, if you want to copy it to a local file:
http://code.jquery.com/jquery-latest.js
But never reference that URL in a <script> tag, it may create problems in the future as newer versions are automatically served to your site
before you've had a chance to test them. Instead, link to the latest version of jQuery that you need specifically.
<script src="//ajax.googleapis.com/ajax/libs/jquery/1.9.1/jquery.min.js"></script>
Just like the wise Barney Stinson says, "New is always better" :P
Selectors ∞
Selectors is one of the most important issues in the use of jQuery. There are many different ways to select an element from the DOM, but that doesn't mean they have the same performance, you can select an element using classes, IDs or methods like find(), children().
Among all of them, select an ID is the fastest one, because its based on a native DOM operation:
$("#foo");
Use for instead of each ∞
The use of native JavaScript functions nearly always results in faster execution than the ones in jQuery. For this reason, instead of using the jQuery.each method, use JavaScript's own for loop.
But pay attention, even though for in is native, in many cases it performs worse than jQuery.each.
And the tried and tested for loop gives us another opportunity to speed things up by caching the length of collections we're iterating over.
for ( var i = 0, len = a.length; i < len; i++ ) {
e = a[i];
}
The use of reverse while and reverse for loops is a hot topic in the community and are frequently cited as the fastest form of iteration. However it's typically avoided for being less legible.
Don't use jQuery... ∞
...unless it's absolutely necessary. :)
Sometimes vanilla JavaScript can be even simpler than jQuery.
Why use the attr() method to search for an ID?
$('a').on('click', function() {
console.log( $(this).attr('id') );
});
If you can can get this attribute natively through this:
$('a').on('click', function() {
console.log( this.id );
});
And it's faster.
Images
Use CSS Sprites ∞
This technique is all about grouping various images into a single file.
![]()
And then positioning them with CSS.
.icon-foo {
background-image: url('mySprite.png');
background-position: -10px -10px;
}
.icon-bar {
background-image: url('mySprite.png');
background-position: -5px -5px;
}
As a result, you've dramatically reduced the number of HTTP requests and avoided any potential delay of other resources on your page.
When using your sprite, avoid leaving too much white space between images. This won't affect the size of the file, but will have an effect on memory consumption.
And despite nearly everyone knowing about sprites, this technique isn't widely used—perhaps due to developers not using automated tools to generate sprites. We've highlighted a few that can help you out with this.
Data URI ∞
This technique is an alternative to using CSS sprites.
A Data-URI is a way to inline the content of the URI you would normally point to. In this example we are using it to inline the content of the CSS background images in order to reduce the number of HTTP requests required to load a page.
Before:
.icon-foo {
background-image: url('foo.png');
}
After:
.icon-foo {
background-image: url('data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAAAEAAAABAQMAAAAl21bKAAAAA1BMVEUAAACnej3aAAAAAXRSTlMAQObYZgAAAApJREFUCNdjYAAAAAIAAeIhvDMAAAAASUVORK5CYII%3D');
}
All browsers from IE8 and above support this base64-encoded technique.
Both this method and the CSS spriting method require build time tools to be maintainable. This method has the advantage of not requiring manual spriting placement as it keeps your images in individual files during development.
However has the disadvantage of growing the size of your HTML/CSS considerably if you have large images. It is not recommended to use this method if you aren't gzipping your HTML/CSS during HTTP requests as the size overhead might negate the speed gains you get from minimizing the number of HTTP requests.
Don't rescale images in markup ∞
Always define the width and height attributes of an image. This will help avoid unnecessary repaints and reflows during rendering.
<img width="100" height="100" src="logo.jpg" alt="Logo">
Knowing this, John Q. Developer who has a 700x700px image decides to use this technique to display the image as 50x50px.
What Mr. Developer doesn't realize is that dozens of unnecessary kilobytes will be sent over the wire.
Always keep in mind: just because you can define with width and height of an image in HTML doesn't mean you should do this to rescale down large images.
Optimize your images ∞
Image files contains a lot of information that is useless on the Web. For example, a JPEG photo can have Exif metadata from the camera (date, camera model, location, etc.). A PNG contains information about colors, metadata, and sometimes even a miniature embedded thumbnail. None of this is used by the browser and contributes to filesize bloat.
There are tools that exist for image optimization that will remove all this unnecessary data and give you a slimmer file without degrading quality. We say they perform lossless compression.
Another way to optimize images is to compress them at the cost of visual quality. We call these lossy optimizations. When you export a JPEG, for example, you can choose the quality level (a number between 0 and 100). Thinking about performance, always choose the lowest number possible where the visual quality is still acceptable. Another common lossy technique is to reduce the color palette in a PNG or to convert PNG-24 files into PNG-8.
To improve user perceived performance, you should also transform all your JPEG files in progressive ones. Progressive JPEGs have great browser support, are very simple to create and have no significant performance penalty. The upside is that the image will appear sooner on the page (see demo).
Server
Enable smart caching ∞
The best way to optimize requests made from your page is to not make requests, obviously. And one of the most useful ways to avoid unnecessary requests is to let the browser cache assets. By default, the browser is left to decide how long to cache files. But we can control the exact time that a file will be kept in cache.
This type of configuration is done on the server (and will depend on which server setup you have). With Apache, for example, you can add the following configuration in an .htaccess file:
ExpiresActive On
ExpiresByType image/gif "access plus 6 months"
ExpiresByType image/jpeg "access plus 6 months"
ExpiresByType image/png "access plus 6 months"
ExpiresByType text/css "access plus 6 months"
ExpiresByType text/javascript "access plus 6 months"
ExpiresByType application/javascript "access plus 6 months"
These instructions cache images, CSS and JS for 6 months—it's recommended to cache them for at least one month. Other servers can be similarly configured.
One important thing to remember is that once cached, the browser won't request a new file anymore. If we need to change the content of the file, it won't work in the way we might expect. To send a new version, we need to change the name of the file. One way to do this is to add some form of versioning or a timestamp to the filename. For example, instead of home.js you can use home-v1.js and when you need to update the file, change the name to home-v2.js, etc.
Another common form of cache-busting is to use a GET parameter in the URL: home.js?v=1 and home.js?v=2. This method has the disadvantage over file renaming that some proxies on the internet ignore GET parameters, but adhere to the cache rules, which makes you unable to bust the cache on them when you need to.
GZIP ∞
A large percentage of a site's content is simple text (HTML, CSS, JavaScript, JSON, XML, etc.). For this kind of content, it's good practice to turn on GZIP compression at the server level.
GZIP compresses data on the server before sending it over the network to the user's browser, which decodes it on the fly for the user. It's just like zipping up a file before sending to someone to make it smaller. All modern browsers and servers support GZIP.
Configuration is set up on the server. On Apache, for example, you can edit the .htaccess file and turn on GZIP for the types of files that you want:
AddOutputFilterByType DEFLATE text/html
AddOutputFilterByType DEFLATE text/plain
AddOutputFilterByType DEFLATE text/xml
AddOutputFilterByType DEFLATE text/css
AddOutputFilterByType DEFLATE application/javascript
All popular servers are as just as simple to set up (consult the docs for yours!) and it's one of the most effective tips for improving page performance.
Bonus
Diagnosis tools: your best friend ∞
If you'd like to venture into this world of web performance, it's crucial to install the YSlow and PageSpeed extensions in your browser—they'll be your best friends from now on.
Or, if you prefer online tools, visit the WebPageTest, HTTP Archive, or PageSpeed sites.
In general each will analyse your site's performance and create a report that gives your site a grade coupled with invaluable advice to help you resolve the potential problems.
That's it for today! ∞
We hope that after reading this guide you can get your site in shape. :)
And remember, like all things in life, there's no such thing as a silver bullet. Performance tuning of your application is a worthwhile venture, but should not be the sole basis of all your development decisions—at times you'll need to weigh out the costs and benefits.
Want to learn more? Check out the References that we used to write this guide.
Have suggestions? Send a tweet to @BrowserDiet or a pull request on Github.
Don't forget to share with your friends, let's make a faster web for everyone. \o/