The previous post was all about improving server-side code. The time your server needs to deliver the HTML is important as it delays loading all other resources. It is, however, usually not the crucial part of the page load time. Because 80-90% of the end-user response time is spent on the frontend, there is much more potential for improvements.
One of the key measures for improving page load times is reducing the number of requests because every request comes with an overhead, impacting the total load time.
Combining Requests
One way to reduce the number of requests is to combine multiple resources into one request. There are three types of files which are prime candidates for combining. (Small) images, CSS files and JavaScript files. Reducing the number of referenced js files is especially important as many browser do not, or only limited support parallel downloading of js files.
ASP.NET already contains a component for bundling JavaScript and CSS files (System.Web.Optimization
) which is by default included in the web project template. However, this was only "recently" introduced with ASP.NET MVC 4 and there are many more components available. The following table contains some of the most popular ones.
Tool |
Mode |
Configuration |
Sprites |
Minification |
Debug Support |
Web Essentials |
build-time |
xml |
no |
AjaxMin |
manually |
Chirpy |
build-time |
xml |
no |
multiple |
manually |
combres |
run-time |
xml |
no |
multiple |
yes |
cassette |
run-time |
code |
no |
AjaxMin |
yes |
RequestReduce |
run-time |
auto |
yes |
AjaxMin |
manually |
System.Web.Optimization |
run-time |
code |
no |
own |
yes |
If you have the time, take a look at several different solutions as many of them provide additional features like support for coffee-/typescript, SASS/LESS or HTML templates. All of them support bundling and minification but there are nevertheless notable differences even when just considering these features. When deciding which one you want to use, you should at first decide whether you want run-time or build-time support.
When using "run-time mode", files are combined at run-time, e.g. using a HttpHandler
. In "build-time mode", the files are combined during the build or when one of the source files changes. Combining once during development is more efficient as the combined files can then be delivered as static files directly by the IIS or can be uploaded to a CDN. For high volume websites, this is probably the way to go. Currently, the visual plug in Web Essentials is my favorite tool in this area.
If you don't require this strong focus on performance and scalability, I would suggest starting with one of the components working at run-time. They are not as efficient but are usually more flexible and have better debugging support (= no bundling/minification during development). Of course, the default choice will be the bundling & minification component provided by ASP.NET, but if you require additional features, take a look at combres or cassette. Combres supports not only the Microsoft Ajax Minifier but also other minification algorithms like YUI compressor or google closure compiler. The downside of having all these different options are the dependencies on the minification libraries. Updates often cause version conflicts or incompatibilities, so you should well consider if you really need additional minification algorithms.
If you want to optimize an existing site, RequestReduce might be the right choice for you. It was especially developed for integration with existing sites, requiring minimal effort. It is also interesting if you need dynamic generation of sprite images.
Example
The ASP.NET MVC 4 project template contains a configuration for the System.Web.Optimzation
run-time bundeling and minification feature (in "App_Start\BundleConfig.cs"). To combine jquery and all jquery plugins, you just need to define a script bundle:
public static void RegisterBundles(BundleCollection bundles)
{
bundles.Add(
new ScriptBundle("~/bundles/jquery").Include(
"~/Scripts/jquery-1.8.2.js",
"~/Scripts/jquery-ui-1.9.2.js",
"~/Scripts/jquery.unobtrusive-ajax.js",
"~/Scripts/jquery.validate.js",
"~/Scripts/jquery.validate.unobtrusive.js"));
}
and reference it in your layout:
@Scripts.Render("~/bundles/jquery")
To achieve the same using the Web Essentials, select the script files in the solution explorer and click "Web Essentials" -> "Create JavaScript bundle file". After entering the name of the bundle, a configuration file will be created containing a list of files which be should be combined, and whether a minified version should generated.
<bundle minify="true" runOnBuild="true">
-->
<file>/Scripts/jquery-1.8.2.js</file>
<file>/Scripts/jquery-ui-1.9.2.js</file>
<file>/Scripts/jquery.unobtrusive-ajax.js</file>
<file>/Scripts/jquery.validate.js</file>
<file>/Scripts/jquery.validate.unobtrusive.js</file>
</bundle>
The corresponding files "{bundleName}.js" and "{bundleName}.min.js" are not only generated on build, but also when one of the source files changes. The bundles are created as simple js files which can be delivered as static content and referenced like any other js file.
Lazy Loading
Another way to reduce the number of HTTP requests (at page load) is to delay loading resources which are not immediately required. This usually applies best to images. Imagine a photo gallery containing hundreds of photos or an online shop with lots of widgets containing mainly not (yet) visible images. Lazy loading of images is usually done by setting the src
attribute of the image to a placeholder image and storing the real image URL in a data attribute. Only when the image is in the viewport, the src attribute is updated with the real image URL. You can check out the difference on the example pages without lazy loading and with lazy loading. If you don't want to write the lazy loading JavaScript yourself, you can use a library like the Lazy Load plug in for jQuery, which is also used in the demo project.
If you want to try it yourself , you can use this small snippet. If you don't have an insanely high resolution, the second image should not be loaded until you scroll down. For two images, you obviously don't gain anything but it works the same for a page with a dozen or hundreds of images. When using the lazy load plug in for a production website, check out the options, which allow you to tweak its behavior (e.g. define custom triggers, adjust sensitivity or add effects).
<img data-original="http://baconmockup.com/640/480"
src="http://placehold.it/640x480" width="640" height="480" />
<div style="height:2000px">I'm just a big placeholder</div>
<img data-original="http://placesheen.com/640/480"
src="http://placehold.it/640x480" width="640" height="480" />
<script src="//ajax.googleapis.com/ajax/libs/jquery/1.9.1/jquery.min.js"></script>
<script src="//cdn.jsdelivr.net/jquery.lazyload/1.8.4/jquery.lazyload.js"></script>
<script>
$(function () {
$("img").lazyload();
})
</script>
These are two of the low hanging fruits and should enable you to reduce the number of HTTP requests without investing a lot of work.
Next time, we will take a closer look at cache control headers.