Drupal

Performance: how Drupal will appeal to Google :)

Published on 15 July 2022
Photo illustrating an internal conference and the debate on the performance of a Drupal site
Google's performance index distributes scores, but more importantly, recommendations. How can you meet its requirements with a Drupal site?

Google PageSpeed?

Google PageSpeed is the reference indicator for measuring website display performance, especially on mobile. Google uses performance criteria in its website ranking indices for SEO. 

Each analyzed site is given a score from 0 to 100. The goal set by Google is to have a score above 90 out of 100... And this goal is not so easy to achieve, especially for the mobile score, if you use a CMS without particular optimizations. It's also important to note that Google PageSpeed analyzes an anonymous user journey.

Elements Analyzed

  • First Contentful Paint – This metric is the loading speed as perceived by the user (displaying the useful content). 
  • Time to Interactive – Time to interactivity measures how long it takes for a page to become fully interactive (the page responds to user interactions within 50 milliseconds).
  • Speed Index – The speed index measures how quickly the content of our page is displayed on the screen while the page is loading. The speed index will vary greatly depending on the technology used to generate the site's content. This index is obtained by comparing a large amount of analyzed sites.
  • Total Blocking Time – This measures the sequences between the First Contentful Paint and the Time to Interactive, when the task duration exceeded 50 milliseconds.
  • Largest Contentful Paint – This metric measures the loading speed of the heaviest element on the page (for example, a decorative image across the entire width of the page).
  • Cumulative Layout Shift – Here, Google measures the movement of visible elements in the viewport (in cases of loading third-party fonts or using JavaScript to add styles).

Google also detects that our site is built on Drupal and suggests optimizations or modules accordingly.

Optimizing the Performance of a Drupal Site

There is no magic, but rather a method—an empirical one. With each optimization, we need to test the results and advance step by step. On this note, the Site Audit module helps to obtain quick and reliable reports to detect common issues and provide an analysis of a Drupal site. Reports can be generated in several formats, including plain text, HTML, and JSON.

Aggregate CSS and JavaScript Files

Among the checks to perform before putting the site into production, it is recommended to enable CSS and JavaScript aggregation. This operation reduces the number of CSS and JS files. To enable it, go to /admin/config/development/performance and ensure that both options are enabled.

By simply reducing the number of raw HTTP requests sent to the server, we will reduce the time needed to download the assets necessary to display the page. 

Install the AdvAgg Module

CSS and JavaScript aggregation can be improved with the AdvAgg module. This module offers many options, notably by minimizing and compressing our site's resources. Google PageSpeed, on its side, will measure the compression of CSS and JavaScript.

Since some settings can alter the display of the site, it is of course important to test this activation in the development environment first. This often requires a few adjustments and workarounds in the code.

Reduce HTML

In addition to CSS and JS, it is advisable to also reduce the weight of the HTML by removing comments and extra line breaks from the code. This operation can be facilitated by using the Minify Source HTML module. This is a simple module that reduces the page to the bare minimum. While performance gains are not drastic, it's another detail to add to our optimization list.

Reduce the Number of DOM Elements

The aim here is to limit the number of

s and nestings on a page, which create a large number of objects and will increase the memory usage required by the browser to store them. 

Google PageSpeed penalizes the performance score if the site's DOM has more than 800 elements and generates an error when it exceeds 1,400 elements. It also penalizes the score if there are more than 32 levels of nested elements.

Reducing the size of the DOM in Drupal can be quite difficult. Each entity, field, and element is wrapped by at least one div, sometimes more. The optimization task, which is tedious, is to remove all unnecessary tags. It is a delicate operation because it is subjective for the developer, who must assess the balance between the performance gain and the user experience (especially the number of elements on a page). For this reason, it concerns both the design team and the technical team!

Remove Unused Styles

Drupal adds a number of CSS files to the theme. The best solution may be to remove them, then only add the files we need. Removing styles that are not necessary for our theme is essential for improving the overall size of our page.

To remove CSS files, we need to add a library-override directive to the theme's .info.yml file, setting the value to "false" for each file we want to remove.

libraries-override:
  system/base:
    css:
      component:
        css/components/ajax-progress.module.css: false
        css/components/align.module.css: false
        css/components/autocomplete-loading.module.css: false
        css/components/fieldgroup.module.css: false
        css/components/container-inline.module.css: false
        css/components/clearfix.module.css: false
        css/components/details.module.css: false
        css/components/item-list.module.css: false
        css/components/js.module.css: false
        css/components/nowrap.module.css: false
        css/components/position-container.module.css: false
        css/components/progress.module.css: false
        css/components/resize.module.css: false
        css/components/sticky-header.module.css: false
        css/components/system-status-counter.css: false
        css/components/system-status-report-counters.css: false
        css/components/system-status-report-general-info.css: false
        css/components/tabledrag.module.css: false
        css/components/tablesort.module.css: false
        css/components/tree-child.module.css: false

After clearing the cache, our theme will no longer include the associated styles.

Regarding JavaScript files, Drupal only loads a JavaScript file (and its dependencies) when its functionality is needed. If a module is misconfigured, it may add script files to all pages. Removing them with hook_page_attachments_alter() helps improve the page score.

Eliminate Render-Blocking Resources

Page loading can be slowed down by downloads needed to display the page. While the page is busy downloading CSS and JavaScript, users are waiting. This waiting time is called Time to Interactive by Google PageSpeed.

You can reduce this delay with a few optimizations:

  • Avoid using @import statements in our CSS files (an extra HTTP request to fetch the CSS file).
  • Optimize CSS so as to load only the necessary CSS (important when using CSS frameworks like Tailwind, as the package is huge and it’s not necessary to use it all). 
  • Minimize and reduce CSS as much as possible, into just one or two small files.
  • Defer CSS using JavaScript to load them after the main rendering of the page. 

To prevent JavaScript from blocking rendering, it’s possible to move all script tags to the bottom of the page. We can also ensure they are not part of the initial page load by adding the defer attribute. Drupal's JavaScript dependencies let you know which files must be loaded first and ensure that scripts are added in the right order.

You can also use the AdvAgg Modifier module (included in AdvAgg) to further defer CSS and JavaScript on the page.

Resize and Reduce Images

Image optimization is essential for the Google PageSpeed index. When using the Media module, we must ensure that Drupal resizes and reduces the size of all images we upload by setting up properly configured image styles.

We should ensure the image style resizes the image for the display where it appears. This prevents displaying an image that's too large and heavy for a user who visits the page on mobile.

In addition to resizing the image, we must also reduce image file sizes. The page at /admin/config/media/image-toolkit lets us control the compression quality for JPEG images. Setting this value to about 75% will greatly reduce our image size without noticeably affecting its quality.

It’s also a good idea to repeat this operation for PNG images. The TinyPNG module can be used to greatly reduce file size (by 20–30%). TinyPNG requires an API key, but the service is free for up to 500 images, which is sufficient for most of our projects.

Use Next Generation Image Formats

Google PageSpeed often asks us to "Serve images in next-gen formats." This recommendation is focused on using images in WebP and AVIF formats as they offer better compression than other formats (including PNG).

Browser support for WebP has only recently become widespread. We now enable the option available in Drupal Core for all our projects.

Set the Image Height and Width

If we do not set a height and width for images, the browser will not know the size to display until the file is downloaded and inserted in the page. This can lead to layout shifts, which affect the performance score. By adding height and width to the image markup, we allow the browser to allocate space.

Some image

Lazy Loading Images

Lazy loading is a technique which allows users to download only page elements as they are needed. This means that an image not immediately visible to the user is not downloaded until the user scrolls down. This technique has a clear impact on the size of the initial page load since not all elements are downloaded at once.

The Lazy Load module makes it possible to configure lazy loading. It should be installed at the beginning of site development, so you don’t have to go back and change image styles later. 

The Drupal Cache

This is the first thing to check. We must make sure the Drupal cache configuration is correct. Note that Drupal is smart enough to automatically invalidate cached pages that a contributor has just modified. Therefore, it is not necessary to clear the Drupal cache after every content update.

Drupal Cache Headers

Next, we should check the "x-drupal-cache" and "x-drupal-dynamic-cache" headers. They indicate the cache status of the page, with values "hit," "miss," or "uncachable." The ideal configuration is:

  • x-drupal-cache: HIT
  • x-drupal-dynamic-cache: HIT

Isolating cache problems can be quite time-consuming. The http.response.debug_cacheability_headers setting in the services.yml file can help. To enable it, set it to "true" and clear the Drupal caches.

After doing this, the "X-Drupal-Cache-Tags," "X-Drupal-Cache-Contexts," and "X-Drupal-Cache-Max-Age" headers are added to the response. These headers tell us which elements interacted with the cache to produce the response.

The Devel module contains a module called Web Profiler that can be used to inspect a page's behavior and the elements that may cause cache invalidation.

Increase Static Cache Expiration

An active element will be served outside of the dynamic files managed by Drupal (images, CSS, JavaScript, or uploaded files). By default, Drupal treats these separately and allows them to be cached for two weeks. This configuration may be insufficient, especially if these resources do not change.

This setting is defined in the .htaccess file.

# Cache all files for 2 weeks after access (A).
ExpiresDefault A1209600

Google PageSpeed will lower our score if our cache expiration is less than a year. So ideally, you should set it as follows: 

# Cache all files for 2 weeks after access (A).
ExpiresDefault A31536000

If the project uses a CDN to manage content, the cache value will likely be replaced by another value. So, you should check its configuration to increase this value to at least a year... Then check the headers of static resources by looking for the "Cache-Control" header. It should have a value of 31536000, which is roughly the number of seconds in a year.

cache-control: max-age=31536000

External Cache

As a last resort, and depending on the site's functional scope (anonymous traffic or not), external cache solutions can considerably improve your users' experience. We regularly use:

  • Redis (caching at the database query level) – It lightens and reduces database accesses;
  • Varnish (HTTP caching) – Known for greatly improving site display speed for anonymous traffic... until expiration.

That said, we must keep in mind that this type of cache acts as a "band-aid," and it can help when application-level optimizations are already in place.

Let’s Not Forget About the Server!

Before starting Drupal optimizations, let’s remember to enlist our hosting management to make our work easier. We must work in environments that are:

  • Properly sized (SSD / memory (RAM) capacity / CPU speed, etc...)
  • Up-to-date configurations (OS, nginx, PHP, mariaDB, and Vhost).
  • Asset compression on .gzip.

Conclusion

An ideal situation does not exist. If performance is the most important criterion for your client, warn the design team! Performance optimization should be organized from the kick-off meeting, during the initial UX workshops, and when deploying the Design System. There’s no magic!!

For the action plan, we prefer to start with classic application-level optimizations and constantly measure their effects. Measurements should be repeated several times, as reports vary from moment to moment. Google PageSpeed is useful, but less for its final score than for its recommendations. It is a tool that helps focus our efforts on targeted areas for improvement. It also helps us maintain and update our development stack.

Hang in there—it's a tough job :)

Read more articles on Drupal