WordPress Speed Optimization Without Plugins

Table of Contents

Get posts directly to your inbox

Why Kegan?

Kegan is an army of one. We've worked with him on numerous WordPress projects, 2018-2021 and counting. He's always current on tech and techniques, extremely responsive and thorough, and his infectious optimism fuels our projects forward. He's a rare find. Travis Culwell - AAA


17 Reviews, 5 Stars

Introduction

As a developer, I can’t help but be obsessed with speed. It’s one thing that I can control when it comes to SEO and user experience. It should come as no surprise that my own site scores 100 on GTMetrix, but then again it’s somewhat easy when you have complete and total control of your own website.

I really hate a lot of plugins (in fact, I only use about 6 for any given site), and have become obsessed with WordPress speed optimization without plugins. It’ll make your site much easier to manage if you reduce your plugin usage as much as you can.

Most websites, however, aren’t so straight forward, and achieving perfect WordPress speed optimization without plugins is really difficult. The real world is messy, and cutting the clutter from your website is hard. In this guide, I’m going to go over how to optimize your WordPress site speed without needing plugins.

It should be noted, that the speed of your website is by no means the end-all-be-all of SEO, but it should be taken into consideration if you’re really serious about getting on the first page of Google. As a rule of thumb, content is king, and rules all other “technical” considerations.

If your speed is 70+, you’re probably doing just fine. Then again to really rise in the ranks you’re going to need a website that scores 90+, as most websites at the top have great content AND are really fast.

Hosting

The web is like many other walks of life: you get what you pay for. Quite simply, you just have to pay for good hosting. There’s really no way around it.

If your servers aren’t optimized, it won’t really matter what else you do, you just cannot get a fast site. The two hosts I recommend are WPEngine and Kinsta. They optimize their servers for WordPress specifically (whereas a lot of $3/mo servers don’t).

They’ll cost about $22/mo, but unfortunately the rest of this article is pretty much a nonstarter unless you have a good host. These are well worth the cost, as you’ll never need another security or speed plugin again.

Themes

The theme you buy will play a major factor in site speed. There are a ton of good themes out there, as it relates to site speed. The site speed should play a role is deciding what theme to buy, but you should also look at other factors like flexibility, Gutenberg adoption, and if the theme can achieve the design you want right out of the box.

WordPress is trending more and more in the direction of flexible blocks, that have a ton of layout options, so you can most likely achieve a design that is good enough using a theme that optimizes for speed. Before the days of Gutenberg, themes had to load all their scripts and styles regardless of the content on the page, which would cause a lot of bloat.

Of course, if you have the budget for it, you should seriously consider getting a custom theme made, as this will be the leanest possible choice, and you can achieve perfect speed while also not sacrificing any design choice.

You will likely have to sacrifice something when it comes to themes, in order to achieve perfect speed. In the case of a pre-made theme, it might be design. In the case of a custom theme, it might be money.

Scripts

Scripts will forever be the bane of any WordPress developer’s existence. With a lot of themes, there are a ton of scripts that are loaded by default. These give your website little scrolling effects, animations, and a lot more. The problem is they’re loaded on every single page (even when they’re not needed).

Many themes have a lot of prebuilt layouts that you can one-click install, and as such they need Javascript for every single one of them. Typically they just load all the Javascript and styles regardless of which layout you’re using.

I would highly encourage you to remove as many scripts as you can. This could be a problem if your site is already built, as the effects of this are often hard to see right away (it could cause forms not to submit, pages to break, etc.) so please be careful when fully removing scripts.

It should be noted that it’s much easier to remove scripts before you start to build a site, as opposed to trying to take them out once your site is already up and running.

At the very least, however, you should be using Autoptimize to concatenate and minify all your scripts (I know, I know, I said no plugins). Furthermore, if the scripts aren’t necessary to loading the page of your site (but you want to keep them), you should move them to the footer and defer their loading. More on this later.

If you must keep the scripts, make sure they’re not loading from a CDN that isn’t yours. It’s common for theme developers to load assets like jQuery and Bootstrap from their global CDN network. It’ll slow your site down a bit, because although it may already be loaded on a users local computer (from hitting it on another site), the browser still has to hit a different domain to check.

Here are a few common scripts you can deactivate to speed up your site (for your functions.php file):

PLEASE TEST THESE ON A STAGING SITE BEFORE IMPLEMENTING ON YOUR PRODUCTION SITE. THEY CAN CAUSE YOUR SITE TO CRASH.

jQuery

If your site is already built, this is a tough one to deactivate. Often a majority of the functionality will depend on jQuery.

function kq_remove_jquery() {
  if (!is_admin()) wp_deregister_script('jquery');
}

add_action( 'wp_enqueue_scripts', 'kq_remove_jquery' );

WP Embed & WP a11y

You’ll have to embed media via an iframe instead of just a URL. The a11y script will update screen readers in case of an ajax call.

function kq_deregister_embeda11y(){
  wp_dequeue_script( 'wp-embed' );
  wp_dequeue_script( 'wp-a11y' );
}
add_action( 'wp_footer', 'kq_deregister_embeda11y' );

Gravity Forms

This will force Gravity Forms scripts to be inline, and then remove those scripts. You’ll likely need some sort of captcha on your forms. I use Really Simple Captcha, but there are others. If you still find yourself getting a lot of spam, then you should consider not removing these scripts and using Google Captcha.

function kq_force_gform_inline_scripts() {
    return false;
}
add_filter("gform_init_scripts_footer", "kq_force_gform_inline_scripts");

function kq_strip_inline_gform_scripts( $form_string, $form ) {
	return $form_string = preg_replace('#<script(.*?)>(.*?)</script>#is', '', $form_string);
}
add_filter("gform_get_form_filter", "kq_strip_inline_gform_scripts", 10, 2);

There are many other scripts that can come installed with themes, so be diligent in removing them before you start building your site.

If you absolutely cannot remove your scripts, they should be deferred:

function kq_defer_parsing_of_js( $url ) {
    if ( is_user_logged_in() ) return $url; //don't break WP Admin
    if ( FALSE === strpos( $url, '.js' ) ) return $url;
    if ( strpos( $url, 'jquery.js' ) ) return $url;
    return str_replace('src', 'defer src', $url );
}
add_filter( 'script_loader_tag', 'kq_defer_parsing_of_js', 10 );

This is going to defer their loading until everything else has loaded, and thus won’t make them render blocking. That code snippet will do it for everything but your jQuery script.

Styles

Styles are much easier to concatenate, minify, and move into a single file than scripts are. Because styles are often built from multiple files, a lot of themes will do this by default, allowing you to override styles in the actual style.css sheet, or in a child theme.

If you want to write your own styles, both of these methods are fine, but if you don’t use them, you should remove the style.css from the parent and child theme from loading on your page.

By default, WordPress will load the front end styles for their blocks. This is great for their themes, but most themes will usually style these blocks themselves, and thus these can be deleted (again, please test this on a staging site before putting it on your production site). To do that, add this to your functions.php

function kq_remove_wp_block_library_css(){
    wp_dequeue_style( 'wp-block-library' );
    wp_dequeue_style( 'wp-block-library-theme' );
    wp_dequeue_style( 'wc-block-style' );
} 
add_action( 'wp_enqueue_scripts', 'kq_remove_wp_block_library_css', 100 );

If you use the Autoptimize plugin, or any other plugin that concatenates your styles and scripts for you, this will be less of an issue. By default, however, these are 3 stylesheets that are loaded independently.

Critical CSS

The last thing you should be doing with your styles is loading what’s called critical CSS. This is all your css for above-the-fold content, all within a <style> tag inside the <head> of each page. This allows your entire stylesheet to be deferred and not cause page loading to stop.

There are a number of critical css generators. Some are paid, and others are free. The paid versions will use an API and automatically add your critical css for a monthly fee. This is useful on larger sites, but if your site has less than 20 pages then you should be able to manually add it with free tools.

To add critical css using free tools, first generate the css. Then add a custom field in each post / page in the form of a textarea box, and paste in the generated css. In your header add the php for the critical css. It should look something like this:

<?php if (get_field('critical_css')) : ?>
<style>
  <?php the_field('critical_css'); ?>
</style>
<?php endif; ?>

After you do that, you can then change the way your stylesheet is called, by removing it from the enqueue within your functions.php, and adding this line right before <?php wp_head(); ?>

<link rel="preload" href="<?php echo get_stylesheet_directory_uri(); ?>/style.css" as="style" onload="this.onload=null;this.rel='stylesheet'">

This should really only be done on a production site, as it will often cause older versions of the styles to be (aggressively) cached. If you want to add a ?ver=1.1 to the end of your style.css you should be able to get the browser to pull in the new version.

Fonts

Fonts can be a major pain point when it comes to WordPress speed optimization without plugins. For the most part, you shouldn’t be loading fonts via plugins (there are a few rare exceptions, but you’re likely not one of them). You also should not be loading fonts from any domain that isn’t yours. This can cause connection issues, and for the most part you want to be loading all assets from a single domain.

In the case of Google Fonts, when you select the fonts you want to use, download the entire family, and move the .ttf files to your theme, and then serve the fonts via CSS (see below).

Any other font generator (Adobe, TypeKit, etc.) will allow you to download font files and serve them yourself too.

If you’re using SASS to write your theme you can use this mixin for fonts (add more extensions in the definition if you have woff, woff2, etc. files. I just use this for Google Fonts because they give you ttf by default).

@mixin font-face($name, $path, $weight: null, $style: null, $exts: ttf, otf) {
  $src: null;

  $extmods: (
    eot: "?",
    svg: "#" + str-replace($name, " ", "_")
  );

  $formats: (
    ttf: "truetype",
    otf: "opentype"
  );

  @each $ext in $exts {
    $extmod: if(map-has-key($extmods, $ext), $ext + map-get($extmods, $ext), $ext);
    $format: if(map-has-key($formats, $ext), map-get($formats, $ext), $ext);
    $src: append($src, url(quote($path + "." + $extmod)) format(quote($format)), comma);
  }

  @font-face {
    font-family: quote($name);
    font-style: $style;
    font-weight: $weight;
    src: $src;
    font-display: swap;
  }
}

And then you can call it in other SASS files:

@include font-face('Lato', '../fonts/Lato-Regular');

This outputs the following (if you’re writing your own CSS rules from within the WordPress theme editor, use this CSS):

@font-face {
  font-family: Lato;
  src: url(../fonts/Lato-Regular.ttf) format("truetype");
  font-display: swap;
}

You can now use font-family: "Lato"; on any element.

At the very least, make sure to add font-display: swap; to all your font rules, as this will allow the browser to load system fonts for a split second until your fonts are loaded. Thus, they don’t have to stop the page rendering to load your custom fonts.

Images

Images are at the heart of speed optimization. While I firmly believe you should be using a plugin for compressing images, it’s not 100% mandatory. Especially on a site that tends to be a bit smaller, with content that doesn’t refresh itself very often.

In the case of small business sites that aren’t blogs or don’t have a lot of new content coming out with any frequency you can compress your images one time, and manually, over at TinyJPG. All you do is upload any image you want to put on your site, and they’ll compress it for you.

However, you may also want to consider WebP images. These are images that are often 40-50% smaller than JPG & PNG images without any loss in quality. They’re not widely adopted yet (they only have support in about 70% of browsers, the big name that doesn’t support them is Safari), but if you use <picture> you can set a fallback JPG image that will show in case of browsers that don’t support.

If you have a site that is going to be uploading a lot of images, you may want to consider Imagify. They’ll automatically compress your images for you (for free up to 20mb per month), and output the WebP version of the same images, with a fallback.

Image Sizing

Your images should be sized to fit their containers, and be no smaller or bigger. If they’re bigger, you’re just wasting resources, and if they’re smaller they’ll appear blurry.

For example, if you have an image with css that sets the width to 50px, you want to make sure the actual image itself is 50 pixels, and not, say 1000px. That would be a huge waste of load time, when a compressed 50 pixel image will be fairly inconsequential to load time.

Icons

As a rule of thumb, you should try to use SVG images as much as possible. SVG images are scalable vector graphics, and are XML files. The file contents tell the browser which lines to draw (and how to color them, size them, etc.) in order to make the icon appear.

SVGs are scalable into infinity, and are very VERY small in size (relative to JPGs). I use Noun Project to find SVGs, but if you’re looking for a company logo (for example) you can Google it and add “SVG” and you should have plenty of options.

By default, WordPress does not allow SVGs to be uploaded via the media uploader, so add this snippet to functions.php

add_filter( 'wp_check_filetype_and_ext', function($data, $file, $filename, $mimes) {

  global $wp_version;
  if ( $wp_version !== '4.7.1' ) {
     return $data;
  }

  $filetype = wp_check_filetype( $filename, $mimes );

  return [
      'ext'             => $filetype['ext'],
      'type'            => $filetype['type'],
      'proper_filename' => $data['proper_filename']
  ];

}, 10, 4 );

function cc_mime_types( $mimes ){
  $mimes['svg'] = 'image/svg+xml';
  return $mimes;
}
add_filter( 'upload_mimes', 'cc_mime_types' );

function fix_svg() {
  echo '<style type="text/css">
        .attachment-266x266, .thumbnail img {
             width: 100% !important;
             height: auto !important;
        }
        </style>';
}
add_action( 'admin_head', 'fix_svg' );

There are, of course, some other ways to allow SVGs to be uploaded, but that snippet should do the trick.

You can use the source on an image tag, the way you would with any other image (ie: <img src="icon.svg"/>), but a better method is to use file_get_contents (docs here) as this will output the actual XML of the SVG, and cause zero rendering slow downs.

Lazy Loading

One other thing to touch on is lazy loading images. This is the process of waiting to load images that are below the fold, until after everything else on the page has already loaded. You should make sure you’re doing this, but it’s a bit too complicated for this tutorial. Instead check out Autoptimize, and make sure you have the lazy load setting checked.

On a final note: images are so important to your site speed, that you should leave this functionality up to a plugin. It’s simply too important to not automate and use best practices.

In Closing

WordPress speed optimization without plugins is hard. It’s likely not as straight forward as this article makes it out to be.

If you haven’t already built your site, I would highly encourage you to take these steps, as it will be much simpler if you’re starting from square one.

If you have already built your site, setup a staging site, and try out as many of these optimizations as make sense for you.