HTML Performance

stablev1.0.0

Use these guidelines to create blazingly fast pages.

Overview

Trying to provide the best experience possible to our customers will ensure their return to our websites. Also, improving the performance can greatly reduce the impact in our infrastructure, resulting in a more maintainable system. There are several techniques from the markup point of view that we can use in our projects to improve the performance.

Clean HTML

Beautiful HTML is the foundation of a beautiful website. To get the most of our CSS and JS, we need an equally strong HTML markup code, that will ease the path for the development.

Doctype

It is important to always declare our HTML5 doctype properly. It must always be the first line in your document, before the HTML tag.

<!DOCTYPE html>

Character encoding

First thing inside the head tag should be the declaration of the character set. Even before the title of the page, to avoid character issues in the title itself.

<meta charset="UTF-8">

Indentation

This has no true impact on the performance of the page, but it goes a long way into improving it's maintainability. Properly indent all your code.

Nesting

Put special care when nesting tags. Block tags should never go inside inline tags. Also, think about the structure you want for your content, for it to make sense and generate a nice outline.

Eliminate unnecessary code

Try to avoid the overuse of div tags or any other element that create unnecessary structure or scaffolding. Create your page with as little elements as possible, granted that it does not loose functionality.

Naming conventions

Always try to define the design or function of the element you're naming, avoiding noise and elements that could make the element confusing afterwards. Following BEM conventions when giving ids or classes to your elements, make this easier, as we get a block name, and then all modifiers are in different, derived classes.

Page weight

The heavier your page is, the slower it will load. Taking this into account, as well as the aging infrastructures in developed countries (there are still a large number of cities relying on copper networks), and the ever-growing number of mobile users, it's very important to keep your page as small as possible.

Server configuration

This is not something that we have direct access to, but we should make sure that these configurations are in place.

The first thing is to configure the server to make use of GZIP or deflate compression, so when sending the images they get compressed, resulting in better transfer times. They can be used in conjunction with HTTP2 protocol to achieve even better load times.

Another important thing to do is enabling the cache of the sites you're developing. Drupal sites make use of cache extensively, but we can replicate or complement this behavior from our server configuration. This will ensure that when somebody is visiting a page already visited, the contents will be served from a local copy, created the first time

.htaccess file

If you have access to the .htaccess file of your server, here you have a true and tried configuration that can help you get a good performance:

Options +Indexes

RewriteEngine On
RewriteCond %{HTTPS} off
RewriteRule (.*) https://%{HTTP_HOST}%{REQUEST_URI} [R=301,L]

AddDefaultCharset utf-8

# BEGIN add type
AddType image/x-icon .ico
AddType video/ogg .ogv
AddType video/mp4 .mp4
AddType video/webm .webm
AddType application/vnd.ms-fontobject .eot
AddType font/ttf .ttf
AddType font/otf .otf
AddType font/x-woff .woff
AddType font/x-woff2 .woff2
AddType application/vnd.ms-fontobject .eot
AddType image/svg+xml .svg .svgz
AddEncoding gzip svgz
# END add type

# BEGIN compress text files
<IfModule mod_deflate.c>

    # Force compression for mangled headers.
    # https://developer.yahoo.com/blogs/ydn/pushing-beyond-gzipping-25601.html
    <IfModule mod_setenvif.c>
        <IfModule mod_headers.c>
            SetEnvIfNoCase ^(Accept-EncodXng|X-cept-Encoding|X{15}|~{15}|-{15})$ ^((gzip|deflate)\s*,?\s*)+|[X~-]{4,13}$ HAVE_Accept-Encoding
            RequestHeader append Accept-Encoding "gzip,deflate" env=HAVE_Accept-Encoding
        </IfModule>
    </IfModule>

    # Compress all output labeled with one of the following MIME-types
    # (for Apache versions below 2.3.7, you don't need to enable `mod_filter`
    #  and can remove the `<IfModule mod_filter.c>` and `</IfModule>` lines
    #  as `AddOutputFilterByType` is still in the core directives).
    <IfModule mod_filter.c>
        AddOutputFilterByType DEFLATE text/html
        AddOutputFilterByType DEFLATE text/css
        AddOutputFilterByType DEFLATE text/javascript
        AddOutputFilterByType DEFLATE text/xml
        AddOutputFilterByType DEFLATE text/plain
        AddOutputFilterByType DEFLATE image/x-icon
        AddOutputFilterByType DEFLATE image/svg+xml
        AddOutputFilterByType DEFLATE application/rss+xml
        AddOutputFilterByType DEFLATE application/javascript
        AddOutputFilterByType DEFLATE application/x-javascript
        AddOutputFilterByType DEFLATE application/xml
        AddOutputFilterByType DEFLATE application/xhtml+xml
        AddOutputFilterByType DEFLATE application/font
        AddOutputFilterByType DEFLATE application/font-truetype
        AddOutputFilterByType DEFLATE application/font-ttf
        AddOutputFilterByType DEFLATE application/font-otf
        AddOutputFilterByType DEFLATE application/font-opentype
        AddOutputFilterByType DEFLATE application/font-woff
        AddOutputFilterByType DEFLATE application/font-woff2
        AddOutputFilterByType DEFLATE application/vnd.ms-fontobject
        AddOutputFilterByType DEFLATE font/ttf
        AddOutputFilterByType DEFLATE font/otf
        AddOutputFilterByType DEFLATE font/opentype
        AddOutputFilterByType DEFLATE font/woff
        AddOutputFilterByType DEFLATE font/woff2
    </IfModule>

</IfModule>
# END compress text files

# BEGIN Expire headers
<IfModule mod_expires.c>
    ExpiresActive on
    ExpiresDefault                                      "access plus 1 month"

  # CSS
    ExpiresByType text/css                              "access plus 1 year"

  # Data interchange
    ExpiresByType application/json                      "access plus 0 seconds"
    ExpiresByType application/xml                       "access plus 0 seconds"
    ExpiresByType text/xml                              "access plus 0 seconds"

  # Favicon (cannot be renamed!)
    ExpiresByType image/x-icon                          "access plus 1 week"

  # HTML components (HTCs)
    ExpiresByType text/x-component                      "access plus 1 month"

  # HTML
    ExpiresByType text/html                             "access plus 0 seconds"

  # JavaScript
    ExpiresByType application/javascript                "access plus 1 year"

  # Manifest files
    ExpiresByType application/x-web-app-manifest+json   "access plus 0 seconds"
    ExpiresByType text/cache-manifest                   "access plus 0 seconds"

  # Media
    ExpiresByType audio/ogg                             "access plus 1 month"
    ExpiresByType image/gif                             "access plus 1 month"
    ExpiresByType image/jpeg                            "access plus 1 month"
    ExpiresByType image/png                             "access plus 1 month"
    ExpiresByType video/mp4                             "access plus 1 month"
    ExpiresByType video/ogg                             "access plus 1 month"
    ExpiresByType video/webm                            "access plus 1 month"

  # Web feeds
    ExpiresByType application/atom+xml                  "access plus 1 hour"
    ExpiresByType application/rss+xml                   "access plus 1 hour"

  # Web fonts
    ExpiresByType application/font-woff2                "access plus 1 month"
    ExpiresByType application/font-woff                 "access plus 1 month"
    ExpiresByType application/vnd.ms-fontobject         "access plus 1 month"
    ExpiresByType application/x-font-ttf                "access plus 1 month"
    ExpiresByType font/opentype                         "access plus 1 month"
    ExpiresByType image/svg+xml                         "access plus 1 month"
</IfModule>
# END Expire headers

# BEGIN Cache-Control Headers
<IfModule mod_headers.c>
  Header unset ETag
  Header edit Set-Cookie ^(.*)$ $1;HttpOnly;Secure
  Header always set X-FRAME-OPTIONS "SAMEORIGIN"
  Header always set X-XSS-Protection "1; mode=block"
  Header always set X-Content-Type-Options "nosniff"
  Header always set Strict-Transport-Security "max-age=63072000; includeSubDomains"
  Header set Content-Security-Policy "default-src 'self' 'unsafe-inline'; script-src 'self' https://www.google.com 'unsafe-inline' 'unsafe-eval'; base-uri 'self'; img-src 'self' https://i.ytimg.com/ https://unsplash.it/ https://selectra.info/ https://via.placeholder.com/ https://picsum.photos/ data:;"
  Header append Vary: Accept-Encoding
</IfModule>
FileETag None
# END Cache-Control Headers

<IfModule mod_setenvif.c>
  <IfModule mod_headers.c>
    BrowserMatch MSIE ie
    Header set X-UA-Compatible "IE=Edge,chrome=1" env=ie
  </IfModule>
</IfModule>

Unused assets

As sites evolve, it's easy to loose track of old code or assets that linger around, making our CSS and JS files heavier than they should. If you make good use of our atomic design, this should not happen normally, but it's always good to use available tools to verify that we are not bundling unused code. Our gulp configuration is configured to purge the CSS in production environments as well as do some tree-shaking in the JS code to remove dead code.

Concatenate and minify assets

To even further down the weight of your assets, remember to concatenate them into bigger files and minify them. Even though the end result of the concatenation is a bigger file, it will drastically reduce the number of requests made to the server, resulting in a faster overall time.

Images

It is very important to get right the use of images. They convey important messages, help hugely to the design, but can ruin the experience if not loaded correctly. Following these practices, you will ensure that your site performs its best.

Formats

Depending on the use of the image, some formats might be better than others:

  • SVG: This vector format is the best in terms of performance and should be used whenever possible. This does not mean that any SVG file works equally: we need to optimize our files so they perform better. Tools like SVGOMG can help you with the optimization of SVG files. Just never use them straight out of Adobe Illustrator.
  • JPEG: JPEG files offer you the best compression algorithms for bitmap files, but they lack alpha channel/transparency. Try to use these file format when possible, with the best compression that does not degrade the image.
  • PNG: This format offer a very high quality compression, with option for alpha channel/transparency, but the compression is not as strong as it is on JPEG files. Use this file when you need a transparency.
  • GIF: Used mainly for animated images, its quality and/or compression are very poor. Sometimes, if the animation you want to include is really big for a GIF, it's better to include a video tag instead. Some video codecs have really good compression algorithms that can easily beat those of GIF format.

👁 Tip
Tools like for both JPEG and PNG files should be used, to ensure that the images are really well optimized. Sometimes, depending on the contents of the image, a PNG might be better compressed than a JPEG. If in doubt, test both formats.

Image sizing

Always serve the images in the size needed for it's location. Delivering a bigger image and forcing it to its container it's a bad practice that takes a huge toll in performance. If you need to have a responsive image, make use of the srcset attribute, to deliver specifically resized images for each breakpoint. Read more about this technique here.

<!-- Notice how we deliver a list of images for given breakpoints -->
<img srcset="awesome-img-320w.jpg 320w,
            awesome-img-480w.jpg 480w,
            awesome-img-800w.jpg 800w"
    sizes="(max-width: 320px) 280px,
            (max-width: 480px) 440px,
            800px"
    src="awesome-img-800w.jpg" alt="Elva dressed as a fairy">

Image lazy-loading

This technique prevent the loading the images outside of the user's visible area, resulting in faster loading times as we are not blocking the render of the page waiting for images that are effectively not seen. As we scroll through our page, and get closer to an image, it gest loaded to have it available an instant before the user scrolls it into view.

Fonts

Try to load as little custom web-fonts as possible. Most of the time, using a custom font for the headings and heros is enough to transmit the company corporative image, leaving the rest of the texts to use user-available fonts. Try these techniques to improve the font loading in your projects.

Placement of scripts

Never place your JS or CSS code inside the HTML, always make a call for it, following the given advices for each type of file. Load in the head the CSS and any JS code that is needed for loading purposes, leaving the rest of the JS code to load in a script tag just before the closing body element.

You can make use of modern technologies to discover above-the-fold content, and only load CSS for that content, leaving the load of the remaining code for a later stage, creating the illusion of a faster loading time.

Requests

We've talked about weight and speed, but there's another factor: number of requests. Each request that we do needs to go through a series of steps that, as short as they might be depending on the conection, they exist and keep adding up. As the calls and connections have to be made, sometimes is better to concatenate files into a single request, not adding the connection times to the overall load time.

Sometimes, configuring the cache of the requests that we have to make can help too, keeping chached versions of the files on the user's machine, not having to download static assets that won't change over time.