Speed up your website

Introduction

This is small tutorial that covers some tips how you can improve the performance of your website. Many of these steps include tasks that can be handled by a build tool (I use Grunt) so I recommend you using a build tool like that. You can test your sites current performance by using a tool like this one or this one.

Don’t use images when it’s not needed

In the earlier days of web development images were used for all sorts of things; buttons, icons, gradients, backgrounds and even texts for displaying headings and logotypes that required specific fonts. With modern day web technlogies though you can easily recreate all of these things without the use of image files.

  • Buttons: CSS3 introduced lots of interesting attributes that can create effects that could previously only could be achieved by rendering a image in for instance Photoshop and then using the img-tag. Attributes like border-radius for rounded corners, box-shadow for drop shadow effects, text-shadow for text shadow effects, text-stroke for text outline as well as a bunch of neat CSS animations for click/hover/focus events. You can use a library like Bootstrap to get some decent looking default buttons. Here is a nice codepen as an example of what can be achieved with HTML and CSS only.
  • Icons: Instead of using images for small icons consider using a glyph icon library like Bootstrap icons or Fotn awesome. It’s better for performance and also scales better.
  • Gradients: If for whatever reason you wan’t to use gradients you can use a gradient css generator like this one to generate css gradients.
  • Backgrounds: It is possible to create a lot of quite impressive background patterns using CSS only, here are a few examples of these.
  • Texts: With CSS3 you can use custom fonts for texts, so there is no need to create images for headings and logos. Here is a nice tutorial about this. You may also consider using a convenient service like Google fonts for this. Here are a few examples of impressive typographies created using CSS only; Example 1, Example 2, Example 3, Example 4, Example 5, Example 6, Example 7, Example 8

Optimize images

There are obviously still some instances where you must use images, for instance when using photos for hero components and whatnot. When using image files you should remember to optmizie them. You can do this by using a simple service like Optimizilla. You’ll be surprised how much space you can save by optmizing images while not seemingly losing any photo quality at all.

Minify JS

I use the build tool Grunt and the plugin uglify to minify, compress and uglify all my Javascript code. This will reduce the file size making it load faster. Here’s how to set it up using Grunt. First install the plugin (this assumes you already have npm and Grunt setup):

npm install grunt-contrib-uglify --save-dev

Now configure your Gruntfile.js:

module.exports = function(grunt) {
    grunt.initConfig({
        pkg: grunt.file.readJSON('package.json'),
        uglify: {
            options: {
                banner: '/*! My compressed and uglified JS | <%= grunt.template.today("yyyy-mm-dd") %> */\n'
            },
            build: {
                files: [{
                    src: 'js/**/*.js',
                    dest: 'build/main.min.js'
                }]
            }
        }
    });
    grunt.loadNpmTasks('grunt-contrib-uglify');
    grunt.registerTask('default', [
        'uglify'
    ]);
};

Now you can compress and uglify your Javascript by just running the default Grunt build task. This will take all js-files (recursively) in your js-folder and compile them into one large js-file. The output will be located in build/main.min.js.

Minify CSS

Minifying CSS is also recommended. I recommend the Grunt plugin grunt-contrib-cssmin. Install it by running:

npm install grunt-contrib-cssmin --save-dev

Personally I don’t use this plugin. Instead I am using the plugin “grunt-contrib-less” (since I am using less as a css postprocessor). This plugin will not only transpile less to css but also minify. However if you are not using less you can use grunt-contrib-cssmin. I add the following configuration to my Gruntfile.js:

module.exports = function(grunt) {
    grunt.initConfig({
        pkg: grunt.file.readJSON('package.json'),
        uglify: {
            options: {
                banner: '/*! My compressed and uglified JS | <%= grunt.template.today("yyyy-mm-dd") %> */\n'
            },
            build: {
                files: [{
                    src: 'js/**/*.js',
                    dest: 'build/main.min.js'
                }]
            }
        },
        cssmin: {
            minify: {
                src: 'css/default.css',
                dest: 'build/default.min.css'
            }
        }
    });
    grunt.loadNpmTasks('grunt-contrib-uglify');
    grunt.loadNpmTasks('grunt-contrib-cssmin');
    grunt.registerTask('default', [
        'uglify',
        'cssmin'
    ]);
};

This will take the file css/default.css and minify it, the output will be located in build/default.min.css.

Lazy load CSS

If your website uses very large CSS-files you might wan’t to consider lazy loading your CSS. Or perhaps you could break up your css into a default stylesheet file that can be included normally and one that can be lazyloaded. Simply lazy load css by using the following script:

<script>
var cb = function() {
    var l = document.createElement('link');
    l.rel = 'stylesheet';
    l.href = 'yourCSSfile.css';
    var h = document.getElementsByTagName('head')[0];
    h.parentNode.insertBefore(l, h);
};
var raf = requestAnimationFrame || mozRequestAnimationFrame ||
    webkitRequestAnimationFrame || msRequestAnimationFrame;
if (raf) raf(cb);
else window.addEventListener('load', cb);
</script>

Inline CSS

Another way to speed up your site is to simply inline you CSS. This means that instead of referencing a separate css-file you paste the full css in a style-tag within the head-tag. By doing this your browser won’t have to download the css-file and it reduces the amount of HTTP requests your browser have to make. Obviously it’s not very convenient having to constantly copypaste your css to your markup so I suggest using the grunt plugin grunt-replace for inlining CSS on every build. Simply run:

npm grunt-replace --save-dev

Then modify your Gruntfile.js file so that it looks a bit like this:

module.exports = function(grunt) {
    grunt.initConfig({
        pkg: grunt.file.readJSON('package.json'),
        uglify: {
            options: {
                banner: '/*! My compressed and uglified JS | <%= grunt.template.today("yyyy-mm-dd") %> */\n'
            },
            build: {
                files: [{
                    src: 'js/**/*.js',
                    dest: 'build/main.min.js'
                }]
            }
        },
        cssmin: {
            minify: {
                src: 'css/default.css',
                dest: 'build/default.min.css'
            }
        },
        replace: {
            inline: {
                options: {
                    patterns: [{
                        match: 'defaultCss',
                        replacement: '<%= grunt.file.read("build/default.min.css") %>'
                    }]
                },
                files: [{
                    src: ['head.html'],
                    dest: 'build/head.html'
                }]
            }
        }
    });
    grunt.loadNpmTasks('grunt-contrib-uglify');
    grunt.loadNpmTasks('grunt-contrib-cssmin');
    grunt.loadNpmTasks('grunt-replace');
    grunt.registerTask('default', [
        'uglify',
        'cssmin',
        'replace'
    ]);
};

In your markup instead of referencing the CSS-file normally instead write:

<style>
    @@defaultCss
</style>

Now everytime you run the default Grunt build command (by simply executing ‘grunt’ in your terminal) the placeholder variable ‘@@defaultCss’ in head.html will be replaced with the contents of your default.min.css file (in this case located in build/css/). The output file will be located in build/head.html. Note how I am here using the plugins cssmin and replace together. It’s therefore important to run cssmin BEFORE replace as it is cssmin that will generate the minified CSS output.

Server cache of static files

The Apache configuration

Make it so that static files (javascript, css and images) are cached for a longer duration so that they don’t have be fetched from the server on every new page request. This one is a bit trickier as it requires some server configuration (I am using Apache) and you may not have access to the server depending on what webhost you are using, if any. Regardless, this is my fix.

First you must enable the Apache mod “expires” by running:

a2enmod expires

Now restart your server for the change to take effect.

/etc/init.d/apache2 restart

Now that the expires mod is enabled I can configure my settings for the mod by adding it to my .htaccess file in my site root folder.

<IfModule mod_expires.c>
    ExpiresActive On
    ExpiresDefault "access plus 1 month"
    ExpiresByType image/jpg "access 1 year"
    ExpiresByType image/jpeg "access 1 year"
    ExpiresByType image/gif "access 1 year"
    ExpiresByType image/png "access 1 year"
    ExpiresByType text/css "access 1 month"
    ExpiresByType text/x-javascript "access 1 month"
</IfModule>

You can use whatever expiration time you like, learn more about this mod here.

Naturally there’s an obvious problem with this, what if we make changes to static files which we most certainly will if we regularly update the site? There’s a solution to this which once again involves Grunt.

Grunt job for version-tagging static files

Now I will install the Grunt plugins grunt-filerev, grunt-filerev-replace, grunt-contrib-clean and grunt-contrib-copy. Run:

npm install grunt-filerev grunt-filerev-replace grunt-contrib-copy grunt-contrib-clean --save-dev

Now modify your Gruntfile a bit like this:

module.exports = function(grunt) {
    grunt.initConfig({
        pkg: grunt.file.readJSON('package.json'),
        uglify: {
            options: {
                banner: '/*! My compressed and uglified JS | <%= grunt.template.today("yyyy-mm-dd") %> */\n'
            },
            build: {
                files: [{
                    src: 'js/**/*.js',
                    dest: 'build/main.min.js'
                }]
            }
        },
        cssmin: {
            minify: {
                src: 'css/default.css',
                dest: 'build/default.min.css'
            }
        },
        replace: {
            inline: {
                options: {
                    patterns: [{
                        match: 'defaultCss',
                        replacement: '<%= grunt.file.read("build/default.min.css") %>'
                    }]
                },
                files: [{
                    src: ['build/head.html'],
                    dest: 'build/head.html'
                }]
            }
        },
        filerev: {
            options: {
                algorithm: 'md5',
                length: 8
            },
            images: {
                src: 'build/**/*.{jpg,jpeg,gif,png}'
            },
            js: {
                src: ['build/main.min.js']
            }
        },
        filerev_replace: {
            options: {
                assets_root: './'
            },
            compiled_assets: {
                src: [
                    'build/main.min.js',
                    'build/default.min.css',
                    'build/head.html'
                ]
            }
        },
        copy: {
            main: {
                files: [{
                    src: 'img/*',
                    dest: 'build/',
                    expand: true,
                    flatten: true
                }, {
                    src: 'head.html',
                    dest: 'build/head.html'
                }]
            },
        },
        clean: {
            files: ['build/*']
        }
    });

    grunt.loadNpmTasks('grunt-contrib-uglify');
    grunt.loadNpmTasks('grunt-contrib-cssmin');
    grunt.loadNpmTasks('grunt-replace');
    grunt.loadNpmTasks('grunt-contrib-copy');
    grunt.loadNpmTasks('grunt-filerev');
    grunt.loadNpmTasks('grunt-filerev-replace');
    grunt.loadNpmTasks('grunt-contrib-clean');

    grunt.registerTask('default', [
        'clean',
        'copy',
        'uglify',
        'filerev',
        'cssmin',
        'filerev_replace',
        'replace'
    ]);
};

The grunt plugin “filerev” will generate new files from the files specified, in this scenario image files and our js-file (main.min.js), however an MD5 hash will be generated based on the content of the file and appended to the file name. For instance a file named background.png would become background.86182fa8.png. If the files are changed the generated hash that is appended to the filename will also change. This means that everytime a file, for instance the compiled js-file is changed, it will no longer be cached on the server since the filename is changed and thus interpreted as a new file. By using this we can safely cache static files on the server.

The plugin “filerev_replace” will go through the specified files (main.min.js and default.min.css) and replace all references to the original files (like background.png) and change it to the newly generated hashed files (like background.86182fa8.png).

I also added the plugins clean and copy for cleaning previously generated build files and also copying files to be hashed to the build-folder. Here is a full explanation of the flow of tasks:

  • clean: Remove all previously built files from the build-folder before doing anything else.
  • copy: Copy all images from the img-folder to the build-folder. The reason is that we will later use filerev for the images in the build folder to generate hashed version but we want to let the original images remain. It is the hashed files in the build-folder that will be used, so references to images in css- and js-files should reference for instance build/background.png. The filerev_replace will then modify these references to build/background.86182fa8.png. We also copy the head.html file (this is a file that contains all tags in the head-tag, this file can then be included in the index.html file).
  • uglify: Minify, uglify and compress all js files from the js-folder and compile it into one js-file, build/main.min.js.
  • filerev: Generate hashed versions of all the images in the build folder and our js-file, build/main.min.js.
  • cssmin: Compress and minify css, output will be located in build/default.min.css.
  • filerev_replace:: Replace all references to images in main.min.js and default.min.css. As you can see build/head.html is also included, this is because it includes the reference to main.min.js which will be hashed.
  • replace: Replace the @@defaultCss placeholder variable in build/head.html and replace it with the content of the default.min.css file. This will inline the css. This is the reason we didn’t include css in the filerev task, because the css is inlined in the markup in this step anyway, it will not be a separate file that the server has to request.

This is about the same Grunt configuration I use on my site and I’m pretty happy with it. I managed to improve the performance of the site a lot by setting this up.

Testing server caching

To test that the files specified in the Apache configuration are now cached properly you can download and install the plugin Live HTTP Headers for Chrome or the Firefox version. Start the plugin and enter your site, watch the server file requests in the Live HTTP Headers plugin and you should now be able to see that image-files and js-files now have an Expires-header and a Cache-Control-header.

Asynchronus js-files

The async-attribute is a new addition to HTML5 and it’s an easy solution to load external javascript-files asynchronously. Note that this is not supported by older browsers like (IE8). Here’s how it looks:

<script async src="build/main.min.js"></script>

The end!

I hope you enjoyed this tutorial. There are more things I could mention regarding website performance but I think I covered the essentials, perhaps one day I will write a part 2 to this tutorial. 🙂

Advertisement

Make your own Raspberry Pi Webserver

Introduction

Do you wan’t to have your very own webserver where you can host your site without the need for third party hosting services that offers limited control of the server configuration? This tutorial will teach you how to setup your own webserver from scratch on a Raspberry Pi! A Raspberry Pi is a fully functional onecard computer with USB, HDMI output, ethernet-port, sound-output and even an IO-interface. All this for just around 40€.

Advantages & downsides

The advantages of this are obvious; it’s cheap, fast and easy to manage. The downsides comes with the hardware limitations of the Raspberry Pi itself. Obviously you get what you pay for, and a Raspberry Pi does not have the same CPU or disk speed capabilities as a regular server, which makes it inadequate for hosting any larger site with lots of heavy backend logic. For instance a regular server that used a SSD drive usually have a read speed of ~550Mb/s while a micro SD card uses clocks in at around 80Mb/s. But for a simpler site it is good enough, a website that is not too heavy will hardly be affected by this anyway. You may also just consider this tutorial for educational purposes as it pretty much covers the basics of setting up a public Linux distribution webserver.

What you need

The recommended version is the new Raspberry Pi 2 which has a much better CPU than the regular Raspberry Pi B+. An important thing to note is that the Raspberry Pi 2 does not come with any accessories, which mean you have to buy a separate micro USB cable (used for power) and a micro SD card (which is used instead of a harddrive). You also need a USB cardreader if you do not already have a computer that can read micro SD cards. I assume that you already a keyboard and a monitor 🙂

Get started

Setup the OS

1. Download a copy of the distribution you wan’t to use. I am using Raspbian Wheezy which can be found here: https://www.raspberrypi.org/downloads/raspbian/. Do not use Raspbian Jessie as this is meant for desktop use, unnecessary for a server.

2. Extract the image to your micro SD-card. This can be done for instance with the software “Win32 Disk Imager” which can be found here: http://sourceforge.net/projects/win32diskimager/

3. Put the micro SD-card into your Raspberry Pi. Plug in a keyboard and use a HDMI-cable to connect to a monitor or TV. Plug your Raspbery Pi into your router. Lastly plug in the micro USB-cable for power which will turn the Raspberry on.

4. After the OS have finished loading you will be prompted to login. The default user is named “pi” and the default username is “raspberry”. Login with this user.

5. Change password for your used by using the command

passwd pi

6. Enter your new password and then confirm it.

Update and upgrade your OS

1. To make sure your dist is up to date run the following commands:

sudo dpkg-reconfigure tzdata
sudo apt-get update
sudo apt-get upgrade

2. Configure the clock by using the following command (replace parts as necessary):

sudo date --set="30 December 2013 10:00:00"

Keep the Raspberry Pi firmdate up to date

1. Run the following commands

sudo apt-get install ca-certificates
sudo apt-get install git-core
sudo wget https://raw.github.com/Hexxeh/rpi-update/master/rpi-update  -O /usr/bin/rpi-update && sudo chmod +x /usr/bin/rpi-update
sudo rpi-update

2. Restart the Raspberry

sudo shutdown -r now

Setup SSH

Obviously it is not very practical to have to manage your webserver by working with it directly. By setting up SSH on your Raspberry you can connect from another computer and work remotely.

1. Type the command

ifconfig

You should see something that looks like this:

eth0  Link encap:Ethernet  HWaddr b8:27:XX:XX:XX:XX
      inet addr:192.168.XXX.XXX  Bcast:192.168.XXX.XXX  Mask:255.255.255.0
      UP BROADCAST RUNNING MULTICAST  MTU:1500  Metric:1
      RX packets:88025091 errors:0 dropped:0 overruns:0 frame:0
      TX packets:107766303 errors:0 dropped:0 overruns:0 carrier:0
      collisions:0 txqueuelen:1000
      RX bytes:1275557050 (1.1 GiB)  TX bytes:1407571910 (1.3 GiB)

In this case the IP-address of the Raspberry Pi is 192.168.XXX.XXX. This means that you can connect via SSH to this IP from another computer. Do note though that this is a local IP, so you can only connect to it via a computer on the same network.

2. Enable SSH:

sudo /etc/init.d/ssh start

3. Restart your Raspberry:

sudo shutdown -r now

4. Now you can connect to your Raspberry from another computer! You can unplug monitor and keyboard from your Raspberry. Open up a terminal on your regular computer and connect to your Raspberry:

ssh pi@192.168.XXX.XXX

If you are using Windows you can use a ssh client like PuTTy. Personally though I prefer to use Git Shell which comes which “Github for Windows” (can be downloaded here: https://desktop.github.com/). Git Shell is an extension of Windows Powershell which can be used pretty much like a Linux terminal.

Installing the webserver

Now that you are connected via SSH to your Raspberry it is time to setup the webserver itself. In this tutorial I will be using Apache webserver with php, however you are obviously free to use whatever webserver you want.

1. Install using the following command:

sudo apt-get install apache2 php5 libapache2-mod-php5

2. Restart the service:

sudo /etc/init.d/apache2 restart

3. Now that Apache is running you should be able to access it via a browser with the IP: 192.168.XXX.XXX. You should see a simple page that says “It Works!”.

Your webserver is now and up running. The following steps will go through additional configuration you may or may not want depending on your needs.

Advanced (sort of) configuration

Access the web

What good is a webserver if you cant access it from the internet? If you have followed the tutorial up to this point you have a fully functional Apache webserver running on a Raspbian Wheezy distribution with support for php. It can be accessed when working on the same local network but is not yet suited for hosting any open website. In this section I will cover how to fix this.

There are several different solutions for this but the easiest one is to just redirect traffic to your Raspberry Pi by configuring your router that the Raspberry is connected to.

1. Login to your routers web interface

2. Look for “port forwarding” in the menus. Where to find this depends on what router you are using but it is usually quite easy to find.

3. Setup rules for your router to redirect traffic on port 80 (HTTP) and 443 (HTTPS) to 192.168.XXX.XXX

You should now be able to access your website in /var/www/ by entering the public IP of your router in a browser. You should be able to find your routers public IP somewhere from the routers web interface.

Connect with a domain name

Now that you can access your website via your routers public IP you might wan’t to connect a domain name to this IP for easier access. Obviously you have to buy a domain name from a site such as godaddy.com. When you own a domain, mydomain.com, you should configure the DNS settings for the domain name as follow:

    Subdomain    Type    IP
    ----------------------------------------
    wwww         A       

Dynamic DNS

If you’ve come this far you now your domain name mydomain.com now successfully leads to your website hosted on your server. But a problem with this setup is that the DNS configuration is static. If the IP of your router changes, which it most likely will for instance when restarting it, you will no longer be able to access your website via your domain name without manually updating the DNS configuration.

To fix this you should use Dynamic DNS. Make sure that the site from where you bought your domain name offers this service. For some sites, such as binero, you have to pay extra to enable this. With a dynamic DNS service, such as DynDNS, you can update the IP of your domain name via a HTTP request which can be done from a shellscript.

1. First of all, create the file for the script:

mkdir /home/pi/dyndns
touch /home/pi/dyndns/dyndns.sh

2. Edit the contents of the file:

nano /home/pi/dyndns/dyndns.sh

Add the following content:

#!/bin/bash

curl --user [username]:[password] [dyndns service update url]?hostname=www.mydomain.com&myip=

curl --user [username]:[password] [dyndns service update url]?hostname=mydomain.com&myip=

You should edit the contents as suitable. Exactly how the URL is structured for instance varies a bit depending on what dynamic DNS service you are using. By leaving the “myip” parameter value empty the target IP will automatically be set. The username and password should be for the domain service you are using such as godaddy.com. You should test out that the script is working by running:

sh /home/pi/dyndns/dyndns.sh

3. Assuming your script is working and is updating your DNS configuration correctly you can now create a cronjob for this script. A cronjob is a scheduled job or action that the OS handles, much like “task scheduler” in Windows. Run:

crontab -e

4. Scroll down to the end and add the following line:

*/5 * * * * sh /home/pi/dyndns/dyndns.sh

Save and close. This will make it so that the dyndns.sh script is run every 5 minutes automatically. You may change the interval to suit your own preference.

Now that you have a domain name with dynamic DNS setup you may from now access your server from any other computer with the following command:

ssh pi@mydomain.com

Free alternatives for domain and dynamic DNS

The solution described above requires that you pay for a domain name and a dynamic DNS service. However there are free alternatives that I can recommend, such as http://www.noip.com/ and https://www.duckdns.org/. With these services you may get a free domain name like mydomain.no-ip.org or mydomain.duckdns.org.

Several websites on same server

Perhaps you wan’t to host more than one website on the same server. For instance on my Raspberry Pi server I have several websites hosted, each with it’s own folder in /var/www and all use the same dynamic DNS service which means the same IP is assigned for each site. So how does the DNS setup work if they all point at the same IP? My way to solve this is to setup some rules in your Apache configuration.

Let’s say you have the websites website1.com and website2.org. They are located on the server in the folders /var/www/website1 and /var/www/website2. To make this work the configuration file for Apache found in /etc/apache2/sites-available/default should be configured to contain:

<VirtualHost *:80>
    ServerAdmin webmaster@website1.com
    ServerName *.website1.com
    ServerAlias website1.com *.website1.com
    DocumentRoot /var/www/website1

    <Directory />
        Options FollowSymLinks
        AllowOverride All
    </Directory>
    <Directory /var/www/website1>
        Options Indexes FollowSymLinks MultiViews
        AllowOverride All
        Order allow,deny
        allow from all
    </Directory>
    ScriptAlias /cgi-bin/ /usr/lib/cgi-bin/
    <Directory "/usr/lib/cgi-bin">
        AllowOverride None
        Options +ExecCGI -MultiViews +SymLinksIfOwnerMatch
        Order allow,deny
        Allow from all
    </Directory>
    ErrorLog ${APACHE_LOG_DIR}/error.log
    CustomLog ${APACHE_LOG_DIR}/access.log combined
    ErrorDocument 404 "/var/www/website1/404.html"
</VirtualHost>

<VirtualHost *:80>
    ServerAdmin webmaster@website2.org
    ServerName *.website2.org
    ServerAlias website2.org *.website2.org
    DocumentRoot /var/www/website2

    <Directory />
        Options FollowSymLinks
        AllowOverride All
    </Directory>
    <Directory /var/www/website2>
        Options Indexes FollowSymLinks MultiViews
        AllowOverride All
        Order allow,deny
        allow from all
    </Directory>
    ScriptAlias /cgi-bin/ /usr/lib/cgi-bin/
    <Directory "/usr/lib/cgi-bin">
        AllowOverride None
        Options +ExecCGI -MultiViews +SymLinksIfOwnerMatch
        Order allow,deny
        Allow from all
    </Directory>
    ErrorLog ${APACHE_LOG_DIR}/error.log
    CustomLog ${APACHE_LOG_DIR}/access.log combined
    ErrorDocument 404 "/var/www/website2/404.html"
</VirtualHost>

With this Apache will know where to get content from depening on the URL that the user visits.

Now all you have to do is to restart your Apache server:

sudo /etc/init.d/apache2 restart

Overclock your Raspberry Pi (Optional)

It is possible to overclock the CPU of your Raspberry Pi using the built in options menu. I have not benchmarked in any way to see how this may benefit server response times so I cannot tell you how much you gain from this (if at all) but it most likely has to with what you do with your server.

1. Run the command:

sudo raspi-config

2. Go to “Overclock”

3. Choose how much you wan’t to overclock. Note that you do this at your own risk.