How to defer scripts to the footer and avoid render-blocking in WordPress

Contents

Overview

This article explains, in exhaustive detail, how to defer scripts to the footer in WordPress and avoid render-blocking resources. It covers fundamental concepts, step-by-step code examples (for functions.php and inline scripts), tips for CSS handling, caveats, testing methodology, and recommended plugins and practices. All examples are ready to paste into a child theme or a site-specific plugin — test on staging first.

Why render-blocking resources matter

Browsers block rendering while they download and parse external CSS and JavaScript that appear in the document head. This delays First Contentful Paint (FCP) and increases Time to Interactive (TTI). Minimizing render-blocking resources improves perceived and actual page speed, SEO, and user experience.

Which resources block rendering

  • CSS loaded with rel=stylesheet in the head blocks rendering until the stylesheet is downloaded and parsed.
  • JavaScript included with a standard ltscript src=… gt tag in the head blocks HTML parsing until download and execution complete.
  • Fonts and preloads can affect rendering if not handled correctly.

Principles and strategy

  1. Keep only truly critical CSS inline and defer the rest.
  2. Enqueue scripts with the in_footer flag whenever possible so they print before the closing lt/bodygt tag.
  3. Use defer or async attributes for non-blocking execution prefer defer for scripts that must preserve execution order.
  4. Exception handling: exclude scripts that must run early (e.g., critical analytics containers, Modernizr, some inline scripts that alter rendering).
  5. Test thoroughly: staging first, then Lighthouse, WebPageTest, Chrome DevTools.

WordPress fundamentals: enqueueing to the footer

The simplest, safest method is to set the in_footer parameter to true in wp_enqueue_script. This ensures WordPress prints the script in wp_footer rather than the head.

// functions.php
function theme_enqueue_assets() {
    // Enqueue styles in head (default)
    wp_enqueue_style(theme-style, get_template_directory_uri() . /assets/css/style.css, array(), 1.0.0)

    // Enqueue main script in the footer: last param true = in footer
    wp_enqueue_script(theme-main, get_template_directory_uri() . /assets/js/main.js, array(jquery), 1.0.0, true)
}
add_action(wp_enqueue_scripts, theme_enqueue_assets)

Notes

  • Always register/enqueue scripts using wp_enqueue_script to keep dependency management intact.
  • Do not hardcode script tags into the head unless absolutely necessary.

Moving jQuery to the footer (careful)

By default some themes/plugins expect jQuery in the head. You can move the bundled jQuery to the footer, but test thoroughly to avoid breaking plugins, admin pages, or inline scripts that expect jQuery early.

// Move core jQuery to the footer on the front-end
function move_jquery_to_footer() {
    if ( ! is_admin() ) {
        wp_deregister_script(jquery)
        // Use the WP-included jQuery file, but load it in footer: last param true
        wp_register_script(jquery, includes_url(/js/jquery/jquery.min.js), array(), null, true)
        wp_enqueue_script(jquery)
    }
}
add_action(wp_enqueue_scripts, move_jquery_to_footer)

Warnings

  • Do not apply on admin pages. The code above checks is_admin() and only changes the front-end.
  • Some themes or inline scripts loaded in the head may break — test and add exceptions where needed.

Adding defer or async attributes to enqueued scripts

WordPress prints script tags for enqueued scripts. You can modify those tags using the script_loader_tag filter to add defer or async. Use an allowlist or denylist to avoid breaking critical scripts.

// Add defer attribute to selected enqueued scripts
function add_defer_attribute( tag, handle ) {
    // List script handles you want to defer
    defer_handles = array( theme-main, another-handle )

    if ( in_array( handle, defer_handles ) ) {
        return str_replace(