• Skip to secondary menu
  • Skip to main content
  • Skip to primary sidebar
  • Home
  • Projects
  • Products
  • Themes
  • Tools
  • Request for Quote

Vengala Vinay

Having 12+ Years of Experience in Software Development

  • Home
  • WordPress
  • PHP
    • Codeigniter
  • Django
  • Magento
  • Selenium
  • Server
Home » How to Hooks and Filters in Lazy Loading Assets and Critical CSS Optimizations Without Breaking Site Responsiveness

How to Hooks and Filters in Lazy Loading Assets and Critical CSS Optimizations Without Breaking Site Responsiveness

Leveraging WordPress Hooks for Advanced Asset Loading and Critical CSS

Optimizing asset loading and implementing critical CSS are paramount for achieving high performance scores in WordPress. This is particularly true for sites that rely on complex JavaScript functionalities or dynamic content. While many plugins offer these features, a deep understanding of WordPress’s hook system allows for granular control, ensuring that these optimizations don’t inadvertently break site responsiveness or user experience. This guide focuses on implementing these optimizations programmatically, using WordPress hooks and filters to manage JavaScript, CSS, and critical CSS generation.

Deferring Non-Critical JavaScript with `script_loader_tag`

By default, WordPress enqueues scripts with the `type=”text/javascript”` attribute. To defer the loading of non-essential JavaScript, we can leverage the `script_loader_tag` filter. This filter allows us to modify the HTML `script` tag before it’s outputted. We’ll target specific scripts that are known to be non-critical and add the `defer` attribute. This ensures that these scripts are downloaded in the background and executed only after the HTML document has been fully parsed, preventing render-blocking.

Consider a scenario where you have a third-party analytics script or a non-essential UI enhancement script that doesn’t need to run immediately on page load. We can identify these scripts by their handle (the name given when enqueuing) and modify their output.

Implementation Example: Deferring Analytics Script

Add the following code to your theme’s `functions.php` file or a custom plugin:

add_filter( 'script_loader_tag', 'my_defer_non_critical_scripts', 10, 2 );

function my_defer_non_critical_scripts( $tag, $handle ) {
    // List of script handles to defer.
    $scripts_to_defer = array( 'my-analytics-script', 'another-non-critical-js' );

    // Check if the current script handle is in our defer list.
    if ( in_array( $handle, $scripts_to_defer ) ) {
        // Add the 'defer' attribute to the script tag.
        $tag = str_replace( ' src', ' defer src', $tag );
    }

    return $tag;
}

In this example, `my-analytics-script` and `another-non-critical-js` are the handles of the scripts we want to defer. The `in_array()` check ensures we only modify the intended scripts. The `str_replace()` function intelligently inserts the `defer` attribute before the `src` attribute, maintaining valid HTML.

Asynchronous Loading of Critical JavaScript

While `defer` is excellent for scripts that should run after parsing, `async` is suitable for independent scripts that can execute as soon as they are downloaded, without blocking parsing. This is useful for scripts that don’t rely on the DOM being fully ready or on other scripts.

Implementation Example: Async for Independent Scripts

Similar to deferring, we can use `script_loader_tag` to add the `async` attribute. This is particularly useful for scripts that fetch data independently, such as certain widgets or dynamic content loaders that don’t have strict execution order dependencies.

add_filter( 'script_loader_tag', 'my_async_independent_scripts', 10, 2 );

function my_async_independent_scripts( $tag, $handle ) {
    // List of script handles to load asynchronously.
    $scripts_to_async = array( 'my-independent-widget-js', 'external-api-fetcher' );

    // Check if the current script handle is in our async list.
    if ( in_array( $handle, $scripts_to_async ) ) {
        // Add the 'async' attribute to the script tag.
        $tag = str_replace( ' src', ' async src', $tag );
    }

    return $tag;
}

Here, `my-independent-widget-js` and `external-api-fetcher` are examples of script handles that can benefit from asynchronous loading. The logic is identical to deferring, but we’re adding the `async` attribute instead.

Conditional Enqueuing and Deregistration

Not all scripts need to be loaded on every page. WordPress provides powerful conditional tags and the ability to deregister scripts. This is crucial for performance, as it reduces the amount of JavaScript the browser needs to process. We can use hooks like `wp_enqueue_scripts` to conditionally enqueue or deregister scripts based on the current page, post type, or user role.

Example: Deregistering Scripts on Specific Pages

Suppose a heavy JavaScript plugin is only necessary for the WooCommerce shop and cart pages, but not for blog posts. We can deregister it from blog posts.

add_action( 'wp_enqueue_scripts', 'my_conditional_script_deregistration', 999 ); // High priority to run after others

function my_conditional_script_deregistration() {
    // Check if we are on a single post page (not a WooCommerce product page).
    if ( is_single() && ! class_exists( 'WooCommerce' ) ) {
        // Deregister a hypothetical heavy script.
        wp_deregister_script( 'heavy-plugin-script' );
        // Also deregister its stylesheet if it has one.
        wp_deregister_style( 'heavy-plugin-style' );
    }

    // Example for WooCommerce pages: only load if WooCommerce is active and on specific pages.
    if ( class_exists( 'WooCommerce' ) ) {
        if ( is_shop() || is_product_category() || is_product() || is_cart() || is_checkout() ) {
            // Ensure the script is enqueued here if it was deregistered elsewhere.
            // Or, if it's a script that should ONLY load on these pages, enqueue it here.
            // wp_enqueue_script( 'woocommerce-specific-script', get_template_directory_uri() . '/js/woocommerce-specific.js', array('jquery'), '1.0', true );
        } else {
            // Deregister scripts that are not needed on non-WooCommerce pages.
            // wp_deregister_script( 'woocommerce-specific-script' );
        }
    }
}

The `is_single()` check, combined with `! class_exists( ‘WooCommerce’ )`, ensures this logic applies only to standard blog posts and not WooCommerce product pages. The `wp_deregister_script()` and `wp_deregister_style()` functions remove scripts and styles that have already been registered. The priority `999` ensures this runs late in the process, after most other scripts have been enqueued.

Critical CSS Generation and Inline Injection

Critical CSS refers to the CSS required to render the “above-the-fold” content of a webpage. Inlining this CSS directly into the HTML’s `` section dramatically improves perceived performance by allowing the browser to render the initial viewport content without waiting for external CSS files to download. The remaining CSS can then be loaded asynchronously.

Generating critical CSS programmatically can be complex, often involving headless browsers or specialized tools. For a WordPress context, a common approach is to use a server-side process or a build step to generate critical CSS for key templates and then use a WordPress hook to inject it.

Server-Side Generation and `wp_head` Injection

Assume you have a process (e.g., a Gulp task, a Node.js script, or a dedicated service) that generates critical CSS files for different page types (e.g., `critical-home.css`, `critical-post.css`). You can then use the `wp_head` action hook to dynamically load the appropriate critical CSS.

add_action( 'wp_head', 'my_inline_critical_css', 0 ); // High priority to ensure it's first in head

function my_inline_critical_css() {
    $critical_css_file = '';

    // Determine which critical CSS file to load based on the current page.
    if ( is_front_page() ) {
        $critical_css_file = get_template_directory() . '/css/critical-home.css';
    } elseif ( is_single() ) {
        $critical_css_file = get_template_directory() . '/css/critical-post.css';
    } elseif ( is_page() ) {
        $critical_css_file = get_template_directory() . '/css/critical-page.css';
    }
    // Add more conditions for archives, WooCommerce, etc.

    if ( ! empty( $critical_css_file ) && file_exists( $critical_css_file ) ) {
        $css_content = file_get_contents( $critical_css_file );
        if ( $css_content ) {
            echo '<style type="text/css">' . "\n";
            echo '/* Critical CSS */' . "\n";
            echo $css_content;
            echo '</style>' . "\n";
        }
    }
}

This function checks for specific page types and attempts to load a corresponding critical CSS file. `file_get_contents()` reads the CSS, and it’s then echoed within a `