When customers want an intranet portal, they sometimes insist upon a landing page with a lot of content. Additionally, the page must be very attractive and the content needs to be targeted based upon user profile properties to ensure that the users are presented with the information that is most valuable for them.
This results in a lot of requests to build the page. If your customer has offices all around the world, this is even more difficult because the network performance can be very different.
In this post, I’ll give a overview of the performance optimizations to the solutions that we implemented for our customers to reduce the number and the size of requests to build the page.
Performance enhancements
The Microsoft support article Introduction to performance tuning for SharePoint Online offers a good starting point. Some of the tips below are coming from that article. Other tips are coming from our own experience or from the code reviews we got from Microsoft.
jQuery.getScript
Be careful when using jQuery.getScript to load scripts dynamically. This function adds a timestamped query parameter to each request to enforce the reload of the script:
/js/v1/SpikesHelper.js?_=1459511141746
Because of this query parameter, the browser cache will never be used, not even if you would load the same script multiple times on the same page.
If you want to cache the script, you have to set the cache property globally using $.ajaxSetup() or create your own method to load the scipts using $.ajax().
More information can be found in the jQuery documentation.
Another alternative could be using a loading framework such as RequireJS.
SP.RequestExecutor
If you want to make cross domain calls in SharePoint, you can use SP.RequestExecutor. However, don’t use SP.RequestExecutor for every call because this will always result in an extra /_api/contextinfo request before the postQuery request.
if (onHostWeb) { window.SpikesHelper.getCachedScript(scriptBase + "SP.RequestExecutor.js", function () {var executor = new SP.RequestExecutor(window.DataProvider.HostWebUrl());executor.executeAsync({ url: searchCallUrl, method: "POST", headers: {"accept": "application/json;odata=nometadata","content-type": "application/json; odata=verbose","X-RequestDigest": $('#__REQUESTDIGEST').val() }, success: onSearchSuccessRest, error: onSearchFailRest}) });}else { $.ajax({'url': searchCallUrl,'type': "POST",'headers': { "accept": "application/json;odata=nometadata", "content-type": "application/json; odata=verbose", "X-RequestDigest": $('#__REQUESTDIGEST').val()},'success': function (data) { onSearchSuccessRest(data);},'error': onSearchFailRest });}
Local storage
Use your browser’s local storage to cache information that doesn’t change frequently such as user profile properties or managed metadata.
Since it may sometimes be necessary to clear this local storage, I suggest providing a button to do this.
saveToStorage: function (key, data, expirationSeconds) { "use strict"; var expirationMS = expirationSeconds * 1000,record = { value: data, timestamp: new Date().getTime() + expirationMS }; localStorage.setItem(key, JSON.stringify(record)); return data;},loadFromStorage: function (key) { "use strict"; var record = JSON.parse(localStorage.getItem(key)); if (!record) {return null; } if (new Date().getTime() < record.timestamp) { return record.value; } // The entry has expired, so remove from local storage localStorage.removeItem(key); return null; }, clearStorage: function () { for (var i = localStorage.length - 1; i >= 0; i--) {if (localStorage.key(i).startsWith('Spikes')) { localStorage.removeItem(localStorage.key(i));} }}
REST batching
Where possible, execute REST commands in batches. You can for instance use Steve Curran’s REST batch executor.
JSON light
You can significantly reduce the payload of your REST request with JSON Light as stated in this blog post.
If you have existing code that uses odata=verbose, be aware that you also have to change the parsing of the results because the format of the returned JSON is different.
In some cases, for instance if you want the edit link of an item, odata=nometadata doesn’t provide enough information and we had to use odata=minimalmetadata.
Search queries
If you need to implement targeting, where possible use search queries. It will take some time before new items become visible on the page, but the request for the data will be quicker since the search results are indexed.
If you have complex queries you might need to do a POST request instead of a GET request.
You can test the performance of your search queries using the SharePoint Search Query Tool.
CDN
Use an Azure CDN to host your own custom files. Not only scripts and CSS, but also the images for the branding. The content that is accessed from a CDN is much faster available than the content that is hosted in SharePoint Online.
For some types of files, such as fonts and html templates, you might need to set CORS headers because using a CDN will result in cross domain calls.
For files coming from Azure CDN, you can control the cache behaviour using the cache-control header depending on your environment. If you have clients with a very slow network, You might even consider to set the max-age to the maximum duration (max-age=31536000) and implement cache busting functionality, for instance using a loading framework such as RequireJS.
External script components can also be referenced from a CDN. Since a browser can only handle a number of request from the same host simultaneously, you could even reference external scripts from multiple CDN’s to improve the page load.
Bundling and minification
If you have multiple JavaScript and CSS files, you can bundle and minify those to reduce the number of requests as well as the size of the requests.
Image sprites
If you have a lot of small images in the branding, you can combine those into one image sprite.
Image renditions
Image renditions are slow in SharePoint because the rely upon the cache on the server. Since SharePoint Online is a huge farm with a lot of different servers, your request will probably be served by another server and it is not likely that you will get a cache hit.
SharePoint navigation
As stated in the support article Navigation options for SharePoint Online, you should never use structural navigation. Use search driven navigation or managed navigation instead.
If you are not using Quick Launch, it is best to disable this in Site Settings – Navigation Elements.
Asset library vs Style library
If you have to store files in SharePoint (for instance proprietary files that you don’t want to store on the CDN), consider using the asset library instead of the style library because the style library requires an extra authentication step.
Custom master page
A custom master page is not recommended, but should you still need it, consider adding an extra placeholder at the end of the body section to load your custom scripts.
Home page layout
Most of the time, we started our home page layout with web part zones and we add web parts to present the different components to the user. This provides a lot of flexibility when we want to adapt the home page.
If there are a lot of web part zones and web parts on the page however, it takes a long time for the server to build up that page. The server returns an SPRequestDurarion header that contains the server processing time in millisecond. For a publishing page with 8-10 web parts we observed times of 2-3 seconds and sometimes even more.
We implemented a home page layout based upon AngularJS and now the server processing time is down to 1 second and even less.
If still better performance would be necessary, the next step would perhaps be to build the home page in an Azure App web using the Office UI Fabric and linking from there to the SharePoint Online site.
Summary
When designing a landing page for an intranet portal, there are definitely a lot of things you can do to reduce the network traffic and server load.
–
–
I love the creativity of your articles. its very informative.
thanks again for sharing and keep up the exceptional work.
LikeLike