Pleasing Google Page Speed
Minifying resources such as Javascript and CSS is becoming common practice nowadays and something which just should be done with no questions asked.
The reason for minification is both to reduce the payload and to reduce the number of requests made by the browser to the server. This makes the page load faster thus more responsive.
Google rewards pages with a quick load time in their search results as well which makes it one of the key points which are checked by their analysis tool called Google Page Speed
About two months ago we’ve just launched a new large Sitecore site for a customer at Pentia where I’ve been the architect and lead developer on the solution. Just after go-live I went in and looked at the page speed results for the page.
I was not pleased with the result at first. The page only scored 62 / 100. On the list of suggestions for improving the results there was a bunch of quick fixes which I went through and the new result ended up on 90 / 100 which is quite fair, but still I wanted more.
Then I noticed one specific suggestion which I actually never really have given a thought before:
https://developers.google.com/speed/docs/best-practices/payload#MinifyHTML
Minifying the html itself? Hm, why not give it a try
Minifying markup essentially means to removes all xml comments, all unnecessary whitespaces and line-breaks. Right-clicking on Google.com and viewing the source clearly shows how it looks.
Minify html in Sitecore
To minify the markup I implemented a service class which uses regular expressions to remove all unwanted whitespaces, comments and linebreaks.
public static string Minify(string markup,
bool removeWhitespaces = true, bool removeLineBreaks = true, bool removeHtmlComments = true)
{
if (removeWhitespaces)
markup = RemoveWhiteSpaces(markup);
if (removeLineBreaks)
markup = RemoveLineBreaks(markup);
if (removeHtmlComments)
markup = RemoveHtmlComments(markup);
return markup;
}
public static string RemoveHtmlComments(string markup)
{
return Regex.Replace(markup, "<!--*.*?-->", string.Empty, RegexOptions.Compiled | RegexOptions.Multiline);
}
public static string RemoveLineBreaks(string markup)
{
return Regex.Replace(markup, "\\n", string.Empty, RegexOptions.Compiled | RegexOptions.Multiline);
}
public static string RemoveWhiteSpaces(string markup)
{
return Regex.Replace(markup, "^\\s*", string.Empty, RegexOptions.Compiled | RegexOptions.Multiline);
}
}
Okay now all the rendered markup just needs to go through the Minify method.
To do this I’ve implemented a HttpRequestProcessor which first checks if the current request should be minified and if so adds a custom Stream as Filter stream on the response.
public override void Process(HttpRequestArgs args)
{
if (!ShouldMinify())
return;
args.Context.Response.Filter = new MinifiedStream(args.Context.Response.Filter);
}
protected bool ShouldMinify()
{
if (!Sitecore.Configuration.Settings.GetBoolSetting("MarkupMinify.MinifyResponseMarkup", true))
return false;
if (args.Context.Request.Url.OriginalString.Contains("/sitecore")
|| args.Context.Request.Url.OriginalString.Contains("/speak"))
return false;
if (args.Context.Request.AcceptTypes == null || !args.Context.Request.AcceptTypes.Any(a => a.Equals("text/html", StringComparison.InvariantCultureIgnoreCase)))
return false;
return true;
}
The first check reads a Sitecore setting in web.config checking if minification is enabled at all.
<setting name="MarkupMinify.MinifyResponseMarkup" value="true" />
If it is then we’ll need to check if it makes sense to minify the current request. This is extremely important, we do not wish to minify the content editor or any Sitecore modules. For your solution it might make sense to adjust these rules.
The HttpRequestProcessor is then added in web.config to the httpRequestBegin pipeline as the last processor
<processor type="[NAMESPACE].MinifyMarkupProcessor, [ASSEMBLY]" />
The custom Stream called MinifiedStream wraps the Response.Stream.Filter in the property _sink.
public class MinifiedStream : Stream
{
private readonly StringBuilder _responseHtml;
private readonly Stream _sink;
public MinifiedStream(Stream sink)
{
_sink = sink;
_responseHtml = new StringBuilder();
}
public override bool CanRead { get { return true; } }
public override bool CanSeek { get { return true; } }
public override bool CanWrite { get { return true; } }
public override long Length { get { return 0; } }
public override long Position { get; set; }
public override void Flush()
{
_sink.Flush();
}
public override int Read(byte[] buffer, int offset, int count)
{
return _sink.Read(buffer, offset, count);
}
public override long Seek(long offset, SeekOrigin origin)
{
return _sink.Seek(offset, origin);
}
public override void SetLength(long value)
{
_sink.SetLength(value);
}
public override void Close()
{
_sink.Close();
}
}
Each time something is written to the stream it gets appended to a StringBuilder. This is done until the end tag