PageSpeed, Bundling, Minification, and Caching

After deploying our web application, it’s not the end of the story. There are still many tasks to do. One of them is to analyze and optimize our new website. Normally, I start with testing my website in Google PageSpeed Insights to understand about the website performance issues.

Oops, my new website only gets 57/100.
Oops, my new website only gets 57/100.

Unfortunately, as shown in the screenshot above, my new website seems having a poor performance. Before finding out the ways to improve the performance, I would like to share what the 3 passed rules are.

The Things Done Right

Improve Server Response Time

Yup, the first thing that affects our web page loading is of course the server response speed. Google recommended to keep the server response time under 200ms.

My homepage loads about 600ms but PageSpeed Insights still let me pass. =)

Avoid Landing Page Redirects

Redirects will increase the loading time of the web page because there will be more HTTP request-response cycle.

In the old days when responsive design was still not popular, redirect is the way we used to bring users from desktop view to the mobile view of our website. So, when users first came in from “www.example.com”, they would be brought to “www.example.com/mobile/index.aspx”.

However, sometimes we still need to do redirects in our website, especially when we have a new web page to replace the old one. We may do redirects also when we are changing the domain name of our website.

There are two major types of HTTP Redirection.

  • 301 (Permanent Redirect): The requested resource has been assigned a new permanent URI and any future references to the resource should be done using the new URI;
  • 302 (Temporary Redirect): The requested resource resides temporarily under different URI.

Hence, 301 Redirect is able to pass the PageRank of the old page to the new page. However, for 302 Redirect, due to the fact that it’s a temporary redirect, the old page will still retain its PageRank and the new page will not accumulate any. So for mobile view redirect, we should use 302 Redirect, as recommended by Google also.

So, how do we do 301 Redirect and 302 Redirect in ASP .NET?

By default, Response.Redirect uses 302 Redirect. This is because the common use of Response.Redirect is just to move users to another page after a postback. Of course, we can change it to use 301 Redirect as follows.

Response.Redirect(newUrl, false);
Response.StatusCode = 301;
Response.End();

In ASP .NET 4.0, we have an alternative way of doing 301 Redirect which is using HttpResponse.RedirectPermanent method.

Besides, we can also do 301 Redirect in URL Rewrite in web.config.

<rewrite>
    <rules>
        <rule name="Redirect to www" stopProcessing="true">
            <match url=".*" />
            <conditions>
                <add input="{HTTP_HOST}" pattern="^domain.com$" />
            </conditions>
            <action type="Redirect" url="http://www.domain.com/{R:0}" redirectType="Permanent" />
        </rule>
    </rules>
</rewrite>

Prioritize Visible Content

One of the key principles of prioritizing visible content is to make sure the above-the-fold content is able to be loaded first.

What does “Above-the-fold Content” (ATF) mean? I found some very interesting explanation about it. ATF Content typically means the content above where the newspaper is folded horizontally. In web design context, ATF Content refers to any content we immediately see without scrolling when the page is first loaded.

Hence, in order to make sure ATF Content to be loaded first, we must have an inline part of the CSS which is responsible for styling the ATF Content.

Thanks, ASP .NET 4.5!

In ASP .NET 4.5, there are two new techniques, Bundling and Minification, introduced to help improving the web page loading time. Hence, a web project done in ASP .NET 4.5 with good back-end design should have all the 3 rules above passed in Google PageSpeed Insights.

The Things Can Be Considered Fixing

Minify CSS and JS

Here I will just use CSS as reference. Minification of JS is actually similar to minifying CSS.

Normally we will have more than one CSS references. If we would like to reference all of them, then we will make one HTTP request for each of them.

<link href='/Content/style1.css' rel='stylesheet' />
<link href='/Content/style2.css' rel='stylesheet' />
...
<link href='/Content/stylen.css' rel='stylesheet' />

In ASP .NET 4.5, we can use Bundling+Minification feature to make the loading time of CSS files shorter.

Bundling is able to improve the load time by reducing the number of requests to the server. Minification will reduce the size of requested CSS files. For example, if we want to reference all the CSS files in the “Content” folder, we will do it as follows. Doing so will tell ASP .NET to scan the “Content” folder, bundle and minify the CSS files within it. A single HTTP response with all of the CSS content will then be sent back to the browser.

<link href='/Content/css' rel='stylesheet' />

By default, CSS files are sorted alphabetically. If there are reset.css and normalize.css, the two files will go before any other files.

Alternatively, we can also do it in C# to tell ASP .NET which CSS files need to be in bundle as well as their ordering.

bundles.Add(new StyleBundle("~/Content/css").Include(
    "~/Content/bootstrap.css",
    "~/Content/animate.css",
    "~/Content/font-awesome.css",
    "~/Content/site.css"));

By default, Bundling will select “.min” file for release when both “style1.css” and “style1.min.css” exist. In debug mode, then it will select non-“.min” version. This is important because Bundling seems to have problem with bootstrap.css.

Bundling doesn’t work well with @import, @keyframes, @-webkit-keyframes that are in the CSS files. Hence, sometimes Bundling+Minification will return some error messages as follows when you view the source of /Content/css on browser.

/* Minification failed. Returning unminified contents.
    (3272,1): run-time error CSS1019: Unexpected token, found '@import'
    (3272,9): run-time error CSS1019: Unexpected token, found 'url("https://fonts.googleapis.com/css?family=Roboto:300,400,500,700")'
    (3278,83211): run-time error CSS1019: Unexpected token, found '@-webkit-keyframes'
    (3278,83255): run-time error CSS1035: Expected colon, found '{'
    (3278,83406): run-time error CSS1019: Unexpected token, found '@keyframes'
    ...
    (3672,3): run-time error CSS1062: Expected semicolon or closing curly-brace, found '0%'
    (4246,11): run-time error CSS1036: Expected expression, found ';'
*/

So one of the solutions suggested on Stack Overflow is to include both bootstrap.css and bootstrap.min.css in the same “Content” folder so that Bundling+Minification will not try (and then fail) to minify bootstrap.css itself. Instead, it will just pickup the existing bootstrap.min.css. However, we don’t need to change anything to the C# code above, so it still looks the same.

bundles.Add(new StyleBundle("~/Content/css").Include(
    "~/Content/bootstrap.css",
    "~/Content/animate.css",
    "~/Content/font-awesome.css",
    "~/Content/site.css"));

One disadvantage of doing so is that if there is any changes done to the bootstrap.css, we have to manually minify it and update the bootstrap.min.css because Minification will not do minification for us but it will just pickup the bootstrap.min.css.

For JavaScript, we will do the same to add multiple JS files into the bundle.

bundles.Add(new ScriptBundle("~/bundles/jquery").Include(
    "~/Scripts/jquery-{version}.js",
    "~/Scripts/jquery-ui.js",
    "~/Scripts/jquery.datetimepicker.js"));

The {version} wildcard above will automatically pick the appropriate version of jQuery available in the “Scripts” folder. Hence, we can happily use NuGet to update jQuery to a newer version without changing our C# code.

In the ASP .NET MVC template, there are actually two JavaScript bundles, “~/bundles/jquery” and “~/bundles/jqueryval”. The reason of doing Bundle Partitioning is to make sure the page doesn’t get the resources that it doesn’t need.

Minify HTML

To minify HTML and Razor views, there are a few options.

One is to use Meleze.Web NuGet Package. It will process the HTML and Razor views on the fly.

Another one is using ASP .NET HTML Minifier which can be included as part of the build process so that the files are minified before being deployed in Visual Studio. The simple command tool is open source and the project can be found on Github.

Enable Compression

It’s great that nowadays all the modern browsers support and automatically negotiate Gzip compression for all HTTP requests. Hence, enabling Gzip compression is able to reduce the size of transferred response.

IIS supports Static Compression and Dynamic Compression. Static Compression is used to compress static resources such as CSS, JS, images. It will compress each file once and then save the compressed file to disk. In IIS 8, Static Compression is enabled by default.

A way to check if the response is Gzipped.
A way to check if the response is Gzipped.

Enabling Dynamic Compression will help to compress the dynamic content and thus always gives us more efficient use of bandwidth (with higher CPU usage). I will not talk about Dynamic Compression here because Google PageSpeed Insights currently emphasizes more on the static file compression. So by using IIS 8, this shouldn’t be an issue.

The reason why I have this compression issue is because of the external files used are not Gzipped. However, I will leave it there first.

The Things That Should Be Fixed

Leverage Browser Caching

Fetching resources over the network is slow. Hence, it is always a good idea to cache our static resources by setting an expiry date in the HTTP headers for static resources.

Bundling in ASP .NET will help to set the HTTP Expire Header of CSS and JS files one year from when the bundle is created.

Bundles set the HTTP Expires Header one year from when the bundle is created.
Bundles set the HTTP Expires Header one year from when the bundle is created.

Alternatively, we can do so in web.config in IIS 7 and above, as shown in the sample below.

<configuration>
    <system.webServer>
        <staticContent>
            <clientCache httpExpires="Fri, 1 Jan 2016 00:00:00 GMT" cacheControlMode="UseExpires" />
        </staticContent>
    </system.webServer>
</configuration>

With this setting, all static content, such as CSS, JS, and images, will now have an expires HTTP header set to next year (2016).

Optimize Image and Eliminate Render-Blocking CSS/JS

I won’t discuss both of them here because to me they are not really good suggestions, especially the CSS Delivery Optimization recommendation from Google.

The document says that “If the external CSS resources are small, you can insert those directly into the HTML document, which is called inlining. Inlining small CSS in this way allows the browser to proceed with rendering the page.” This will actually split the CSS code into multiple files and eventually make the whole project harder to maintain.

For the images, I think there is no need to compress them. Doing image browser caching should be enough to make the web page loads fast already.

So, I will just skip these two parts.

Summer 2015 Self-Learning Project

This article is part of my Self-Learning in this summer. To read the other topics in this project, please click here to visit the project overview page.

Summer Self-Learning Banner

Summer 2015 Self-Learning

Summer Self-Learning
It has been about half a year since I started to learn ASP .NET MVC and Entity Framework (EF). In this period of time, I have learnt about not just MVC and EF, but also Azure PaaS, Google Maps API, web application security, cool jQuery plugins, Visual Studio Online, etc.

In the beginning of May, I started to note down useful things I’d learned in my learning journey. Months of bringing together information in this summer has helped me compile my notes about what I’ve learned in the past 6 months. I have currently completed compiling notes for 17 topics that I’ve learnt in this summer.

I listed down the title of the 17 posts below to give you a quick overview about all the 17 topics.

Contents

ASP .NET MVC and Entity Framework

Security

Microsoft Azure

Google APIs

Web Development Tools

Learning After Work

I’m working in Changi Airport. The office working hour is from 8:30am to 6pm. In addition, I am staying quite far from the airport which will take about one hour for me to travel from home to office. Hence, the only time that I can have sufficient time to work on personal projects is weekends.

This summer self-learning project is originally planned to be done by the end of May. Normally, it takes me about one day to finish writing a post. After that, if I find any new materials about the topics, I will then modify the post again. Sometimes, however, I am just too tired and I would not write anything even though it’s weekend. Hence, I end up finishing all the 17 topics three months later.

This summer learning project covers not only what I’ve learnt in my personal projects, but also new skills that I learn in my workplace. I always enjoy having a chat with my colleagues about the new .NET technology, app development, Azure hosting, and other interesting development tools. So yup, these 17 articles combine all the new knowledge I acquire.

I’m also very happy that that I am able to meet developers from both .NET Developers Community Singapore and Azure Community Singapore and share with them what I’ve learnt. That gives me a great opportunity to learn from those experienced .NET developers. =)

Azure Community March Meetup in Microsoft Singapore office.
Azure Community March Meetup in Microsoft Singapore office.

I am not that hardworking to work on personal projects every day. Sometimes, I will visit family and friends. Sometimes, I will travel with friends to overseas. Sometimes, I will play computer games or simply just sleep at home. So ya, this self-learning project takes a longer time to complete. =D

Working on personal projects after work is stressful also. Yup, so here is a music that helps reducing my stress. =)