« Blog Home

§ Faster webpages

I was looking over my google webmaster tools the other day, and on the Site performance page there's a link to Page Speed, an add-on for Firefox.  (Actually, an add-on to Firebug, a Firefox add-on.)  I thought I'd take a look - maybe there'd be some good pointers.  Here's where that led...

I suppose I should also mention here yahoo's YSlow add-on for Firefox.  (Again, an add-on to Firebug, a Firefox add-on.)  For all I know, maybe using YSlow first would be more productive.  In my case, I discovered google's Page Speed first.

When someone clicks on a link to your web page, what you really want is for something to show up in their browser ASAP.  Don't you?  Personally, if I have to wait more than a few seconds, I'll probably bail out back to the search results and go elsewhere; but if the page seems to be loading, I'll probably be patient enough to see what shows up.

As I seem to always say on these longish posts, I didn't keep a log of all this activity, so there's most likely stuff I've forgotten.  Some of these things depend on the web server's defaults, so you may not need some of these, and you may need others that I don't.  I should also point out that I am no expert on any of this; don't believe me!  Think - what if you "learned from me" last month?  Always do your own research.  I would also like to point out that I probably use incorrect terminology, in a technical sense; this isn't intended to be a technical paper.

CSS ΔSections

The first thing I discovered was that I have always made my CSS, well, as inefficient as possible!  Take a look at How to Write Efficient CSS Selectors and Writing Efficient CSS for more information.

In CSS, the tag, id, or classname before a set of rules is called the selector.  In my case, Page Speed complained loudly about my inefficient selectors.  Somewhere along the way, I had gotten the impression that something like this was "best practice" (since it makes logical sense for a left-to-right reading programmer):

div.BlogShort div.postinfo ul.tagLinkList li a {
  font-size: 1em;
  ...
}

Here's what I thought it meant:
When you find a div, see if its class is BlogShort; if not, skip the rest.
Now see if it has a div inside with class postinfo; if not, skip the rest.
Now see if it has a ul inside with class tagLinkList; if not, skip the rest.
etc.

No!  This is a disaster!  Don't ever do this.  CSS selectors are tested RIGHT TO LEFT.  Here's what really happens:
For every link (a) on the entire page, see if its parent is an li; if not, skip the rest.
Now see if the li's parent has a class of tagLinkList; if not, skip the rest.
Now see if the thing with class tagLinkList is a ul; if not, skip the rest.
etc.

One huge problem with this is that the browser has to check EVERY LINK ON THE PAGE!  This blog page you're viewing has something like 40 links on it, but this rule only applies to one or two of them, if any.  Even for the ones it does apply to, it has to check the parent, the next parent, the next parent, etc.  Now I give the tag links (below the blog post title) a class of postinfotaglink and use:

.postinfotaglink {
  font-size: 1em;
  ...
}

Incidentally, they're no longer in a ul, which actually simplified things, even though I had to hassle with the blog code for a while.

I do still make judicious use of inefficient selectors in the sideboxes for the blog:

.sideboxdiv {
  margin: 9px;
  font-weight: 900;
}
.sideboxdiv ul {
  margin: 0;
  padding-left: 25px;
}
.recentcommentsdiv ul {
  padding: 0;
  font-size: 0.7em;
  font-weight: 500;
  list-style-position: inside;
}

I suppose I should add classes or ids for these elements, too, but I don't feel like changing the blog code.  Maybe later.  But (currently) there are only four ul elements on the page, as opposed to the 40 links I talked about above.

The parts between the braces are called rules, and the browser has to parse and process them to prepare to display whatever selectors the rules apply to.  Well, this also takes some time, so it's wasteful to repeat the same rules over and over for separate selectors.  I used to do something like this for those sideboxes:

div.taglistdiv ul {
  margin: 0;
  padding-left: 25px;
}
div.archivelistdiv {
  font-weight: 700;
  margin: 9px;
}
div.archivelistdiv ul {
  margin: 0;
  padding-left: 25px;
}
div.authorlistdiv {
  font-weight: 700;
  margin: 9px;
}
div.authorlistdiv ul {
  margin: 0;
  padding-left: 25px;
}
div.recentcommentsdiv {
  font-weight: 700;
}

In addition to the bad practice of preceding each selector with div, I was setting the exact same margin, padding-left, and font-weight values repeatedly for very similar elements, so the browser had to figure out the exact same thing over and over.  This also makes the CSS larger, of course.  I also had all of the various colored boxes styled separately, so if I wanted to change the colors I had to find them all, copy-paste, copy-paste, oops I did that one wrong, etc.  Really, kind of dumb, and a real time waster.  Now I have a highlightbox class defined once for the whole site, and adjust only the color or border width for a couple of elements as needed.

Since I had done the CSS for the whole site like this, and a lot of things (like font size; always use ems where possible - did I mention do your own research?) were related to the parent's CSS, it's taken quite a bit of work to change everything.  Many of the HTML tags on the site now have classes defined, so some of the HTML files are a little larger; but the CSS, used on every page, parsed every time a page is displayed (even if pulled from cache), is 55% of its previous size, while the blog-specific CSS is 36% of its previous size.  (These sizes include minimizing.)

CACHING ΔSections

As I did some speed tests, I saw that many things that could be cached were being retrieved from the server too often.  At a minimum, the browser contacted the server to see if the file had changed since the last time it was grabbed for its cache.  Here, I'm talking about images, CSS, and javascript files.  For example, the little button images I use for the navigation links near the top of the page; they're identical on every page, but every page request checked to see if they had changed.  Well, they haven't changed recently, certainly not in the last 10 seconds.  The browser can't render the page until it knows what to display, so this caused an additional round-trip delay for every page just for a stupid button image!  I had thought that since the button image would naturally be in the browser's cache, so what?  One of the quotes that I show at the top of my pages is, "It was amazing how consistently wrong he could be."  No foolin'.

Have a look at Speed Up Sites with htaccess Caching.  I have added:

<ifmodule mod_expires.c>
  ExpiresActive On
  ExpiresDefault A604800
</ifmodule>
<ifmodule mod_headers.c>
  Header set Cache-Control "max-age=604800, public"
  Header unset ETag
  FileETag None
</ifmodule>

to selected .htaccess files on the site.  (If your server isn't Apache, or if those modules aren't available, you'll have to do something different.)  This tells browsers to not bother even checking for a new version for a week.  The public (on the Cache-Control header) tells proxy servers that they can cache it, too.  Some have suggested that the Expires and the Cache-Control headers should not both be used, but others say to use both.  How would I know?  Better safe than sorry...

JAVASCRIPT ΔSections

As I understand it (always suspicious), browsers have to inspect all of the javascript loaded before it can render the page; in fact, downloading anything else (CSS, images) will be delayed while the javascript is inspected (so load all CSS before any javascript).  After all, the javascript may change something (like the CSS, or the DOM structure).  If you don't need scripts to run before rendering, load it after rendering.  But... scripts are supposed to go in the head element, aren't they?  (The correct answer is yes.)  So now I just include in the head element:

<script type="text/javascript">
function downloadJSAtOnload() {
  var el = document.createElement('script');
  el.src = 'my.js';
  document.getElementsByTagName('head')[0].appendChild(el);
}
function makeDoubleDelegate(function1, function2) {
  return function() {
    if(function1) {
      function1();
    }
    if(function2) {
      function2();
    }
  };
}
window.onload = makeDoubleDelegate(window.onload, downloadJSAtOnload);
</script>

Examples I found of this append it to the body element, but, as I say, they are supposed to go in the head element, so that's where I append it.  Since the javascript file will be loaded when the window.onload event happens, I don't need to have a bunch of makeDoubleDelegate() lines for all of the functions I want to run at onload; just call the functions - it's all happening at onload.

I discovered jslint from Douglas Crockford.  While my javascript worked well enough, jslint helped make it a bit better.  I took most of the advice, but left a few things alone, mostly for code readability; I might need to be able to decipher my own code later...

MINIMIZING ΔSections

One cool thing about Page Speed is that it automatically gives you minimized version of CSS, javascript, and HTML.  I didn't use the minimized HTML, but I looked at it to see if there were some ideas I could use.  I thought it bizarre that a google tool seemed to give, well, bad - make that miserable - no, disasterous - advice on the HTML minimizing - be careful there; it changed the doctype, left off closing tags, etc.  However, the CSS and javascript minimizing worked great.  I strongly recommend using jslint before accepting the minimized javascript, though, just to be safe.

Incidentally, I believe the javascript minimizing is done with the jsmin code, also from Douglas Crockford.  His page explains how jsmin does its work; it also has a link to JS Minifier which gives more options for minimizing than the built-in code of Page Speed.

Page Speed also automatically offers you optimized images - I liked that quite a bit.  Out of all of the images on this site, I only had to adjust one of the optimized images in an image editor; the rest worked just fine.

(After Page Speed optimized my images, I checked them with YSlow and saved a few more bytes.)

CHARSET ΔSections

I've been using utf-8 for a while.  That makes me happy, considering some of the things I've read recently.  Check out UTF-8: The Secret of Character Encoding.

In any case, if you don't tell a browser what character set the page is in, it will make a guess to start with.  If it later decides that its guess was incorrect, it has to start over.  The best thing is to include an HTTP header with a charset; that way, the browser knows what charset to use before it even starts receiving the HTML.  Some have suggested, however, that the meta tag:

<meta content="text/html; charset=utf-8" http-equiv="content-type" />

(or whatever's appropriate) should also be included.  Is this necessary?  How would I know?  Better safe than sorry...

It's vitally important, however, that the HTTP header charset matches the meta tag - otherwise the browser believes one thing, then has to change its mind and start over.  Even more important, the charset of the (delivered) page better actually be what the header and meta tag claim!  (Otherwise the browser believes one thing, then has to change its mind and start over.)  If you give it three different things, well, exactly what do you expect to happen?  (The browser believes one thing, then has to change its mind and start over.  Twice.)  You'll be lucky if the page renders decently at all.

For my particular server, I had to add (to my root .htaccess file):

#for html, php
AddDefaultCharset UTF-8
#for css, js, xml
AddType 'text/css; charset=UTF-8' css
AddType 'application/x-javascript; charset=UTF-8' js
AddType 'application/xml; charset=UTF-8' xml

COMPRESSION ΔSections

Again, for my particular server, I had to add (to my root .htaccess file);

<IfModule mod_deflate.c>
  <FilesMatch "\.(css|js|xsl|xml)$">
    SetOutputFilter DEFLATE
  </FilesMatch>
</IfModule>

This apparently automagically (lots of magic in .htaccess!) takes care of a couple of things, including adding the Vary: Accept-Encoding HTTP header that can be a problem for some clients (i.e., if it's missing). (Wanna guess which popular client has this problem? Hint: it starts with an I and ends with an E.)

CSS SPRITES ΔSections

Update January 8, 2011:  I realized this should have been in this post originally, so I decided to add it here.

Another way to minimize http requests, bandwidth, and page rendering speed in general is to combine several images into a single image, then use CSS to show just part of the image right where you want it - thus, CSS sprites.  I use this for those little button images on the navigation links.  Since I have the button change when you hover over it, and I have two sizes of buttons (depending on the length of the text), the old way needed four separate images.  Besides, the hover image wouldn't load until the first time you hovered on a link, and this looked a kinda weird while it went blank and then finally loaded.  Those four images totaled 5962 bytes.  So what, right?  But they also used four transactions from your browser to the server, with the network latency and bandwidth overhead that entails.  I combined all four images into one, which is 2428 bytes.  Well, there's a savings of 3534 bytes right there, about the same total savings from minimizing the javascript and CSS files.  Since I stacked them vertically, I use some CSS like:

.lnkbtn {
  background: url('white-all.gif') no-repeat top left;
  height: 24px;
}
.lnkbtn1 {
  width: 75px;
}
.lnkbtn1:hover {
  background-position: 0 -24px;
}
.lnkbtn2 {
  background-position: 0 -48px;
  width: 95px;
}
.lnkbtn2:hover {
  background-position: 0 -72px;
}

to show the right piece as appropriate.

OTHER ΔSections

Page Speed will point out some other things, too.  For example, specifying image dimensions in the img tag.  I guess I knew about this, but didn't think it was important for small images, but it can make a difference.  If the height and width are specified in the img tag, the browser can start rendering the page before it has downloaded the image itself.  If not, it has to get the image first, in order to see what its dimensions are, before it can render the page.

Page Speed will also check your page for bad requests, DNS lookups, redirects, duplicate resources, and so forth.

LINKS ΔSections

Here are the links I referred to, plus a couple more:

http://code.google.com/speed/page-speed
http://developer.yahoo.com/yslow
http://answers.oreilly.com/topic/647-how-to-write-efficient-css-selectors
https://developer.mozilla.org/en/Writing_Efficient_CSS
http://www.askapache.com/htaccess/speed-up-sites-with-htaccess-caching.html
http://www.jslint.com
http://javascript.crockford.com/jsmin.html
http://fmarcia.info/jsmin/test.html
http://htmlpurifier.org/docs/enduser-utf8.html
http://developer.yahoo.com/performance/rules.html
http://www.die.net/musings/page_load_time
http://www.alistapart.com/articles/sprites
http://searchengineland.com/google-now-counts-site-speed-as-ranking-factor-39708

FINALLY... ΔSections

If your website is small, doesn't use many images, uses minimal javascript and CSS, etc., then none of this matters very much.  But... a few years ago, this website used neither CSS nor javascript, had no contact form, no blog...  Things tend to grow larger and more complicated as time passes.  If you start doing things efficiently in the first place, and continue doing so, you won't have to go to pains to revamp a bigger mess later.

Each topic in this post may seem negligible, but added all together they have cut my average page load time by about 75%.  I like fast pages.  It’s Official: Google Now Counts Site Speed As A Ranking Factor, so it looks like google likes fast pages, too.  How about you?

last edited on January 14th, 2011 at 3:44 PM

Comments

No Comments Here. Add yours below!

Add your comment

Name:
Email: (Will not be displayed - Privacy policy)
Website:
Comments:
  random image I can't read it!
Give me a different one!
Verify Post: Input the text from the image above to verify this post (helps prevent spam)
 

« Blog Home


“...I may be completely off base, and panicking prematurely.”
“I don’t think so.  I think you’re panicking post-maturely.  In fact, if you were panicking any later it would be practically posthumously.  I’ve been panicking for days.”
Miles & Ivan, Cetaganda, Lois McMaster Bujold