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