An Extreme Approach to Speeding Up WordPress

on in Blog
Last modified on

Note: This tutorial applies to a specific use case and parts of it can be used depending on your needs.

The specific use case is WordPress as a minimal CMS, highly optimized for SEO.

Table of Contents

Hardcode the menu items

Note 2: The site is the service, so the theme will never be changed.

Note 3: I need to add various checks before showing menu items, such as:

<?php
if (is_user_logged_in()) {
    display_menu_item('My Account');
}

// or

if (is_user_level('x')) {
    display_menu_item('My Account');
}

The first thing to do is hardcode all the things! Obviously not a good functional programming practice, but hear me out:

I have 5 menu items and a pretty off-canvas mobile menu with only 3 items. I need to be able to fully control them without using JavaScript.

  • Do I need to query the database for the menu items every pageview? No.
  • Do I need to cache the menu items using a transient? No.
  • Do I need to rely on the PHP/MySQL/WordPress caching features? Possibly, but why do it when I don’t need any dynamic features for my menu?

The solution is to build a nice menu in header.php while removing the native menu feature of WordPress.

Here’s a sample, complete with a navigation schema:

<ul itemscope itemtype="http://www.schema.org/SiteNavigationElement">
    <li itemprop="name">
        <?php if ( ! is_user_logged_in() ) { ?>
            <a itemprop="url" href="#">Log in</a>
        <?php } else { ?>
            <a itemprop="url" href="#">My Account</a>
        <?php } ?>
    </li>

    <li itemprop="name"><a itemprop="url" title="Some Title Here" href="#">Pricing</a></li>
    <li itemprop="name"><a itemprop="url" title="Another Title Here" href="#">Features</a></li>
</ul>

Note how simple and easy it is to create the menu. The only feature I miss from the wp_nav_menu() function is the automatic class injection to highlight the active menu item. I can do that in CSS, though, by targeting the page class and the menu item class together.

I can already hear the questions, “what’s the point in using WordPress if not taking advantage of the menu feature?” or “what if the editor needs to add a new page?” or “what if the marketing team needs to create a landing page?”.

Well, as we’re trying to keep the theme as fast as possible and as optimized as possible, we’re going to strip away some of the editor’s and the marketing team’s capabilities. The theme should be under strict version control and everything affecting page speed needs to be vetted and implemented by the development team.

So let’s remove menu support from WordPress. If you’re not using register_nav_menus() in your theme’s functions.php and you add:

function my_theme_setup() {
    remove_theme_support('menus');
}
add_action('after_setup_theme', 'my_theme_setup');

Then the AppearanceMenus menu item won’t be loaded.

Remove the Customizer

Another feature we’re not using is the Customizer. As it appears in the menu, and it also injects inline JavaScript right after the opening <body> tag, here are the steps to remove this functionality:

Remove the Customizer from the admin bar and remove the injected JavaScript:

In header.php, add a default class of no-customize-support to your body_class() function:

<body <?php body_class('no-customize-support'); ?>>

Then add the code below to your theme’s functions.php:

<?php
add_action('admin_bar_menu', function() {
    remove_action('wp_before_admin_bar_render', 'wp_customize_support_script');
}, 50);

Note that there’s been lots of attempts to make this configurable, but it never happened.

Second, let’s remove the Customize link from the Dashboard:

<?php
function my_remove_customize_page() {
    global $submenu;

    unset($submenu['themes.php'][6]); // Remove Customize link
}
add_action('admin_menu', 'my_remove_customize_page');

At this point, you’re left with only the Appearance menu item, which opens the Themes page. If you only have one theme and want to be really extreme, you can completely remove The Appearance menu item, by using the native remove_menu_page() function:

<?php
function my_remove_menus() {
    remove_menu_page('themes.php'); // Remove Appearance item
}
add_action('admin_menu', 'my_remove_menus');

Hardcode Google Fonts

This feature will save you several HTTP requests and several preconnection requests.

Note 4: This depends on your required browser support. In my case, it’s modern browsers only. Keep reading.

Let’s say I use the Muli font and I need to enqueue the fonts:

function my_load_assets() {
    wp_enqueue_style( 'fonts', 'https://fonts.googleapis.com/css?family=Muli:400,500,700&display=swap' );
}
add_action( 'wp_enqueue_scripts', 'my_load_assets' );

This will trigger the following code being added to the <header> section:

<link rel="dns-prefetch" href="//fonts.googleapis.com" />
<link rel="dns-prefetch" href="//fonts.gstatic.com" />

<link rel="stylesheet" id="google-fonts-css"  href="//fonts.googleapis.com/css?family=Muli:300,400,500,700" type="text/css" media="all" />

Note that the stylesheet will load the fonts like below, one for each font weight:

/* latin-ext */
@font-face {
    font-family: 'Muli';
    font-style: normal;
    font-weight: 300;
    font-display: swap;
    src: local('Muli'), url(https://fonts.gstatic.com/s/poppins/v9/pxiByp8kv8JHgFVrLDz8Z1JlFc-K.woff2) format('woff2');
    unicode-range: U+0100-024F, U+0259, U+1E00-1EFF, U+2020, U+20A0-20AB, U+20AD-20CF, U+2113, U+2C60-2C7F, U+A720-A7FF;
}

My solution is to self-host these fonts (let’s pretend they are official branding/identity fonts, so they won’t change next month or even next year). Here’s a great solution to automate self-hosting – Google webfonts helper. After generating the fonts (make sure you select Modern Browsers), add them to your main stylesheet:

/* muli-300 - latin */
@font-face {
    font-family: 'Muli';
    font-style: normal;
    font-weight: 300;
    src: local(''),
         url('../fonts/muli-v20-latin-300.woff2') format('woff2'), /* Chrome 26+, Opera 23+, Firefox 39+ */
         url('../fonts/muli-v20-latin-300.woff') format('woff'); /* Chrome 6+, Firefox 3.6+, IE 9+, Safari 5.1+ */
}
/* muli-regular - latin */
@font-face {
    font-family: 'Muli';
    font-style: normal;
    font-weight: 400;
    src: local(''),
         url('../fonts/muli-v20-latin-regular.woff2') format('woff2'), /* Chrome 26+, Opera 23+, Firefox 39+ */
         url('../fonts/muli-v20-latin-regular.woff') format('woff'); /* Chrome 6+, Firefox 3.6+, IE 9+, Safari 5.1+ */
}
/* muli-700 - latin */
@font-face {
    font-family: 'Muli';
    font-style: normal;
    font-weight: 700;
    src: local(''),
         url('../fonts/muli-v20-latin-700.woff2') format('woff2'), /* Chrome 26+, Opera 23+, Firefox 39+ */
         url('../fonts/muli-v20-latin-700.woff') format('woff'); /* Chrome 6+, Firefox 3.6+, IE 9+, Safari 5.1+ */
}

You’ll get two versions of the font, woff and woff2. You only need woff2, it’s 2022. Remove the woff one, like below:

/* muli-300 - latin */
@font-face {
    font-family: 'Muli';
    font-style: normal;
    font-weight: 300;
    src: local(''),
         url('../fonts/muli-v20-latin-300.woff2') format('woff2');
}
/* muli-regular - latin */
@font-face {
    font-family: 'Muli';
    font-style: normal;
    font-weight: 400;
    src: local(''),
         url('../fonts/muli-v20-latin-regular.woff2') format('woff2');
}
/* muli-700 - latin */
@font-face {
    font-family: 'Muli';
    font-style: normal;
    font-weight: 700;
    src: local(''),
         url('../fonts/muli-v20-latin-700.woff2') format('woff2');
}

Done!

No more DNS prefetching, no more external HTTP requests, no more extra CSS being enqueued.

Additional reading on self-hosting fonts: making Google fonts faster, optimizing Google fonts performance and don’t just copy the font-face out of Google fonts URLs.

Conclusion

This approach may sound extreme, but it’s proven, and it works. And when you’re in a race on pagespeed improvement and every millisecond and every byte saved counts, that’s when extreme solutions come in really handy.

Related posts