Scoring 100 on Google’s PageSpeed Insights with Jekyll

To get you a score of 100 on Google’s PageSpeed Insights you have to optimize a lot of stuff. Some things can easily be done via Cloudflare. For some others you need to do further manual optimizations. Here gulp automation comes in handy.

Steve Edson did a great job to get you started with gulp (and Jekyll). Take a look at Automating optimisations with Gulp. It covers a lot of optimizations to bring you close to 100.

I will show you some more things to get you right to that warm and fuzzy green bar feeling you may already know from testing tools.

Inline CSS

Google’s PageSpeed Insights requires eliminating of render-blocking CSS in above-the-fold content. That is achivable through including css styles directly into the html head. Because we are using uncss to reduce all css styles to only the ones which are actually used, we get a very small amount of styles. For this page the raw amount of css is about 120kb. Reduced it’s about almost 8kb. And if we compress it it’s even smaller.

To replace the reference of the css file in our html files with the actuall content of that css file we can do the following:

var gulp = require('gulp');
var replace = require('gulp-replace');
var fs = require('fs');
gulp.task('include-css', function() {
return gulp.src('_site/**/*.html')
.pipe(replace(/<link href=\"\/css\/main.css\"[^>]*>/, function(s) {
var style = fs.readFileSync('_site/css/main.css', 'utf8');
return '<style>\n' + style + '\n</style>';
}))
.pipe(gulp.dest('_site/'));
});

Google Analytics

Inserting your typicall Google Analytics snippet will result in fetching a script which is cachable for only 2 hours. Funny thing is: using a Google product will prevent you from getting to 100 because PageInsights nags about “Leverage browser caching”.

So what do we do about this? On each deployment we download a fresh version of the analytics.js script. In our html files we include our local analytics.js file instead of Google’s remote file. And afterwards either your own server settings for cache expiration or Cloudflare will set a high expiration time so that PageSpeed Insights won’t bug you again.

var gulp = require('gulp');
var download = require('gulp-download');
gulp.task('fetch-newest-analytics', function() {
return download('https://www.google-analytics.com/analytics.js')
.pipe(gulp.dest('assets/'));
});

Minimize number of used icons in icons-font

Current css frameworks like Twitter Bootstrap or Zurp’s Foundation use icon-font files. Awesome for development, bad for production. During development you can quickly insert an icon and continue your work. But for production you only need a fraction of all available icons. Here Fontellocomes in handy. You choose which icons you want to use out of many available icon collections and get customized font files.

Cloudflare

You still have to do many optimizations like special cache headers and compression. You can either modify your webserver configs, and there are plenty of good tutorials out there. Or you can setup Cloudflare and get many optimizations out of the box, like cache headers for expiration, compression, mobile optimizations, a CDN for your assets etc.

The whole gulp script

You can download, modify and use my current gulp optimization automation and deployment script here: github.com/dimitri-koenig/jekyll-gulp-optimizations

var config = require('./gulpconfig.json');
var gulp = require('gulp');
var shell = require('gulp-shell');
var minifyHTML = require('gulp-minify-html');
var cloudflare = require('gulp-cloudflare');
var runSequence = require('run-sequence');
var autoprefixer = require('gulp-autoprefixer');
var uncss = require('gulp-uncss');
var minifyCss = require('gulp-minify-css');
var imagemin = require('gulp-imagemin');
var pngquant = require('imagemin-pngquant');
var jpegtran = require('imagemin-jpegtran');
var gifsicle = require('imagemin-gifsicle');
var replace = require('gulp-replace');
var fs = require('fs');
var download = require('gulp-download');
gulp.task('jekyll', function() {
return gulp.src('index.html', { read: false })
.pipe(shell([
'jekyll build'
]));
});
gulp.task('optimize-images', function () {
return gulp.src(['_site/**/*.jpg', '_site/**/*.jpeg', '_site/**/*.gif', '_site/**/*.png'])
.pipe(imagemin({
progressive: false,
svgoPlugins: [{removeViewBox: false}],
use: [pngquant(), jpegtran(), gifsicle()]
}))
.pipe(gulp.dest('_site/'));
});
gulp.task('optimize-css', function() {
return gulp.src('_site/css/main.css')
.pipe(autoprefixer())
.pipe(uncss({
html: ['_site/**/*.html'],
ignore: []
}))
.pipe(minifyCss({keepBreaks: false}))
.pipe(gulp.dest('_site/css/'));
});
gulp.task('optimize-html', function() {
return gulp.src('_site/**/*.html')
.pipe(minifyHTML({
quotes: true
}))
.pipe(replace(/<link href=\"\/css\/main.css\"[^>]*>/, function(s) {
var style = fs.readFileSync('_site/css/main.css', 'utf8');
return '<style>\n' + style + '\n</style>';
}))
.pipe(gulp.dest('_site/'));
});
gulp.task('fetch-newest-analytics', function() {
return download('https://www.google-analytics.com/analytics.js')
.pipe(gulp.dest('assets/'));
});
gulp.task('rsync-files', function() {
return gulp.src('index.html', { read: false })
.pipe(shell([
'cd _site && rsync -az --delete . ' + config.remoteServer + ':' + config.remotePath
]));
});
gulp.task('purge-cache', function() {
var options = {
token: config.cloudflareToken,
email: config.cloudflareEmail,
domain: config.cloudflareDomain
};

cloudflare(options);
});
gulp.task('raw-deploy', function(callback) {
runSequence(
'jekyll',
'rsync-files',
'purge-cache',
callback
);
});
gulp.task('dry-run', function(callback) {
runSequence(
'fetch-newest-analytics',
'jekyll',
'optimize-images',
'optimize-css',
'optimize-html',
callback
);
});
gulp.task('deploy', function(callback) {
runSequence(
'fetch-newest-analytics',
'jekyll',
'optimize-images',
'optimize-css',
'optimize-html',
'rsync-files',
'purge-cache',
callback
);
});