Improving ‘time to first load’ and generating critical CSS for Drupal and WordPress sites

This document is a work in progress….

One thing that we want to fix is ‘time to first load’. As you probably know, people are recommending to put as little in the way of initial loading as possible, and zero external requests. This means no blocking JS in head, and as little CSS as possible, placed inline instead of in an external file – the so-called ‘critical.css’

Our situation was very much the opposite. Most of our sites are on Drupal or WordPress, with quite a few plugins, which generate a lot of CSS and JS. For wordpress, we often use a theme called Divi, which is quite powerful, but it generates a LOT of css and js, and a lot of it we don’t need.

We started by working on CSS, and then moving onto JS.

CSS

  • Using a web inspector, like Firebug or Safari’s developer tools, take a look at your <head> element. Copy all of the internal CSS files and inline CSS you see there into a single stylesheet (External css, like webfont importing, leave for the moment). If a css file has an @import statement from another file, you have to replace the @import statement with the contents of that file. For example – our WordPress main theme style.css has an import statement from Divi style.css. You will now have a VERY big single css file – lets call it style1.css
  • There are some tools you can use to make the CSS file a bit more manageable. I would recommend first to use a tool like CSS Compressor with Moderate settings so that the file is still readable.
    Then, as much as you can, you can set about removing unused css and combining ‘duplicate’ and overriding css statements that were formerly in different files. You dont need to aim for absolute precision here; if you are not sure about some css, you can just leave for later. This applies especially if you are not familiar with the theme. There are some command line tools and paid online tools that can help you with removing unused and duplicate CSS.
  • Now that you have a single file, you can use a free critical css generator, which will generate some critical css. You should save this into a seperate file called critical.css
  • Then you remove all of the css out of style1.css that is already inside critical.css. Diffmerge for Mac helped with this: you can export a ‘diff’ which unfortunately contains some extra characters but you can remove with search/replace. This file is now your new style1.css.

Now we want to put the contents of critical.css inline in the header, and place style1.css in the footer just before all the footer js, and remove all of the other CSS.

  • In WordPress, you can disable all theme CSS in your function.php file: See this solution on stack overflow (Note: we may need to modify this for logged-in users). You can then override the header.php file in your theme, and add a php include statement for the critical.css file just above wp_head() to load the contents directly
    <style type="text/css"><?php include get_stylesheet_directory() . '/.critical.css'; ?></style>
    Note that if you have an external CSS like webfont, for now it is good to just place there manually.
    Then you should place the style css file manually in your footer.php file just above wp_footer. It is probably best to rename this to style.css and make it your theme style file, complete with theme info on top.
    <link rel="stylesheet" href="<?php include get_stylesheet_directory() ?>./style.css" type="text/css" media="all">
  • In Drupal 7 you can also disable css in your theme template.php file, and load certain css for admins

/* Unset all the base drupal stylesheets */
function themename_css_alter(&$css) {
global $user;
$admincss = array(
drupal_get_path('module', 'user') . '/user.css',
drupal_get_path('module', 'contextual') . '/contextual.css',
drupal_get_path('module', 'ckeditor') . '/css/ckeditor.css',
drupal_get_path('module', 'admin_menu') . '/admin_menu.css',
drupal_get_path('module', 'admin_menu') . '/admin_menu.uid1.css',
drupal_get_path('module', 'domain_admin') . '/domain_admin.css'
);
$temp = $css;
foreach($temp as $key => $data) {
if(!$user->uid || !in_array($key, $admincss)) {
unset($css[$key]);
}
}
Then you can override html.tpl.php and paste the critical css file above the $styles variable.

<style><?php include drupal_get_path('theme', 'yourtheme') . '/critical.css'; ?></style>

and the style css file at the bottom above the $page_bottom variable

<link rel="stylesheet" href="<?php include drupal_get_path('theme', 'yourtheme') ./style.css" type="text/css" media="all">

Cleaning up icon fonts

Some themes (eg wordpress Divi) has its own icon font or import a common font, which is pretty big! We should use fontello to create our own much smaller font based on the icons you actually use and import this one instead.

Javascript

more to come…

Adding LetsEncrypt certs to a cPanel account with more than 100 domains using AutoSSL

We recently had to add Let’s Encrypt SSL certs to a cpanel account which contained over 100 parked domains (or domain aliases, as they are now called)

The problem is that Lets Encrypt has a 100 domain limit for SAN certs (multiple domains on the same certificate). In reality that limit is 50, because you have to issue certs for both the non-www and www versions of the domain to ensure effective non-www to www redirect (or vice versa)

The WHM AutoSSL function adds an SSL certificate for each Apache virtual host. Parked domains are included in the account domain’s virtual host, so AutoSSL will try to add all the parked domains to the same SSL certificate, adding as many as it can before the domain limit is reached.

However if you add each domain to the account as an add-on domain, cPanel creates an Apache virtual host for each add on domain, and AutoSSL will issue a seperate certificate for each host.

So the solution in our case, was to remove all our parked domains, and turn them into add-on domains pointing at the account domain’s web root.

Delete inactive Drupal users – sql query

Delete users who have no role assigned, created no content and who have not logged in in a few years:

DELETE u.* FROM users u LEFT JOIN users_roles r on u.uid = r.uid LEFT JOIN node n ON u.uid = n.uid WHERE r.rid IS NULL AND n.nid IS NULL AND u.login < 1400000000 AND u.uid > 1;
DELETE a.* FROM authmap a LEFT JOIN users u ON a.uid = u.uid WHERE u.uid IS NULL;

Export all 404 errors stored in Drupal database log

A drush command to export a text file with all 404s on your site and how often they occur:

drush sql-query "SELECT count(*) as num_rows, message from watchdog WHERE type = 'page not found' group by message order by num_rows desc;" --result-file=404s.txt

WordPress: get all posts with no audio files attached

Useful SQL query for wordpress:

SELECT p.post_title, p.guid
FROM wp_posts p
WHERE p.post_type = 'post' AND
NOT EXISTS (SELECT *
FROM wp_posts a
WHERE p.ID = a.post_parent
AND a.post_mime_type LIKE '%audio%');

Generating thumbnails on the fly with WordPress

One advantage of Drupal’s resizing images over WordPress – WordPress’s resized images are generated at upload time only, whereas Drupal’s will be generated automatically on page load if the thumbnail doesn’t already exist.

You could put this function into your WP template – given an image url $image_url, it checks for the address of its thumbnail $thumb_url. If it doesnt find it, then it generates a thumbnail of size $xdim x $ydim

function maybe_generate_userarticlethumb($thumb_url, $image_url, $xdim, $ydim) {  
  
  /* There might be quicker ways to check if url exists than file_get_contents */
  if(!@file_get_contents($thumb_url)) {   
    $imagepath = str_replace(array($_SERVER["HTTP_HOST"], 'http://', 'https://'), array($_SERVER["DOCUMENT_ROOT"], '', ''), $image_url);
    $thumbpath = str_replace(array($_SERVER["HTTP_HOST"], 'http://', 'https://'), array($_SERVER["DOCUMENT_ROOT"], '', ''), $thumb_url);
    $image = wp_get_image_editor($imagepath);
    if ( ! is_wp_error( $image ) ) {
       /* Use any of WP's image manipulation functions here */
       $image->resize( $xdim, $ydim, true );
       $image->save($thumbpath);
    }
  }
}  

Then just call maybe_generate_userarticlethumb($thumb_url, $image_url, $xdim, $ydim) in your template, and print $thumb_url where you need to.

Target Safari and Chrome with specific css

Sometimes you need to apply CSS rules to deal with Webkit browsers such as Safari and Chrome. One example: sometimes non-standard fonts rendered using @font-face or an embed service such as Typekit or Google webfonts display thinner than on other browsers.

You can deal with this using a media query:

@media screen and (-webkit-min-device-pixel-ratio:0) {
   body { font-weight: 400; }
}

Thanks to phrappe.com for this useful tidbit!

Searching for large files on your server

Here’s a command that will check the root / folder recursively for files greater than or equal to 1GB and sort the list.

cd / ; du -h | grep '^[0-9]\+\.\?[0-9]\?G' 2>/dev/null | sort -nrk 1

Thanks to Phil from WiredTree for sharing that info, I’m just putting it down here so I can remember it in future 🙂

Redirect rules to change underscore to hyphen

Unfortunately Apache redirect only allows for 9 placeholders, so that’s the maximum amount of undercore changes that this code allows. We are using this mainly within WordPress sites, so here is the entire wp htaccess file, so you know where to position the code.

# BEGIN WordPress

RewriteEngine On
RewriteBase /

# BEGIN underscore to hyphen redirect code
RewriteRule ^([^_]*)_([^_]*)_([^_]*)_([^_]*)_([^_]*)_([^_]*)_([^_]*)_([^_]*)_([^_]*)$ /$1-$2-$3-$4-$5-$6-$7-$8-$9 [R=301,L]
RewriteRule ^([^_]*)_([^_]*)_([^_]*)_([^_]*)_([^_]*)_([^_]*)_([^_]*)_([^_]*)$ /$1-$2-$3-$4-$5-$6-$7-$8 [R=301,L]
RewriteRule ^([^_]*)_([^_]*)_([^_]*)_([^_]*)_([^_]*)_([^_]*)_([^_]*)$ /$1-$2-$3-$4-$5-$6-$7 [R=301,L]
RewriteRule ^([^_]*)_([^_]*)_([^_]*)_([^_]*)_([^_]*)_([^_]*)$ /$1-$2-$3-$4-$5-$6 [R=301,L]
RewriteRule ^([^_]*)_([^_]*)_([^_]*)_([^_]*)_([^_]*)$ /$1-$2-$3-$4-$5 [R=301,L]
RewriteRule ^([^_]*)_([^_]*)_([^_]*)_([^_]*)$ /$1-$2-$3-$4 [R=301,L]
RewriteRule ^([^_]*)_([^_]*)_([^_]*)$ /$1-$2-$3 [R=301,L]
RewriteRule ^([^_]*)_([^_]*)$ /$1-$2 [L,R=301]
# END underscore to hyphen redirect code

RewriteRule ^index\.php$ - [L]
RewriteCond %{REQUEST_FILENAME} !-f
RewriteCond %{REQUEST_FILENAME} !-d
RewriteRule . /index.php [L]


# END WordPress