SharePoint for Public-facing Internet Site Performance Tips

My MVP Lead has been asking for this, but somehow in the battlefield of consulting work, I forgot. Then a SharePoint consultant based in Singapore messaged me via Twitter asking for the same thing, and so I forced my lazy Friday into writing this, in the hope that the next Google search with the above title will yield this post as #1   :)

So here goes my story about building NBAD.com , a public website of the biggest bank in United Arab Emirates, running on SharePoint 2010. Note that I cannot share specifics due to client’s Intellectual Property, however I will share the main ideas. And it wouldn’t be fun if I just gave you all the source code, cos this post is for devs who love to code, especially optimizing even 10KB of JavaScript   :)

0. Get your catfish – Ferrari.com

There was this movie “Catfish” that I watched in my Emirates flight, at the end of the movie the guy said something along the lines of:

Fishermen sent frozen codfish from A to B. Arrived mushy not fresh. Fishermen sent live codfish again. Arrived dead. Fishermen sent live codfish + 1 catfish again. Arrived fresh and alive, cos the catfish kept them alive (the codfish were busy fleeing)

So you can’t measure performance without a baseline. Can’t remember whether it was when I was at Microsoft or was it a Microsoft friend who told me that Ferrari.com runs on SharePoint. So anyway, I decided I want to see what Ferrari.com does and improve on it.

 

1. Remove unnecessary JavaScripts

Let me show you what I mean, here is Ferrari.com JavaScripts:

Why are we loading 40KB of SP.Ribbon when we know there will be no ribbon (as this is a public-facing internet/anonymous SharePoint site)?

And this is NBAD.com JavaScripts:

The only SharePoint 2010 JavaScript we need is init.js  and look at the Download Size difference (377 KB vs 135 KB).

So how do you do this? Well during development, I created a MasterPage code-behind (ugly according to Syed Akif Kamal my SharePoint architect), then during production I moved the code into a custom HTTP Module.

How do I filter which JS to load and which to pass through? I built a mini rule engine that has these rules:

static readonly IList<IRule> _rules = 
    new List<IRule>
    {
        new Rule_BlankJs(),
        new Rule_MyLinksRibbonLoad(),
        new Rule_SpNavigateHierarchy(),
        new Rule_SafeRunFunctions(),
        new Rule_LoadMdn(),
        new Rule_InitPageStateHandler(),
        //new Rule_SearchEnsureSOD(),
        //new Rule_SearchInputKeywords(),
    };

And to optimize even further, I add heuristic that says:

My JavaScript Filter rule will only be applied once. So if this Rule is already satisfied, move on to the next rule.

And another one that says:

If this <script> block has been evaluated and modified by a Rule, then there is no point to pass this <script> block to the other Rules.

private static string FilterJS(string scriptContent)
{
    string newContent = null;
    foreach (var rule in _rules)
    {
        if (rule.IsAlreadySatisfied) continue;

        bool isModified;
        newContent = rule.FilterContent(scriptContent, out isModified);
        if (isModified) break; // rule applied, no need to go to next rule
    }

    return newContent;
}

interface IRule
{
    bool IsAlreadySatisfied { get; set; }
    string FilterContent(string content, out bool isModified);
}

So that’s the first part – making sure SharePoint 2010 output only the necessary JavaScripts.

 

2. Download JavaScript On-Demand (when it’s really needed)

You can see from the above NBAD.com JavaScripts that we don’t have SharePoint’s out-of-the-box Search.js , eventhough we have a Search Box.

But hold on, open up FireBug again, and see what happens when you click inside the Search Box (ie. give the Search Box the focus):

Lo and behold, the 10KB Search.js is downloaded asynchronously in the background, and then loaded on-demand, even before you press Enter in the search box!

The clever trick here is that we assume you will type mininum 5 letters in the search box (“hello”) and that takes 5 seconds, while downloading a 10KB Search.js will only take 1 second or less  :)

Now why are we doing this trick, just to save 10KB? No, not really. My belief is that page will load really fast if it’s just content. When we start adding JavaScript and DOM manipulation, then the browser has to do extra JavaScript evaluation and execution. That will add delay before the page can be displayed. So by doing on-demand JavaScript loading, we are saying:

The Landing Page does not need to execute Search.js because we don’t know whether users will always do a search after page is loaded. So let’s just load Search.js when we know users click the Search box.

Similar principle can be applied to the Video Player:

The video does not need to autoplay when the page is loaded. Rather let’s display an image thumbnail and load the VideoPlayer.js when we know the user clicks the triangle Play icon.

How the mechanics? Basically you register an OnClick handler, then in that OnClick you download the JavaScript asynchronously and append to the <head>. After the script is loaded, you call a CallBack function that’s passed to the On-Demand Loader. This means you can chain calls like this: User Click Video Thumbnail –> VideoPlayer.js downloaded –> After VideoPlayer.js is loaded, call PlayVide() function

 

3. Inject Minify CSS & JS into the Build Process

I have a Target in the .csproj that gets triggered when SharePoint is preparing the PKG folder (before zipping it up as .WSP file). I created a console app that basically accepts an input folder, and minifies all CSS & JS in that folder with overwrite mode (replaced the file with new minified ones)

  <!-- MINIFY CSS and JS -->
  <UsingTask TaskName="AjaxMin" AssemblyFile="$(MSBuildProjectDirectory)\..\..\AjaxMinTask.dll" />
  <Target Name="AfterLayout">
    <Message Text="Minifying CSS and JS...">
    </Message>
    <ItemGroup>
      <JS Include="$(LayoutPath)\**\*.js" Exclude="$(LayoutPath)\**\*.min.js;Scripts\*.js" />
    </ItemGroup>
    <ItemGroup>
      <CSS Include="$(LayoutPath)\**\*.css" Exclude="$(LayoutPath)\**\*.min.css" />
    </ItemGroup>
    <AjaxMin Clobber="True" JsSourceFiles="@(JS)" JsSourceExtensionPattern="\.js$" JsTargetExtension=".js" CssSourceFiles="@(CSS)" CssSourceExtensionPattern="\.css$" CssTargetExtension=".css" />
  </Target>

 

4. One BIG CSS

Since the CSS file will be referenced in all pages, it makes sense to put all styles in one big CSS file, then minify it (see point 3 above) and then let the user cache it for future use (hey, this is public internet site remember? changes will be made usually every 6 months or so)

 

5. Inspect your Page_Load in WebParts

We widgetize everything (the carousel, video player, news viewer, etc) by building them as WebParts. Now here’s the catch, I noticed our devs are so entrenched in building their web parts, that when the time comes to integrate all these web parts into a working page, performance suffers.

You know the saying, The Sum is Greater Than the Parts?  :)

So then we have to code-review all the webparts, making sure that all those expensive PageLoad() will be delegated as JavaScript calling WebMethods or implement the ICallBack interface.

Here’s the philosophy:

I would rather have 10 webparts showing “Loading data” indicator and a fast loading page, rather than have this page wait for 10 webparts to finish their PageLoad() funky codes.

I think that’s about it. I’ll make sure to add another post if I miss something. I hope this is useful to you devs out there who are trying to make their SharePoint public-facing internet site faster than Ferrari.com  :)