Site Speed: Prioritize Visible Content

Written on

These days Google is using your website's loading time as one of the factors by which your rank is calculated. Google has provided a tool to tell you how fast your website is and what you can do to improve it. Some of the suggestions are obvious, but one of the more confusing ones is "Prioritize visible content."

Varvy.com wrote an article about prioritizing visible content and provided an example page to show how to fix the issue. Ironically, if you pass this page through Google's PageSpeed Insights tool you'll find that it has no issues except prioritizing visible content. I'll explain why in this article.

What does "prioritize visible content" mean?

To understand this, we need to know what "above the fold" means. "Above the fold" refers to everything that's visible on the page without scrolling. When a page loads, all the above-the-fold content should not be slowed down by things that are loading below the fold.

Google determines whether you have an issue with prioritizing visible content if the above-the-fold content looks different between the time the HTML alone has finished downloading and the time the entire page has finished loading. I'll be talking about several things that may cause this to happen.

In this article, mentions of "before loading" will refer to the time when the HTML has finished downloading but the rest of the page resources have not finished loading; "after loading" will refer to the moment when every single resource has finished downloading and rendering.

Javascript

If you have deferred Javascript (using <script async> or <script defer>), or you're using events like onload or jQuery's $(document).ready() then the Javascript will not load until after the HTML has finished downloading. If you're using this to collapse boxes or dropdown menus then the boxes will appear uncollapsed before loading the page and collapsed once the whole process is done. This means that the page will display uncollapsed boxes for a moment before it finishes loading.

How do I solve it?

The best solution for this is to have a short piece of inline Javascript in <script> tags immediately following the elements you wish to collapse which collapses the elements. The logic to handle clicks and uncollapsing can be loaded asynchronously later.

Here's an example for clarity:

<button id="menu-button">Menu</button>
<ul id="menu">
    <li>Item 1</li>
    <li>Item 1</li>
    <li>Item 1</li>
</ul>
<script>document.getElementById("menu").style.display = "none";</script>

Since this code cannot be deferred, it should not rely on external Javascript files that are being deferred, so it should be done with plain Javascript instead of frameworks like jQuery.

Web Fonts

Web fonts are a major problem for prioritizing visible content. Most webfont implementations, such as Google Fonts, will have the text invisible until the font finishes loading and that's really bad for user experience. It will also trigger the "prioritize visible content" warning because at first the page appears without text and afterwards the text appears.

How do I solve it?

My first approach to solving it was to find a way to get the browser to display a default font while the web font was loading, but this actually did not solve the problem. Google is still complaining about prioritizing visible content. The reason is that, even though there's text, the above-the-fold content still looks different before loading and after loading. Because this solution doesn't work I won't be explaining how to do it.

The solution to the web fonts problem was to embed the font right in the page, but to make sure that it goes as fast as possible, it only downloads the font data the first time, then it loads the data from a cache in the browser. Here's how it's done:

Generate embeddable font data

Instead of loading the font from a file, we're going to convert it into a single string of bytes that can be embedded right in the CSS. To do this, use FontSquirrel's tool to convert your font files. Follow these steps:

  1. Select the font files to upload.
  2. Click "Expert" to open up all the options we'll need to make this happen
  3. Deselect all the font formats
  4. Scroll down to the CSS options and select "Base64 encode." This is the most important part.
  5. Click on "Download your kit" which will provide you with a zipped file.
  6. Pull stylesheet.css from the zipped file and set the correct font-family, font-weight and font-style for your fonts.

I'd suggest renaming the stylesheet as well, I called mine "font-data.css".

Loading the font

We're going to use Javascript to load the font. Normally, we don't want Javascript to interfere with styling on the page, but this will not have a detrimental effect if for some reason Javascript is disabled and the font fails to load. The page is still usable and readable.

There are two parts to this:

  1. The font is not cached yet.
  2. The font is already cached.

If the font is not cached yet, what we're going to do is download the font from the server using AJAX and embed that on the page. Here's how it's done:

var fontURL = "/css/font-data.css";
var request = new XMLHttpRequest();
request.onreadystatechange = function(e) {
  if(request.readyState == 4 && request.status == 200) {
    var style = document.createElement("style");
    style.innerHTML = request.responseText;
    document.getElementsByTagName("head")[0].appendChild(style);
    localStorage.setItem("fonts", request.responseText);
  }
};
request.open("GET", fontURL, true);
request.send();

We're using Javascript's localStorage API to cache the font data. Once the font data is cached, we can put it onto the page like this:

var fonts = localStorage.getItem("fonts");
document.write("<style>" + fonts + "</style>");

We also can provide a fallback for browsers that don't support localStorage. For those browsers, we'll load the CSS stylesheet the normal way, using a <link> element. The following code will do that for us:

var loadStylesheet = function() {
  var fontURL = "/css/font-data.css";
  link = document.createElement("link");
  link.rel = "stylesheet";
  link.href = fontURL;
  link.media = "only x";
  link.addEventListener("load", show);
  document.getElementsByTagName("head")[0].appendChild(link);
  function show(e) {
    e.target.media = "all";
  }
}
window.addEventListener("DOMContentLoaded", loadStylesheet);

When we put all of this together, this is what we get:

(function(){
var fontURL = "/css/font-data.css"; if(localStorage) { var fonts = localStorage.getItem("fonts"); if(fonts) { document.write(""); } else { var request = new XMLHttpRequest(); request.onreadystatechange = function(e) { if(request.readyState == 4 && request.status == 200) { var style = document.createElement("style"); style.innerHTML = request.responseText; document.getElementsByTagName("head")[0].appendChild(style); localStorage.setItem("fonts", request.responseText); } }; request.open("GET", fontURL, true); request.send(); } } else { // If local storage not available, use the CSS technique var loadStylesheet = function() { link = document.createElement("link"); link.rel = "stylesheet"; link.href = fontURL; link.media = "only x"; link.addEventListener("load", show); document.getElementsByTagName("head")[0].appendChild(link); function show(e) { e.target.media = "all"; } } window.addEventListener("DOMContentLoaded", loadStylesheet); } })();

It's a bit long, so I suggest passing it through a minifier. This code should not be put into an external file, it should be embedded right in the HTML with <script> tags in the <head> of the document. Remember to update the fontURL variable to point to the location of your font data stylesheet.

Images

I haven't had major trouble with images in above-the-fold content, but my recommendation is to not have too many and to make sure the file sizes are relatively small. If an image above the fold takes too long to load Google PageSpeed tools will warn you to prioritize visible content as the image will be missing when the page starts to load.

Too much inline content

Here's where Google PageSpeed Insights was not clear: If there's too much content above the fold you'll have a problem with prioritizing visible content as well. If you remember the page I mentioned early on in the article, it had an issue with prioritizing visible content. Google's message says "The entire HTML response was not sufficient to render the above-the-fold content." But why? There's nothing on that page but HTML.

Here's the truth: Google doesn't wait for all the HTML to load. I'm not certain at what point it begins rendering, but if there's too much code before all the visible content then it will try to render the first part of the code while it's downloading the rest.

How do I solve it?

The only solution is to rewrite your code to use fewer bytes.

Images that are very large should not be inline and should be in a separate file.

If your inline stylesheets are very large, try removing rules from inline stylesheets that don't apply to above-the-fold content. Those rules can be placed into an external stylesheet that's loaded asynchronously.

A technique for loading stylesheets asynchronously

Since this solution uses Javascript, I recommend only putting styles that are non-essential in these stylesheets. The page should still be usable if these stylesheets are missing.

loadCSS("/css/style.css");
function loadCSS(url) {
  var link = document.createElement("link");
  link.media = "only x";
  link.rel = "stylesheet";
  link.href = url;
  link.addEventListener("load", function(e) { e.target.media = "all"; }, false);
  document.getElementsByTagName("head")[0].appendChild(link);
}