[Update 02/21/2007: VERY IMPORTANT -- the optimization technique described below may introduce an undesirable caching side effect. An updated optimization technique using a new page layout has been posted here.]
If you’ve implemented or are planning to implement a Internet facing website, you are probably exploring all avenues to make it more responsive to users and visitors. 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 *after* the user sees the page. 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 entry, I will describe how you can modify a specific page within your site (in this example, the root homepage) and remove core.js from the list of resources that must be downloaded before the page can render for anonymous users. Because the core.js file is about 54 KB in its compressed form, doing this can enable the page to render as much as 10 seconds faster for users on a 56 Kbps 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. My performance tuning tip makes it possible to delay downloading core.js by certain users on a specific page. It does not matter whether the user is anonymous or authenticated; core.js will still be downloaded to the browser. 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.
Before I continue, let me set some expectations. This is *not* an officially supported optimization, but it puts the ability to fine tune in the hands of the site administrator, who must assume the burden of ensuring that the site works as expected afterwards. This does not remove core.js from the entire site -- just from a single page, and this page must not have anything that requires core.js (otherwise, it will obviously break). For example, the SharePoint Menu control, if required for anonymous users, would not function on this page.
For starters, you 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 http://<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; this optimization requires that the Site Master Page 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. This optimization 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 below 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 the above criteria, you can now be confident that the site is ready for this optimization.
1. Create the page, which is not to reference core.js. We’ll call it NoCore.aspx, but in your case, default.aspx might be more appropriate since that’s the default Home Page for your site.
2. In SharePoint Designer, navigate to NoCore.aspx in the tree hierarchy, right click, and select “Detach from Page Layout.” This will have the effect of converting NoCore.aspx to an “untemplatized page.” What this means is that while it is still subject to the site wide Master Page, its markup is no longer governed by the layout from which it was created, and you will be able to update the markup as you see fit.
3. In SharePoint Designer, Edit NoCore.aspx.
4. 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=188.8.131.52, Culture=neutral, PublicKeyToken=71e9bce111e9429c" %>
5. Create a new binary based on the code in Appendix A, and set the version to “184.108.40.206”. 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 want the page 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.
6. Add PerfTools.dll to the GAC on the server.
7. 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=220.127.116.11, Culture=neutral, PublicKeyToken=3ec1cbf2475be08c" Namespace="WebControls" TypeName="*" Safe="True" />
8. In NoCore.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=18.104.22.168, Culture=neutral, PublicKeyToken=3ec1cbf2475be08c" %>
9. On the line following the markup from Step 4, add the following markup:
10. 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.
11. 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.
12. In NoCore.aspx, at the end of the file, add markup that looks like this:
<asp:Content ContentPlaceholderID="PlaceHolderBottomIFrame" runat="server">
<iframe src="http://blogs.msdn.com/_layouts/CorePreLoad.aspx" mce_src="http://blogs.msdn.com/_layouts/CorePreLoad.aspx" style="display:none"/>
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).
13. The file should now be currently checked out to whoever is editing it in SPD. At this point it can be edited, checked in, published, approved, or whatever you need. 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 the page.
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 NoCore.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 the optimization is working properly because when you browse to it 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.
I hope that you found this optimization tip useful. Leave a comment here if you have any questions or suggestions for other ways of improving page load performance.
Sterling Crockett, Software Design Engineer
SharePoint Web Content Management
Appendix A: RegisterCoreWhenAuthenticatedControl
public class RegisterCoreWhenAuthenticatedControl : WebControl
protected override void OnInit(EventArgs e)
Appendix B: CorePreLoad.aspx
<%@ Register Tagprefix="SharePoint" Namespace="Microsoft.SharePoint.WebControls" Assembly="Microsoft.SharePoint, Version=22.214.171.124, Culture=neutral, PublicKeyToken=71e9bce111e9429c" %>
<SharePoint:ScriptLink name="core.js" runat="server" />