Website Load Speed Optimization - Part 2

April 2017 ยท 5 minute read

In the previous post, I explained the reasoning behind the code you’ll see in this post. Head over there if you want to gain a better understanding of the concepts.

Google PageSpeed Plugin for Nginx & Apache

If you use nginx or apache as your webserver, then you should definitely consider using Google’s PageSpeed plugin. It already implements all the best practices and optimizations. I highly recommend you use that for your websites.

If you go with PageSpeed, then you don’t need to read any further.

Nginx

1. Caching

Use h5bp’s caching configs, and edit them as you see fit, to add the proper Expires and Cache-Control headers. Feel free to include some of their other configs as well, such as cache busting, cross domain settings, etc.

# In your server block
include b5hp/expires.conf

Set up a cache server that sits in front of your origin server:

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
# Path to save the cached files
proxy_cache_path /var/www/nginx/cache/mujz.ca levels=1 keys_zone=mujz_cache:1m max_size=256m inactive=60m;

# Cache server
server {
  listen       80 default_server;
  listen       [::]:80 default_server;
  server_name  mujz.ca;

  location / {
    # proxy requests to origin server
    proxy_pass          http://127.0.0.1:9001;
    # cache responses to mujz_cache
    proxy_cache         mujz_cache;
    # bypass the cache if the request has the header Cache-Control: no-cache
    # You can add that header in Chrome when you reload the page with cmd+shift+r
    proxy_cache_bypass  $http_cache_control;
  }
}

# Limit the number of requests coming from a single IP address
limit_req_zone $binary_remote_addr zone=2persec:32k rate=2r/s;

# Origin Server
server {
  listen       9001;

  # Only allow requests from the cache server
  allow        127.0.0.1;
  deny         all;

  # set the encoding
  charset      utf-8;

  # Limit the number of requests as defined by zone 2persec
  limit_req    zone=2persec burst=10;

  # Set Expires and Cache-Control headers
  include      b5hp/expires.conf

  # Static content location
  root         /usr/share/nginx/html;

  # 404 page
  error_page   404 /404.html;

  # For serving static content
  location / {
    try_files  $uri $uri/index.html =404;
  }
}

2. Gzip

Put this in your server or http block. I recommend the latter:

gzip on;
gzip_min_length 256;
gzip_disable "msie6";
gzip_vary on;
gzip_proxied any;
gzip_comp_level 6;
gzip_buffers 16 8k;
gzip_http_version 1.1;
gzip_types *;

3. Dynamic Image Resizing

In order for this to work, you’ll need a version of nginx that is compiled with the image_filter_module. Feel free to use this nginx docker image, which is built with http2 support and the image_filter_module.

Resize the requested image to the specified width for requests like /img1000/my-img.jpg, /img2000/my-img.jpg, etc. Note that /img1000 and /img2000 are not directories on the server and those files are created upon request. Check out the demo.

location ~ ^/img([0-9]+)(?:/(.*))?$ {
  # Find the original image file
  alias /usr/share/nginx/html/img/$2;
  # Resize it while preserving the aspect ratio
  image_filter_buffer 10M;
  image_filter resize $1 -;
}

Since we have set up a cache server in the previous step, the image will be resized only the first time it is requested; the cache server would serve the consequent requests for that image.

Additionally, our server is safe from DOS attacks because we’re limiting the rate of requests coming from any one IP address.

4. Serve WebP images to supported browsers

Add this in the http block:

# set the variable webp_suffix to ".webp" if client accepts image/webp
map $http_accept $webp_suffix {
  default   "";
  "~*webp"  ".webp";
}

Add this in the origin server block:

# will serve my_image.png.webp or my_image.png or 404
location ~* \.(?:jpg|jpeg|gif|png)$ {
  try_files $uri$webp_suffix $uri =404;
}

This requires that you save two versions of your images; the original (say my_image.png) and a webp version (`my_image.png.webp). If the browser supports webp and you have a webp file, it will be served, otherwise the original will be served or a 404 if it doesn’t exist.

5. Enable HTTP2

If you already have SSL setup, then this is easy. Just add http2 after listen:

listen       443 ssl http2 default_server;

Test if this worked by opening chrome’s inspector->Network and checking the protocol tab. If it says h2 then you’re good. If it didn’t, check that your version of nginx was compiled using --with_http_v2_module. If it is, then make sure that your nginx was compiled with at least version 1.0.2 of WebSSL. If you’re on Docker and want a version of nginx that is compiled with both of these things, then don’t use the official nginx image since it’s compiled with version 1.0.1 of WebSSL. You can use the docker image I mentioned before, that has a working http2. That’s the imageg I use for my servers.

Bonus

Redirect www addresses to non-www:

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
# Redirect www to non-www
server {
  listen 443;
  listen [::]:443;
  server_name  www.mujz.ca;

  # 15768000 seconds = 6 months
  add_header Strict-Transport-Security "max-age=15768000" always;
  return 301 https://$host$request_uri;
}

HTML, CSS, & JavaScript

Minification

Depending on how you build your code, find the best way to minify your JS, CSS, and HTML files. Just ask Google about this.

Use srcset with img tags

This attribute allows you to specify different sizes based on the pixel density of the screen your website is being viewed on. Check out the demo.

<img src="/img2000/image.jpg"
  srcset="/img1000/image.jpg 1x,
          /img2000/image.jpg 2x,
          /img4000/image.jpg 4x">

Note that you should still specify an src attribute to maintain browser compatibility.


Did I miss anything? Please let me know and I’ll make sure to add it :)