Best Practices for Speeding Up Your Web Site
Minimize HTTP Requests
background-positionproperties to display the desired image segment.
data:URL scheme to embed the image data in the actual page. This can increase the size of your HTML document. Combining inline images into your (cached) stylesheets is a way to reduce HTTP requests and avoid increasing the size of your pages. Inline images are not yet supported across all major browsers.
Use a Content Delivery Network
Add an Expires or a Cache-Control Header
- For static components: implement "Never expire" policy by setting far future
- For dynamic components: use an appropriate
Cache-Controlheader to help the browser with conditional requests
Expires: Thu, 15 Apr 2010 20:00:00 GMT
ExpiresDefault "access plus 10 years"
Accept-Encoding: gzip, deflate
Put Stylesheets at the Top
Put Scripts at the Bottom
document.writeto insert part of the page's content, it can't be moved lower in the page. There might also be scoping issues. In many cases, there are ways to workaround these situations.
DEFERattribute indicates that the script does not contain document.write, and is a clue to browsers that they can continue rendering. Unfortunately, Firefox doesn't support the
DEFERattribute. In Internet Explorer, the script may be deferred, but not as much as desired. If a script can be deferred, it can also be moved to the bottom of the page. That will make your web pages load faster.
Avoid CSS Expressions
background-color: expression( (new Date()).getHours()%2 ? "#B8D4FF" : "#F08A00" );
expressionmethod is ignored by other browsers, so it is useful for setting properties in Internet Explorer needed to create a consistent experience across browsers.
Reduce DNS Lookups
DnsCacheTimeoutregistry setting. Firefox caches DNS lookups for 1 minute, controlled by the
network.dnsCacheExpirationconfiguration setting. (Fasterfox changes this to 1 hour.)
Redirects are accomplished using the 301 and 302 status codes. Here's an example of the HTTP headers in a 301 response:
HTTP/1.1 301 Moved Permanently Location: http://example.com/newuri Content-Type: text/html
The browser automatically takes the user to the URL specified in the
Location field. All the information necessary for a redirect is in the headers. The body of the response is typically empty. Despite their names, neither a 301 nor a 302 response is cached in practice unless additional headers, such as
The main thing to remember is that redirects slow down the user experience. Inserting a redirect between the user and the HTML document delays everything in the page since nothing in the page can be rendered and no components can start being downloaded until the HTML document has arrived.
One of the most wasteful redirects happens frequently and web developers are generally not aware of it. It occurs when a trailing slash (/) is missing from a URL that should otherwise have one. For example, going to http://astrology.yahoo.com/astrology results in a 301 response containing a redirect tohttp://astrology.yahoo.com/astrology/ (notice the added trailing slash). This is fixed in Apache by using
mod_rewrite, or the
DirectorySlashdirective if you're using Apache handlers.
Connecting an old web site to a new one is another common use for redirects. Others include connecting different parts of a website and directing the user based on certain conditions (type of browser, type of user account, etc.). Using a redirect to connect two web sites is simple and requires little additional coding. Although using redirects in these situations reduces the complexity for developers, it degrades the user experience. Alternatives for this use of redirects include using
mod_rewrite if the two code paths are hosted on the same server. If a domain name change is the cause of using redirects, an alternative is to create a CNAME (a DNS record that creates an alias pointing from one domain name to another) in combination with
Remove Duplicate Scripts
Unnecessary HTTP requests happen in Internet Explorer, but not in Firefox. In Internet Explorer, if an external script is included twice and is not cacheable, it generates two HTTP requests during page loading. Even if the script is cacheable, extra HTTP requests occur when the user reloads the page.
One way to avoid accidentally including the same script twice is to implement a script management module in your templating system. The typical way to include a script is to use the SCRIPT tag in your HTML page.
HTTP/1.1 200 OK Last-Modified: Tue, 12 Dec 2006 03:03:59 GMT ETag: "10c24bc-4ab-457e1c1f" Content-Length: 12195
If-None-Matchheader to pass the ETag back to the origin server. If the ETags match, a 304 status code is returned reducing the response by 12195 bytes for this example.
GET /i/yahoo.gif HTTP/1.1 Host: us.yimg.com If-Modified-Since: Tue, 12 Dec 2006 03:03:59 GMT If-None-Match: "10c24bc-4ab-457e1c1f" HTTP/1.1 304 Not Modified
inode-size-timestamp. Although a given file may reside in the same directory across multiple servers, and have the same file size, permissions, timestamp, etc., its inode is different from one server to the next.
ChangeNumberis a counter used to track configuration changes to IIS. It's unlikely that the
ChangeNumberis the same across all IIS servers behind a web site.
Expiresheader, a conditional GET request is still made whenever the user hits Reload or Refresh.
Last-Modifiedheader validates based on the component's timestamp. And removing the ETag reduces the size of the HTTP headers in both the response and subsequent requests. This Microsoft Support article describes how to remove ETags. In Apache, this is done by simply adding the following line to your Apache configuration file:
Make Ajax Cacheable
&t=1190241612. If the address book hasn't been modified since the last download, the timestamp will be the same and the address book will be read from the browser's cache eliminating an extra HTTP roundtrip. If the user has modified her address book, the timestamp ensures the new URL doesn't match the cached response, and the browser will request the updated address book entries.
Flush the Buffer Early
Use GET for AJAX Requests
XMLHttpRequest, POST is implemented in the browsers as a two-step process: sending the headers first, then sending data. So it's best to use GET, which only takes one TCP packet to send (unless you have a lot of cookies). The maximum URL length in IE is 2K, so if you send more than 2K data you might not be able to use GET.
- Unconditional preload - as soon as onload fires, you go ahead and fetch some extra components. Check google.com for an example of how a sprite image is requested onload. This sprite image is not needed on the google.com homepage, but it is needed on the consecutive search result page.
- Conditional preload - based on a user action you make an educated guess where the user is headed next and preload accordingly. On search.yahoo.comyou can see how some extra components are requested after you start typing in the input box.
- Anticipated preload - preload in advance before launching a redesign. It often happens after a redesign that you hear: "The new site is cool, but it's slower than before". Part of the problem could be that the users were visiting your old site with a full cache, but the new one is always an empty cache experience. You can mitigate this side effect by preloading some components before you even launched the redesign. Your old site can use the time the browser is idle and request images and scripts that will be used by the new site
Reduce the Number of DOM Elements
s only to fix layout issues? Maybe there's a better and more semantically correct way to do your markup.
s only when it makes sense semantically, and not because it renders a new line.
Split Components Across Domains
www.example.organd split static components between
Minimize the Number of iframes
- Helps with slow third-party content like badges and ads
- Security sandbox
- Download scripts in parallel
- Costly even if blank
- Blocks page onload
HTTP requests are expensive so making an HTTP request and getting a useless response (i.e. 404 Not Found) is totally unnecessary and will slow down the user experience without any benefit.
Reduce Cookie Size
HTTP cookies are used for a variety of reasons such as authentication and personalization. Information about cookies is exchanged in the HTTP headers between web servers and browsers. It's important to keep the size of cookies as low as possible to minimize the impact on the user's response time.
For more information check "When the Cookie Crumbles" by Tenni Theurer and Patty Chi. The take-home of this research:
- Eliminate unnecessary cookies
- Keep cookie sizes as low as possible to minimize the impact on the user response time
- Be mindful of setting cookies at the appropriate domain level so other sub-domains are not affected
- Set an Expires date appropriately. An earlier Expires date or none removes the cookie sooner, improving the user response time
Use Cookie-free Domains for Components
When the browser makes a request for a static image and sends cookies together with the request, the server doesn't have any use for those cookies. So they only create network traffic for no good reason. You should make sure static components are requested with cookie-free requests. Create a subdomain and host all your static components there.
If your domain is
www.example.org, you can host your static components on
static.example.org. However, if you've already set cookies on the top-level domain
example.org as opposed to
www.example.org, then all the requests to
static.example.org will include those cookies. In this case, you can buy a whole new domain, host your static components there, and keep this domain cookie-free. Yahoo! uses
yimg.com, YouTube uses
ytimg.com, Amazon uses
images-amazon.com and so on.
Another benefit of hosting static components on a cookie-free domain is that some proxies might refuse to cache the components that are requested with cookies. On a related note, if you wonder if you should use example.org or www.example.org for your home page, consider the cookie impact. Omitting www leaves you no choice but to write cookies to
*.example.org, so for performance reasons it's best to use the www subdomain and write the cookies to that subdomain.
Minimize DOM Access
- Cache references to accessed elements
- Update nodes "offline" and then add them to the tree
For more information check the YUI theatre's "High Performance Ajax Applications" by Julien Lecomte.
Develop Smart Event Handlers
Sometimes pages feel less responsive because of too many event handlers attached to different elements of the DOM tree which are then executed too often. That's why using event delegation is a good approach. If you have 10 buttons inside a
div, attach only one event handler to the div wrapper, instead of one handler for each button. Events bubble up so you'll be able to catch the event and figure out which button it originated from.
You also don't need to wait for the onload event in order to start doing something with the DOM tree. Often all you need is the element you want to access to be available in the tree. You don't have to wait for all images to be downloaded.
DOMContentLoaded is the event you might consider using instead of onload, but until it's available in all browsers, you can use the YUI Event utility, which has an
For more information check the YUI theatre's "High Performance Ajax Applications" by Julien Lecomte.
Choose over @import
One of the previous best practices states that CSS should be at the top in order to allow for progressive rendering.
@import behaves the same as using
at the bottom of the page, so it's best not to use it.
AlphaImageLoader filter aims to fix a problem with semi-transparent true color PNGs in IE versions < 7. The problem with this filter is that it blocks rendering and freezes the browser while the image is being downloaded. It also increases memory consumption and is applied per element, not per image, so the problem is multiplied.
The best approach is to avoid
AlphaImageLoader completely and use gracefully degrading PNG8 instead, which are fine in IE. If you absolutely need
AlphaImageLoader, use the underscore hack
_filter as to not penalize your IE7+ users.
After a designer is done with creating the images for your web page, there are still some things you can try before you FTP those images to your web server.
- You can check the GIFs and see if they are using a palette size corresponding to the number of colors in the image. Using imagemagick it's easy to check using
identify -verbose image.gif
When you see an image using 4 colors and a 256 color "slots" in the palette, there is room for improvement.
- Try converting GIFs to PNGs and see if there is a saving. More often than not, there is. Developers often hesitate to use PNGs due to the limited support in browsers, but this is now a thing of the past. The only real problem is alpha-transparency in true color PNGs, but then again, GIFs are not true color and don't support variable transparency either. So anything a GIF can do, a palette PNG (PNG8) can do too (except for animations). This simple imagemagick command results in totally safe-to-use PNGs:
convert image.gif image.png
"All we are saying is: Give PiNG a Chance!"
- Run pngcrush (or any other PNG optimizer tool) on all your PNGs. Example:
pngcrush image.png -rem alla -reduce -brute result.png
- Run jpegtran on all your JPEGs. This tool does lossless JPEG operations such as rotation and can also be used to optimize and remove comments and other useless information (such as EXIF information) from your images.
jpegtran -copy none -optimize -perfect src.jpg dest.jpg
Optimize CSS Sprites
- Arranging the images in the sprite horizontally as opposed to vertically usually results in a smaller file size.
- Combining similar colors in a sprite helps you keep the color count low, ideally under 256 colors so to fit in a PNG8.
- "Be mobile-friendly" and don't leave big gaps between the images in a sprite. This doesn't affect the file size as much but requires less memory for the user agent to decompress the image into a pixel map. 100x100 image is 10 thousand pixels, where 1000x1000 is 1 million pixels
Don't Scale Images in HTML
Don't use a bigger image than you need just because you can set the width and height in HTML. If you need
then your image (mycat.jpg) should be 100x100px rather than a scaled down 500x500px image.
Make favicon.ico Small and Cacheable
The favicon.ico is an image that stays in the root of your server. It's a necessary evil because even if you don't care about it the browser will still request it, so it's better not to respond with a
404 Not Found. Also since it's on the same server, cookies are sent every time it's requested. This image also interferes with the download sequence, for example in IE when you request extra components in the onload, the favicon will be downloaded before these extra components.
So to mitigate the drawbacks of having a favicon.ico make sure:
- It's small, preferably under 1K.
- Set Expires header with what you feel comfortable (since you cannot rename it if you decide to change it). You can probably safely set the Expires header a few months in the future. You can check the last modified date of your current favicon.ico to make an informed decision.
Imagemagick can help you create small favicons
Keep Components under 25K
This restriction is related to the fact that iPhone won't cache components bigger than 25K. Note that this is the uncompressed size. This is where minification is important because gzip alone may not be sufficient.
For more information check "Performance Research, Part 5: iPhone Cacheability - Making it Stick" by Wayne Shea and Tenni Theurer.
Pack Components into a Multipart Document
Packing components into a multipart document is like an email with attachments, it helps you fetch several components with one HTTP request (remember: HTTP requests are expensive). When you use this technique, first check if the user agent supports it (iPhone does not).
Avoid Empty Image src
Image with empty string src attribute occurs more than one will expect. It appears in two form:
- straight HTML
var img = new Image();
img.src = "";
Both forms cause the same effect: browser makes another request to your server.
- Internet Explorer makes a request to the directory in which the page is located.
- Safari and Chrome make a request to the actual page itself.
- Firefox 3 and earlier versions behave the same as Safari and Chrome, but version 3.5 addressed this issue[bug 444931] and no longer sends a request.
- Opera does not do anything when an empty image src is encountered.
Why is this behavior bad?
- Cripple your servers by sending a large amount of unexpected traffic, especially for pages that get millions of page views per day.
- Waste server computing cycles generating a page that will never be viewed.
- Possibly corrupt user data. If you are tracking state in the request, either by cookies or in another way, you have the possibility of destroying data. Even though the image request does not return an image, all of the headers are read and accepted by the browser, including all cookies. While the rest of the response is thrown away, the damage may already be done.
The root cause of this behavior is the way that URI resolution is performed in browsers. This behavior is defined in RFC 3986 - Uniform Resource Identifiers. When an empty string is encountered as a URI, it is considered a relative URI and is resolved according to the algorithm defined in section 5.2. This specific example, an empty string, is listed in section 5.4. Firefox, Safari, and Chrome are all resolving an empty string correctly per the specification, while Internet Explorer is resolving it incorrectly, apparently in line with an earlier version of the specification, RFC 2396 - Uniform Resource Identifiers (this was obsoleted by RFC 3986). So technically, the browsers are doing what they are supposed to do to resolve relative URIs. The problem is that in this context, the empty string is clearly unintentional.
HTML5 adds to the description of the tag's src attribute to instruct browsers not to make an additional request in section 4.8.2:
The src attribute must be present, and must contain a valid URL referencing a non-interactive, optionally animated, image resource that is neither paged nor scripted. If the base URI of the element is the same as the document's address, then the src attribute's value must not be the empty string.
Hopefully, browsers will not have this problem in the future. Unfortunately, there is no such clause for