How I Boost My Mobile PageSpeed From 35 to 96

Three years after starting GadgetReactor, it had become bloated. Google PageSpeed scores were dismal 35 (Mobile) and 42 (Desktop) which translates to a poor user experience for my readers. No one wants to wait for a page to load. So for the last week or so, I dived into some drastic measures to boost my PageSpeed scores and the results have been positive.

The Google PageSpeed insights tool focuses on mostly front end speed improvements, such as compressing and resizing images, removing render blocking javascript / css and optimising delivery of assets. A relatively small amount of weight is given to actual server response time. You can combine this with Pingdom speed test to check more on back end delivery and the overall load time of your website.

Some History to Explain the Initial Slow PageSpeed

Prior to this upgrade, I was running off shared hosting at Bluehost. Server resources (CPU / RAM) are shared with others and the server response itself was rather slow. I had tried a few of the WordPress plugins such as W3TC, Super cache, to see if I could optimise the performance, but it still wasn’t great. I decided to switch to a VPS for hosting instead, since I had free credits at DigitalOcean and it would be good to learn how to administer a VPS server. The DigitalOcean VPS is excellent value at US$5 (20GB SSD, 1 TB Bandwidth, dedicated 1xCPU, and 512 MB RAM), punching above it’s class and even allows geographical customisation by picking your VPS location. Options available are US, UK, Netherlands, Frankfurt and Singapore. Another viable alternative is Amazon AWS, which offers an initial one year free trial for new subscribers.

Backend Configuration

Step 1 : Backup Files On Your Existing WordPress Server

If you have access to phpmyadmin, you can backup the entire database, but I don’t think that’s necessary. I use the built in WordPress export function, which will save your information to an .xml file. User information, biography, settings would not be exported with the tool and you would need to take note of the settings, or you can copy them over later.

Step 2: Configure the new VPS

DigitalOcean has a few pre-setup rigs that you can use as a default. I chose the WordPress app as it comes with a typical LAMP stack (Linux, Apache2, MySQL, PHP) pre-configured. I was contemplating using NGINX over Apache2 but there didn’t seem to be significant difference in performance once you add a cache layer to both. I opted to go for Apache2 due to my familiarity.

DigitalOcean has a bunch of excellent tutorials on initial VPS setup and login, which you would need to use an SSH client like Putty.

Step 3: Transfer Files / Configure WordPress on the new VPS

SSH into your VPS. The initial message would display the user and login password required.

With the provided IP from DigitalOcean, access the IP Address in your web browser to configure up the WordPress server. Follow the setup steps until you reach the Dashboard. This should be familiar to you, so head first to the Appearance tab and install back your existing theme. Take note if you switch themes, certain theme-specific features such as custom post types would not be imported. Same goes for plugins.

Next, for the Settings tab, you should use back the same settings as your original account, except for WordPress Address (URL) and Site Address (URL). Leave them as the IP for now. You should also use back the same thumbnail settings otherwise some images would not load.

With that, you can start the import process. Go to Tools – Import (Install the Plugin) and select the xml export file earlier. Optional: You might have to increase the PHP Upload File Size. My export xml file was 8MB and the default PHP upload file size is capped at 2MB. To do so, return to the SSH window and enter the following command._

nano /etc/php5/apache2/php.ini

Look for these two lines, and update them accordingly.

; Maximum allowed size for uploaded files.
upload_max_filesize = 40M

; Must be greater than or equal to upload_max_filesize
post_max_size = 40M

After you exit and save the file, restart the web server with the following command.

service apache2 restart

Choose to import all uploads and attachments. It would take a while. I had over 3000+ files, and the process took over 15 minutes. Do not navigate away from the website at this stage. You can grab a coffee, or head over to step 4.

Once done, it would list all the files that couldn’t be imported. Save this list so as to check compatibility subsequently.

Step 4: Setup a swap file

As the smallest droplet on DigitalOcean only has 512mb of RAM, I would advise to setup a swap file. You never know when memory would run out and would force apps to be closed. Definitely not something I want in a production server.

You can enter the following commands to check your memory allocation and the presence of a swap file.

free -m
sudo swapon -s

To setup a swap file:

sudo dd if=/dev/zero of=/swapfile bs=1024 count=512k
sudo mkswap /swapfile
sudo swapon /swapfile
sudo swapon -s
echo 10 | sudo tee /proc/sys/vm/swappiness
echo vm.swappiness = 10 | sudo tee -a /etc/sysctl.conf
sudo chown root:root /swapfile
sudo chmod 0600 /swapfile

To load the swapfile when the server reboots, you would need to edit the fstab.

sudo nano /etc/fstab 

Paste in the following line

/swapfile       none    swap    sw      0       0 

Step 5: Configure the VPS to use Varnish

Varnish is a caching HTTP reverse proxy. You install it in front of any server that speaks HTTP and configure it to cache the contents. Varnish Cache is really, really fast. It typically speeds up delivery with a factor of 300 – 1000x, depending on your architecture.

Install varnish.

sudo apt-get update
sudo apt-get install varnish

Edit the varnish default settings.

sudo nano /etc/default/varnish

In the nano editor, look for the section with Alternate 2 without the # infront and edit the port.

DAEMON_OPTS="-a :80 \
-T localhost:6082 \
-f /etc/varnish/default.vcl \
-S /etc/varnish/secret \
-s malloc,256m"

Once you save and exit out of that file, open up the default.vcl file:

sudo nano /etc/varnish/default.vcl

This file tells Varnish where to look for content. Although Apache listens on port 80 by default, we will change the settings for it later. Within this file, we will tell varnish to look for the content on port 8080.

The configuration should like this:

backend default {
    .host = "127.0.0.1";
    .port = "8080";
}

So that’s all for Varnish. Now we need to update Apache. Open up the apache ports file:

sudo nano /etc/apache2/ports.conf

Change the port number from Listen 80 to Listen 8080.

Next, open up the apache2.conf.

sudo nano /etc/apache2/apache2.conf

Look for the line VirtualHost *:80 and change the port number to 8080.

Step 6: Enable Browser Caching

Setting an expiry date or a maximum age in the HTTP headers for static resources instructs the browser to load previously downloaded resources from local disk rather than over the network.

To do so edit your apache2.conf.

sudo nano /etc/apache2/apache2.conf

Add the following chunk in between the VirtualHost settings.

ExpiresActive On
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 application/pdf "access 1 month"
ExpiresByType text/x-javascript "access 1 month"
ExpiresByType application/x-shockwave-flash "access 1 month"
ExpiresByType image/x-icon "access 1 year"
ExpiresDefault "access 2 days"

Turn on the headers and expires modules on Apache2 and restart the server.

a2enmod headers
a2enmod expires
service apache2 restart
service varnish restart

Step 7: Configure WordPress DigitalOcean to send emails

I had a hard time sorting this out. The WordPress droplet from Digitalocean comes with postfix installed but you just need to configure it.

sudo nano /etc/postfix/main.cf

Update the following variables, myhostname, and mydestination. You would need to leave the mydestination blank so that the emails would not be routed internally and handled by your external mailserver.

myhostname = gadgetreactor.com
mydestination = 

Reload postfix and server.

/etc/init.d/postfix reload
service postfix restart
service apache2 restart
service varnish restart

Now with that all done, the backend configuration is done and you can return to WordPress settings to update your site URL. Also update your name servers and DNS A records as necessary. Your new site should be up and running after the DNS cache updates.

Frontend Speed Improvements

With the above, my Pagespeed score went up to around 80+ on Desktop but was still low at 60+ on Mobile. The usual problems were ‘Eliminate render-blocking JavaScript and CSS in above-the-fold content’ and ‘Optimise Images’.

Step 8: Shift Javascript and CSS to Footer

To eliminate the render-block Javascript and CSS in above the fold content, you would need to shift your CSS and Javascript requests to the footer.

You can head to the theme editor and configure your functions.php.

WordPress itself loads jquery and you would need to add this to your functions.php to load the jquery loading call to the footer. The register and enqueue script accepts an argument which is is_footer=true.

if( !is_admin()){
    wp_deregister_script('jquery');
    wp_register_script('jquery', ("http://ajax.googleapis.com/ajax/libs/jquery/2.1.3/jquery.min.js"), false, '2.1.3', true);
    wp_enqueue_script('jquery');
}

Similarly, for your theme scripts, look for the lines wp_enqueue_script and add true as the sixth argument. Check the requirements for wp_enqueue_script over at WordPress.

For CSS, there isn’t a similar argument so I transferred the script calls to footer.php. For lines that start with wp_enqueue_style, cut them from the functions.php and paste them in a <?php block in footer.php before wp_footer().


// Add Bootstrap default CSS
wp_enqueue_style( 'gadgetreactor-bootstrap', get_template_directory_uri() . '/inc/css/bootstrap.min.css' );
// Add Font Awesome stylesheet
wp_enqueue_style( 'gadgetreactor-icons', get_template_directory_uri().'/inc/css/font-awesome.min.css' );
// Add main theme stylesheet
wp_enqueue_style( 'gadgetreactor', get_stylesheet_uri() );

wp_footer(); 

You can also choose to remove Jetpack CSS. Add this to functions.php.

// First, make sure Jetpack doesn't concatenate all its CSS
add_filter( 'jetpack_implode_frontend_css', '__return_false' );

// Then, remove each CSS file, one at a time
function jeherve_remove_all_jp_css() {
  wp_deregister_style( 'AtD_style' ); // After the Deadline
  wp_deregister_style( 'jetpack_likes' ); // Likes
  wp_deregister_style( 'jetpack_related-posts' ); //Related Posts
  wp_deregister_style( 'jetpack-carousel' ); // Carousel
  wp_deregister_style( 'grunion.css' ); // Grunion contact form
  wp_deregister_style( 'the-neverending-homepage' ); // Infinite Scroll
  wp_deregister_style( 'infinity-twentyten' ); // Infinite Scroll - Twentyten Theme
  wp_deregister_style( 'infinity-twentyeleven' ); // Infinite Scroll - Twentyeleven Theme
  wp_deregister_style( 'infinity-twentytwelve' ); // Infinite Scroll - Twentytwelve Theme
  wp_deregister_style( 'noticons' ); // Notes
  wp_deregister_style( 'post-by-email' ); // Post by Email
  wp_deregister_style( 'publicize' ); // Publicize
  wp_deregister_style( 'sharedaddy' ); // Sharedaddy
  wp_deregister_style( 'sharing' ); // Sharedaddy Sharing
  wp_deregister_style( 'stats_reports_css' ); // Stats
  wp_deregister_style( 'jetpack-widgets' ); // Widgets
  wp_deregister_style( 'jetpack-slideshow' ); // Slideshows
  wp_deregister_style( 'presentations' ); // Presentation shortcode
  wp_deregister_style( 'jetpack-subscriptions' ); // Subscriptions
  wp_deregister_style( 'tiled-gallery' ); // Tiled Galleries
  wp_deregister_style( 'widget-conditions' ); // Widget Visibility
  wp_deregister_style( 'jetpack_display_posts_widget' ); // Display Posts Widget
  wp_deregister_style( 'gravatar-profile-widget' ); // Gravatar Widget
  wp_deregister_style( 'widget-grid-and-list' ); // Top Posts widget
  wp_deregister_style( 'jetpack-widgets' ); // Widgets
}
add_action('wp_print_styles', 'jeherve_remove_all_jp_css' );

Step 9: Minify and Compress Base CSS / JS Files

If you use Bootstrap, the following resource might come in handy. Boostrap is commonly used web frontend development framework but it is rather bloated. If you know what features you require, you can use a customised boostrap package from getboostrap.

Other popular resources to minify your CSS and JS are CSS Minifier and JS Mini.

Step 10: Compress Images Losslessly

You would need two tools – so install them with sudo apt-get install optipng and jpegotim. Optipng runs quite slow so do try to load new images as jpegs in future. I would suggest you do this as a script.

#!/bin/bash

Add this to the editor:

echo `date` >> /root/optipng.log
find /var/www/ -mtime -2 -iname '*.png' -print0 | \
 xargs -0 optipng -o7 -log /root/optipng.log -preserve
echo `date` >> /root/jpegoptim.log
find /var/www/ -mtime -2 -iname '*.jpg' -print0 | \
 xargs -0 jpegoptim --max=80 --preserve --totals >> /root/jpegoptim.log

Then make this into a cronjob crontab -eby adding a line to cron:

# m h dom mon dow command
0 1 * * * /root/optimize-images.sh

Conclusion

Whew, that was a long post. This post is by no means comprehensive. It was more of an introduction into what’s necessary to boost your page speed. Start with a clean, fast install from the beginning and it would be a good base to work on subsequently. Hopefully, things wouldn’t slow down too much in future as I add more features. I will be heavily reliant on PageSpeed to check the overall performance of my website from time to time as I add new features.

Sean

Sean is a tech geek and star wars fan. He loves playing with new gadgets, writing little code snippets in Python, JS, and dabbling with Android programming from time to time, while keeping a lookout for the next biggest happening in the world of tech!

Leave a Reply