Anthony McLin

Drupal Performance: Eliminating the Render-Blocking resources

One of the biggest factors in page performance is render-blocking assets. These are assets that prevent the page from being rendered while they load. Typically this is Javascript and CSS in the head of the document. When you're using a CMS like Drupal, you often don't have much control over how these are structured since the CMS does its own management for combining various files into cached versions depending on what components or modules exist on a given page. However if you run a Lighthouse audit against a typical Druapl site (like mine), you'll see that reducing render-blocking resources is one of the most impactful steps you can take to improve the performance:

We should be able to make some significant improvements here for very little effort.

Move Scripts to the Bottom of the Page

The easiest thing you can do is move your Javascript includes out of the document head and to the bottom of the page. In my template I've already split scripts into 2 pieces, $head_scripts and $scripts. In my document head I have the line of PHP to output that set of scripts, and at the bottom of the page I have the second line to output all the rest of the scripts.

<html>
<head>
  ...
  <?php print $head_scripts; ?>
</head>
<body class="<?php print $classes; ?>" <?php print attributes; ?>>
  ...
  <?php print $scripts; ?>
</body>
</html>

Since I already am limiting how many scripts are in my Document head, let's move on to other improvements.

Eliminate Unnnecessary Legacy Libraries

The first thing that stands out to me in the Lighthouse audit is modernizer.js. Modernizr is a feature detection library that uses Javascript to dynamically classes on your document indicating if the visitor has various browser features that aren't commonly available across browsers. Browser capability testing is a much better approach than testing for specific versions of specific browsers, because you can easily capture all edge cases and deliver true progressive enhancement. But since I last updated the theme for my site at least 5 years ago, it's very likely the feature detection of Modernizer is no longer necessary since modern browsers (including IE) all support whatever I was dealing with at the time. Let’s take a look and see what plugins were enabled:

/* Modernizr 2.6.2 (Custom Build) | MIT & BSD * Build: http://modernizr.com/download/#-fontface-backgroundsize-borderimage-borderradius-boxshadow-flexbox-hsla-multiplebgs-opacity-rgba-textshadow-cssanimations-csscolumns-generatedcontent-cssgradients-cssreflections-csstransforms-csstransforms3d-csstransitions-applicationcache-canvas-canvastext-draganddrop-hashchange-history-audio-video-indexeddb-input-inputtypes-localstorage-postmessage-sessionstorage-websockets-websqldatabase-webworkers-geolocation-inlinesvg-smil-svg-svgclippaths-touch-webgl-shiv-mq-cssclasses-addtest-prefixed-teststyles-testprop-testallprops-hasevent-prefixes-domprefixes-load */

Ok, based on that I can see it’s a bunch of standard CSS feature checks for gradients, transforms, rounded corners, the usual lot of stuff that didn’t work until IE9 and IE10. Let me check my theme’s JS….. nope, nothing there that depends on anything from Modernizer. How about my theme’s CSS? Again, nothing that is depending on the classes that Modernizer generates. All this time this has been loading on my pages, wasting CPU performance. I probably had it because I followed the HTML 5 boilerplate defaults, or perhaps I was doing some transparency and rounded corners support in IE8. Regardless, it’s certainly unnecessary now, let’s remove it by deleting modernizr-2.6.2.min.js from my theme folder and also commenting out the lines in my template preprocess html hook that were adding it to the document head script array:

$path = drupal_get_path('theme', 'amc');
drupal_add_js($path . '/js/vendor/modernizr-2.6.2.min.js', array('scope' => 'head_scripts', 'weight' => -1, 'preprocess' => FALSE));

Let’s see what that does for the performance:

Nice! That shaved off almost a third of a second of download time. It also eliminated an HTTP request, and removed a bunch of Javascript execution logic.

Specify media types for CSS links

That’s the only render-blocking Javascript file in the list because everything else is being loaded from the document footer. All the other identified requests is CSS. I think we can probably tackle those as well. Originally I wrote this theme with hand-built CSS… painful in the days before CSS preprocessors. I’m sure I could whip up some SASS/SCSS magic to consolidate everything to one CSS file, but adding build systems is a bit beyond the scope of quickly addressing performance issues. I could also manually flatten all the theme CSS files to a single CSS file, but that also seems a bit convoluted and defeats the value of leveraging Drupal's capabilities to manage this automatically based on page contents.

But here’s something interesting in how Lighthouse identifies render-blocking CSS

Does not have a media attribute that matches the user's device.

Ahah! The stylesheets are linked with the attribute media=“all” which means both the print and screen styles are loaded all the time. By being more specific with the media attribute on the <llink> tags, the styleshseets will no longer be render-blocking. I can easily clean this up by making some small changes to the theme's .info file:

stylesheets[all][] = css/normalize.css
stylesheets[all][] = css/main.css
stylesheets[all][] = css/base.css
stylesheets[all][] = css/skeleton.css
stylesheets[all][] = css/menu.css
stylesheets[all][] = css/portfolio.css

Changing these from media “all” to media “screen” should help. Also, I have all my print styles wrapped in one print media query in main.css, so I’ll move those to a new file called print.css and update the theme .info file accordingly:

stylesheets[screen][] = css/normalize.css
stylesheets[screen][] = css/main.css
stylesheets[screen][] = css/base.css
stylesheets[screen][] = css/skeleton.css
stylesheets[screen][] = css/menu.css
stylesheets[screen][] = css/portfolio.css
stylesheets[print][] = css/print.css

There’s also one CSS file loaded remotely from Google Fonts. That one isn’t in the theme .info file, instead it’s being added via PHP, so I need to address it there so that it also is set with the media type. This one is the web fonts, which are needed both for screen and print, so I'll set the media type accordingly when I add it to Drupal's list of CSS files for the page:

//Add fonts from Google CDN
drupal_add_css('http://fonts.googleapis.com/css?family=Source+Sans+Pro:400,600,400italic|Open+Sans:300', array('type' => 'external'));

Becomes:

//Add fonts from Google CDN
drupal_add_css(
  'http://fonts.googleapis.com/css?family=Source+Sans+Pro:400,600,400italic|Open+Sans:300',
  array(
    'type' => 'external',
    'media' => 'screen or print'
  )
);

Aggregating CSS and JS

Built-in to Drupal is a feature that combines Javascript and CSS files based on the contents of what's on a specific page. The idea is that if the indivudal asset files are small, then Drupal can dynamically combine only the ones necessary for the page, reducing the number of HTTP request,s and preventing scripts and CSS from existing on pages where they could cause conflicts. However the built in aggregation is simply not very aggressive. It prefers the approach of letting these files be highly reusable across pages. That improves multipage browsing, but at a significant cost to the first page load since there are many more HTTP requests being made for individual files. Given that most of my traffic doesn't proceed past one or tow pages, it's pretty clear that first page load is a more improtant performance impact than subsequent page browsing.

There is a Drupal module for Advanced CS/JSS Aggregation which provides more granular controls and aggressive caching mechanisms than the out-of-box concatenation and caching. Install it via the modules manager, and then enable it:

Then in the configuration for the module, let's enable some features:

  1. Use Customized Settings - Since my site isn't HTTP2, I want to get more fine grained than the defaults
  2. Bundler is Active - Enable this, or else why even both installing the module?
  3. Target Number Of CSS Bundles Per Page - Since I don't have a lot of blocks or other reusable elements, I can get away with very few generated files. I set this to 2. The lower it is set, the better the initial page load performance will be, but the higher the likelyhood that visitors will need to download page-specific files as they navigate across multiple pages of your site.
  4. Target Number Of JS Bundles Per Page - Same idea, but for Javascript instead of CSS. I set this to 1.

As usual, flush all the caches, and let's run the Lighthouse audit again:

Nice! Just enabling this plugin reduced 2 render-blocking requests by combining them! However, Lighthouse is now warning that my Javascript is no longer compressed like it was when using the out-of-box concatenation provided by Drupal:

To fix this, enable the plugins included AdvAgg Compress Javascript and AdvAgg Compress CSS:

Set the configuration for each. For the CSS compression module I want:

  1. Compressor: Core - I don't want to use YUI because I don't want to download the library, and because it is an ancient compressor that's buggy with modern files.
  2. Inline Compression: Disabled

​For the JS compression module I want:

  1. Compressor: JSqueeze - This is the fastest that doesn't require reconfiguring the server
  2. Inline Compressor: JSQueeze

​There's a bunch more options in the main module if you choose "Use Customized Settings", but those may impact site behavior, so I'll go through them later with more granularity when I can test specific outcomes. In the meantime, just these basic straightforward settings have made significant performance improvements to this Drupal site.

Add new comment