Building a new page layout which does not reference core.js (but downloads it while the page is being viewed thereby optimizing response time)

[The optimization technique described below is an updated version of a previous blog entry. The primary change is that a new page layout rather than a detached page should be used.]

If you have an internet-facing website, you are probably exploring all avenues to make it more responsive to clients. For some users – particularly those with low-bandwidth or high-latency connections – the very first page load of a site can take a long time because of all the resources which need to be downloaded along with the page markup itself. Although these resources are often shared between pages and cached on the client, the first page load can be painfully slow.

In MOSS 2007, it is possible to defer the downloading of some of this content until the user is actually able to read the homepage. In this way, the page is rendered much more quickly, while the user’s experience of interacting with the site is not negatively impacted.

In this blog we will describe how you can create a specific page within your site (for example, it could be the root homepage) using a special layout, and remove core.js from the list of resources that must be downloaded before the page can render for an anonymous user. Because this core.js is about 54KB in its compressed form, this can enable the page to render as much as 10 seconds earlier for clients on a 56kb modem connection. Once the user is reading the page, core.js will download in the background.

By default, all pages in MOSS 2007 contain a reference to core.js. In many places, this file is critical, such as where the Site Actions Menu is shown. There are also some instances, however, when core.js is unnecessary. This workaround makes it possible to delay downloading core.js by certain users on a pages created with a specific layout. It does not matter whether the user is anonymous or authenticated; core.js will still be downloaded to the client. The distinction is that for authenticated users, it will be downloaded before the page is readable, while for anonymous users it will be downloaded after the page is readable.

First, let’s set some expectations. This is not an officially supported optimization, but it puts the ability to fine tune in the hands of the site administrators, while assuming the burden of ensuring that the site works as expected afterwards. This does not remove core.js from the entire site, just from pages created with a specific layout, and these pages will have the requirement that nothing on them requires core.js (otherwise they would obviously break). For example, the SharePoint Menu control, if required for anonymous users, would not function on such a page.

We welcome feedback on any elements of this process.

Before beginning, we need to do three things:

· Ensure that the Site Master Page is different from the System Master Page. This information can be found by browsing to the URL https://<yourservername>/_Layouts/ChangeSiteMasterPage.aspx . If you observe that the Site Master Page and the System Master Page are referencing the same file, this optimization will not work for you; it is required to change the Site Master Page to use a different file. Example pages that use the System Master Page are /_layouts/Settings.aspx, and in general any pages in the _layouts directory.

· Ensure that there are no controls in the Site Master Page which will be visible by anonymous users and which require core.js but do not register it. The optimization we use will suppress core.js only for anonymous users, and only on a single page, which means that authenticated users will still get core.js by default. If you modify the code referenced in step 5 to suppress core.js for more than just anonymous users, you will need to take this into account at the Site Master Page level, as well.

· Ensure that the Site Master Page does not have any ScriptLink controls which register core.js. The goal of this exercise is to create a single page which suppresses core.js, even though it would have been registered by the SPWebPartManager under typical circumstances.

Having satisfied these criteria, we can now be confident that the site is ready for our optimization.

1. Create the page layout which is not to reference core.js.   One way of doing this is to use SharePoint Designer, make a copy of an existing layout and paste the copy in the same directory. For this example, we’ll copy ArticleLeft.aspx and call the new layout ArticleLeftNoCore.aspx. Note that you’ll probably want to edit the description of this new file so that when you create a new page, you’ll be able to distinguish the two layouts.

2. In SharePoint Designer, Edit ArticleLeftNoCore.aspx.

3. Inside the tag with ID PlaceHolderAdditionalPageHead, Add the following markup: <SharePointWebControls:ScriptLink runat="server"/>  

This has the effect of telling the server that unless core.js is specifically registered by some control, it should not be referenced. Note that this example was created using BlueBand.master, but other master pages may have a different tag required to reference the ScriptLink control. For example, in default.master, the tag would be SharePoint:ScriptLink instead of SharePointWebControls:ScriptLink. The correct tag will be referencing the Microsoft.SharePoint.WebControls namespace, and looks something like this:

<%@ Register Tagprefix="SharePointWebControls" Namespace="Microsoft.SharePoint.WebControls" Assembly="Microsoft.SharePoint, Version=12.0.0.0, Culture=neutral, PublicKeyToken=71e9bce111e9429c" %>

4. Create a new binary based on the code in Appendix A, and set the version to “1.0.0.0”. In my example, this new binary is called PerfTools.dll.

The purpose of this code is to tell the server that if the user is not anonymous, we do want to specifically register core.js. We do this because by default many authenticated users will have access to the Site Actions menu, which requires core.js, while anonymous users do not. Your scenario may require that you suppress core.js under different circumstances, and if so, this is the code to change. Please note that the OnInit method will be executed any time the page is loaded, so you will want to ensure that you do not add any code which would adversely impact the throughput on the server. Accessing the current SPListItem, for example, will most likely incur a round trip to the SQL database.

5. Add PerfTools.dll to the GAC on the server.

6. In the web.config file for the server, add this line to the set of SafeControls (but with the correct PublicKeyToken):

      <SafeControl Assembly="PerfTools, Version=1.0.0.0, Culture=neutral, PublicKeyToken=3ec1cbf2475be08c" Namespace="WebControls" TypeName="*" Safe="True" />

7. In ArticleLeftNoCore.aspx, add a tag to register the new binary. It will look something like this (but with a different PublicKeyToken):

<%@ Register TagPrefix="PerfTools" Namespace="WebControls" Assembly="PerfTools, Version=1.0.0.0, Culture=neutral, PublicKeyToken=3ec1cbf2475be08c" %>

8. On the line following the markup from Step 4, add the following markup: <PerfTools:RegisterCoreWhenAuthenticatedControl runat="server"/>

9. Copy CorePreLoad.aspx (file content in Appendix B) to the _layouts directory on the server.

The markup in this file references a single function in core.js, and will download core.js before executing it.

10. In the master page used by the site, add a new placeholder in the markup after the </form> tag and before the </body> tag that looks like this:

<asp:ContentPlaceHolder id="PlaceHolderBottomIFrame" runat="server" />

This is necessary because the IFrame we are about to add can not be inside the Form tag, otherwise the page will break when a postback is executed.

11. In ArticleLeftNoCore.aspx, at the end of the file, add markup that looks like this:

<asp:Content ContentPlaceholderID="PlaceHolderBottomIFrame" runat="server">

                <iframe src="https://blogs.msdn.com/_layouts/CorePreLoad.aspx" style="display:none"/>

</asp:Content>

This will reference CorePreLoad.aspx in such a way that everything else on the page is displayed before it gets loaded and serves its purpose (which is to download core.js).

12. The page layout should now be currently checked out to whoever is editing it in SPD.   At this point it can be checked in, published, approved, or whatever you need to make it live.

13. Create a new page using ArticleLeftNoCore.aspx as the layout.   It is ready for use. When anonymous users browse to it, core.js will not need to be downloaded before they can begin interacting with or reading the page. (If you create two identical pages, one with ArticleLeft.aspx as the layout, and the other with ArticleLeftNoCore.aspx as the layout, you should see that one references core.js while the other does not.)

14. Ensure that no controls on the page are broken for anonymous users due to missing scripts.   Test the page well, and if there are any controls which fail to work for anonymous users because core.js is missing, they must be removed from the page itself or from ArticleLeftNoCore.aspx. The assumption of the optimization is that this special page has been created which does not require core.js because nothing on the page requires it.

15. Ensure that core.js does continue to appear on other pages in the site.   If core.js is now missing from other pages aside from the one we have just created, you can be sure problems will follow. Browse various other pages and examine the markup to see that core.js is referenced on the page.

You will know that our optimization is working properly because when you browse to the new page with an empty browser cache as an anonymous user and view the page source, you will not find a reference to core.js. When you look in your cache, however, you will find that core.js has been downloaded from your server. Other pages in your site will continue to reference core.js as usual.

Sterling Crockett, Software Design Engineer

SharePoint Web Content Management

Appendix A: RegisterCoreWhenAuthenticatedControl

using System;

using System.Collections.Generic;

using System.ComponentModel;

using System.Text;

using System.Web;

using System.Web.UI;

using System.Web.UI.WebControls;

using Microsoft.SharePoint;

namespace WebControls

{

    [DefaultProperty("Text")]

    [ToolboxData("<{0}:RegisterCoreWhenAuthenticatedControl runat=server></{0}:RegisterCoreWhenAuthenticatedControl>")]

    public class RegisterCoreWhenAuthenticatedControl : WebControl

    {

        protected override void OnInit(EventArgs e)

        {

            if (HttpContext.Current.Request.IsAuthenticated)

            {

                Microsoft.SharePoint.WebControls.ScriptLink.RegisterCore(this.Page, true);

            }

            base.OnInit(e);

        }

    }

}

Appendix B: CorePreLoad.aspx

<%@ Register Tagprefix="SharePoint" Namespace="Microsoft.SharePoint.WebControls" Assembly="Microsoft.SharePoint, Version=12.0.0.0, Culture=neutral, PublicKeyToken=71e9bce111e9429c" %>

<html>

<head>

<title>Pre-Load Core.js</title>

</head>

<body>

<SharePoint:ScriptLink name="core.js" runat="server" />

<script language="javascript">

 DisableRefreshOnFocus();

</script>

</body>

</html>