Just in case if somebody doesn’t know what YSlow is? YSlow is Firefox add-on integrated with Firebug. It analyzes web pages and tells you why they are slow. You can run it on you project and see your grade. You can do some easy improvements and maybe even get D grade. However to get higher then D grade becomes a real challenge. Below I will describe how I got A grade (100 points) on my web blog application.
First of all you don’t want measure performance with any external stuff which is beyond of your control such as Google Ads, Google Analytics, Dotnetkicks. So, you need to make a query string switch, which is going to disable/hide all this stuff. In my case it is PageType=NoExternal, so full URL would be http://www.karpach.com/default.aspx?PageType=NoExternal. Also you need to add CDN setting for YSlow. My personal CDN is http://karpach.appspot.com/cdn/. Now if you measure performance of my default.aspx?PageType=NoExternal, you would see that it is A (100). Now let’s see how achieved this.

1. Make fewer HTTP requests.
First of all you need to combine stylesheets and javascripts files in one file. You should do this only for your production build using MS Build tasks.
<ItemGroup>
<TextFiles Include="*.css" Exclude="global.css"/>
</ItemGroup>
<Exec Command="echo y| type %(TextFiles.Identity) >> global.css"/>
This way you can combine your custom css and javascript files.
Some developers would ask: What about WebResource.axd? New AJAX Control Toolkit has ToolkitScriptManager, which can combine most of your WebResource.axd files. It also gzip them (this is important for later sections).
Next, you need to create CSS Sprites. You can read CSS Sprites: Image Slicing’s Kiss of Death article to get better idea what is CSS Sprites. Article doesn’t discuss how to create sprites for repeated backgrounds. The idea is to group such backgrounds. For example, all vertical repeating backgrounds in vbackground.gif and all horizontal repeating backgrounds in hbackground.gif. Then you can use background-position: -OffsetPixels, 0px for vertical repeated backgrounds and background-position: 0px, -OffsetPixels for horizontal repeated backgrounds. Here is the example of CSS Sprite from my blog application.

2. Use CDN (Content Delivery Networks)
Content Delivery Networks are expensive. I googled for something cheap and found following article 10 Easy Steps to use Google App Engine as your own CDN. Of course you need to do a few adjustments in order to make it a perfect CDN.
First of all default cache expiration is 600 sec. However, YSlow wants it to be at least 7 days. So, in your app.yaml after api_version add following line:
default_expiration: "7d"
Again you can use msbuild to modify stylesheet file to use CDN. Here is how I did this:
<Import Project=".\References\MSBuild.Community.Tasks.targets" />
<Target Name="Release">
<FileUpdate Files="$(OutputPath)styles\basic.css" Regex="\.\.\/images/([^\)]*)" ReplacementText="http://karpach.appspot.com/cdn/images/$1" />
</Target>
You need to put on CDN all your images, styles and JavaScript files. Here is a cache problem comes up. What if some visitor accessed your application just before you released new version, which has modified stylesheet and some modified images. This somebody is going to have cached version of stylesheet and images for 7 days, so he is not going to see changes until his browser cache expires. This is not good. If question like this comes up then it is time to see what yahoo did on their web site. For images you can see that they have date stamp and version in image name. For example: trough_2.0_062308.gif. It makes sense. If you modify one of your css sprites just rename it manually with new date stamp and version.
However, you probably don’t want to use the same approach for your stylesheet file. First of all you want to track all versions of your stylesheet in version control system (in my case subversion). For images it doesn’t really matter, since you don’t change them too often. My idea is to have just one physical css stylesheet in my case basic.css. However on a page I would use it like this: basic_14102262.css, where 14102262 is current version of application. Then you can use url rewrite techniques, so basic_14102262.css redirect to basic.css. A user who has cached version of basic_14102261.css would be forced to load new version basic_14102262.css, since file name changed.
Google Aps engine makes url rewrite easy. Modify your app.yaml to have the following:
- url: /cdn/styles/basic_\d*\.css
static_files: cdn/styles/basic.css
upload: cdn/styles/basic\.css
3. Add an Expires header
Everything under CDN would already have it. For all other files I created HttpModule:
private readonly static string[] CACHED_FILE_TYPES = new string[] { ".jpg", ".gif", ".png",".css" };
public void Init(HttpApplication context)
{
context.AcquireRequestState += new EventHandler(context_AcquireRequestState);
}
void context_AcquireRequestState(object sender, EventArgs e)
{
HttpContext context = HttpContext.Current;
if (context != null && context.Response != null)
{
string fileExtension = Path.GetExtension(context.Request.PhysicalPath).ToLower();
if (context.Response.Cache != null && Array.BinarySearch<string>(CACHED_FILE_TYPES, fileExtension) >= 0)
{
HttpCachePolicy cache = context.Response.Cache;
TimeSpan duration = TimeSpan.FromDays(365);
cache.SetCacheability(HttpCacheability.Public);
cache.SetExpires(DateTime.Now.Add(duration));
cache.SetValidUntilExpires(true);
cache.SetNoServerCaching();
cache.SetMaxAge(duration);
}
}
}
Ideally you won’t need this module, since all qualified files should be under CDN.
4. Gzip components
You can read Building a GZip JavaScript Resource Compression Module for ASP.NET article how to create gzip http module. Just keep in mind that IE6 doesn’t have 100% gzip support.
5. Put CSS at the top.
This is an easy one. It’s just common sense. Keep this in mind when you are developing server controls. Use Header.Controls collection to add your stylesheet include.
6. Put JS at the bottom.
Sometimes it’s really difficult to follow this rule. For example, I always want jquery script include at the top, so I don’t need to worry if library script is loaded or not. Anyway for your custom scripts use inline script to check if library is loaded and then use it.
For example:
Library.js
function DoSomething()
{
}
And then inline script:
<script>
if (typeof (DoSomething) == 'undefined')
{
alert('Library is not loaded yet');
}
</script>
7. Avoid CSS expressions.
CSS expressions work only in IE, so if you build cross browser application you should not use them anyway. The only good use for them is min-width/min-height hack for IE6.
8. Make JS and CSS external.
This is also common sense. Just read what Yahoo suggests http://developer.yahoo.com/performance/rules.html#external
9. Reduce DNS lookups.
Try to copy external resources (images, javascripts) to your websites.
For example, my blog has a link to w3 validation service. Originally icon for it was located on w3 schools web site, so I copied it to my project. Now this icon is local, so I have 1 less dns lookup.
10. Minify JS.
Do minification during production build using MS Build. I using YUI compressor for this purpose. Here is an example of MS Build script.
<Target Name="Compress">
<Message Text="Create temp files ..." />
<Copy SourceFiles=".\$(ProjectName)\Javascript\ColorPicker.js" DestinationFiles=".\$(ProjectName)\Javascript\ColorPicker.js.full"/>
<Copy SourceFiles=".\$(ProjectName)\Styles\ColorPicker.css" DestinationFiles=".\$(ProjectName)\Styles\ColorPicker.css.full"/>
<Exec Command="java -jar yuicompressor-2.4.2.jar --type js .\$(ProjectName)\Javascript\ColorPicker.js.full >.\$(ProjectName)\Javascript\ColorPicker.js"/>
<Exec Command="java -jar yuicompressor-2.4.2.jar --type css .\$(ProjectName)\Styles\ColorPicker.css.full >.\$(ProjectName)\Styles\ColorPicker.css"/>
</Target>
11. Avoid Redirects
You should not have a problem with this. Just read what yahoo recommends.
12. Remove Duplicate Scripts
Be careful when you are developing custom server and user controls. Make sure that for multiple controls of the same kind include script just one time.
13. Configure ETags
Cache module provided above and CDN should take care of this issue. Just keep in mind that ASP.NET has special method for ETags:
Response.Cache.SetETag
P.S. The article was mentioned on Channel 9 this week: Martin Woodward, MVP Summit, Web Perf, Show Off, and a VSTS Pep Talk
