• 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 » Reducing database query bloat in Understrap styling structures layouts using custom lazy loaders

Reducing database query bloat in Understrap styling structures layouts using custom lazy loaders

Diagnosing Understrap Query Bloat in WooCommerce

Understrap, a popular WordPress starter theme, often forms the foundation for custom WooCommerce themes. While flexible, its reliance on WordPress’s core query mechanisms, especially when combined with WooCommerce’s product display logic, can lead to significant database query bloat. This bloat manifests as slow page load times, increased server resource consumption, and a degraded user experience, particularly on product listing pages, category archives, and even single product pages where related products or upsells are displayed.

The primary culprits are often multiple, redundant `WP_Query` calls, especially those executed within loops that render product grids or lists. WooCommerce itself adds numerous queries for product attributes, variations, reviews, and related items. When these are layered on top of standard WordPress post queries for content, the database becomes a bottleneck. Identifying these queries is the first critical step. We’ll leverage the Query Monitor plugin for this, as it provides unparalleled insight into the queries executed on any given page.

Using Query Monitor for Deep Dives

Install and activate the Query Monitor plugin. Navigate to a WooCommerce product archive page (e.g., your main shop page or a category page). Scroll to the bottom of the page to reveal the Query Monitor panel. Focus on the “Queries” tab. You’ll see a breakdown of all SQL queries executed, their execution time, and the function/hook that triggered them. Look for:

  • Queries with high execution times.
  • Repeated identical queries.
  • Queries related to product loops, meta data retrieval, and taxonomy terms.
  • Queries triggered by hooks like woocommerce_after_shop_loop, woocommerce_before_shop_loop, or within template files like archive-product.php or content-product.php.

For instance, you might observe multiple queries fetching product IDs for related products, or repeated calls to wp_get_post_terms for product categories and tags within a single loop iteration. This is the “bloat” we aim to eliminate.

Implementing a Custom Lazy Loader for Product Queries

The strategy is to defer the execution of non-critical product-related queries until they are actually needed, typically when the user scrolls down the page and the relevant content enters the viewport. This requires a JavaScript-driven approach combined with WordPress’s AJAX capabilities.

Step 1: Enqueueing Custom Scripts

First, we need to enqueue our custom JavaScript file and localize it with necessary data, such as the AJAX URL and nonce for security.

/**
 * Enqueue custom scripts for lazy loading.
 */
function my_custom_lazy_loader_scripts() {
    // Only enqueue on relevant WooCommerce pages (e.g., shop, product archives)
    if ( is_woocommerce() || is_product_category() || is_product_tag() ) {
        wp_enqueue_script(
            'my-lazy-loader',
            get_stylesheet_directory_uri() . '/js/lazy-loader.js',
            array( 'jquery' ),
            '1.0.0',
            true // Load in footer
        );

        wp_localize_script(
            'my-lazy-loader',
            'myLazyLoaderAjax',
            array(
                'ajax_url' => admin_url( 'admin-ajax.php' ),
                'nonce'    => wp_create_nonce( 'my_lazy_loader_nonce' ),
            )
        );
    }
}
add_action( 'wp_enqueue_scripts', 'my_custom_lazy_loader_scripts' );

Step 2: JavaScript for Intersection Observer and AJAX

Create a JavaScript file (e.g., js/lazy-loader.js) in your child theme’s directory. This script will use the Intersection Observer API to detect when a designated “lazy load trigger” element enters the viewport. Upon detection, it will initiate an AJAX request to fetch the additional product data.

jQuery(document).ready(function($) {
    // Select all elements that should trigger lazy loading
    $('.lazy-load-trigger').each(function() {
        var $triggerElement = $(this);
        var observer = new IntersectionObserver(function(entries, observer) {
            entries.forEach(function(entry) {
                if (entry.isIntersecting) {
                    // Element is in the viewport, trigger AJAX
                    var postId = $triggerElement.data('post-id'); // Assuming trigger has a data-post-id
                    var loadType = $triggerElement.data('load-type'); // e.g., 'related-products', 'upsells'

                    // Prevent multiple loads for the same trigger
                    $triggerElement.removeClass('lazy-load-trigger');
                    observer.unobserve(entry.target);

                    // Perform AJAX request
                    $.ajax({
                        url: myLazyLoaderAjax.ajax_url,
                        type: 'POST',
                        data: {
                            action: 'load_more_product_data', // WordPress AJAX action hook
                            nonce: myLazyLoaderAjax.nonce,
                            post_id: postId,
                            load_type: loadType,
                            // Add any other necessary parameters, e.g., current page number for pagination
                        },
                        beforeSend: function() {
                            // Optional: Show a loading indicator
                            $triggerElement.after('
Loading...
'); }, success: function(response) { if (response.success) { // Append the loaded content $triggerElement.after(response.data.html); } else { console.error('AJAX Error:', response.data.message); } }, complete: function() { // Remove loading indicator $('.loading-indicator').remove(); }, error: function(jqXHR, textStatus, errorThrown) { console.error('AJAX Request Failed:', textStatus, errorThrown); } }); } }); }, { root: null, // relative to the viewport rootMargin: '0px', threshold: 0.1 // Trigger when 10% of the element is visible }); // Start observing the trigger element observer.observe(this); }); });

Step 3: WordPress AJAX Handler

Now, create the PHP function in your child theme’s functions.php file to handle the AJAX request. This function will perform the specific query that was previously causing bloat and return the HTML for the requested content.

/**
 * AJAX handler for loading additional product data.
 */
function my_load_more_product_data_callback() {
    check_ajax_referer( 'my_lazy_loader_nonce', 'nonce' );

    $post_id   = isset( $_POST['post_id'] ) ? intval( $_POST['post_id'] ) : 0;
    $load_type = isset( $_POST['load_type'] ) ? sanitize_key( $_POST['load_type'] ) : '';
    $html      = '';

    if ( $post_id && ! empty( $load_type ) ) {
        // Example: Loading related products
        if ( 'related-products' === $load_type ) {
            // Temporarily override global $product to ensure WC functions work correctly
            global $product;
            $original_product = $product;

            $product = wc_get_product( $post_id );
            if ( $product ) {
                // Use WooCommerce's built-in function to get related products
                // This function itself might perform queries, but we're loading it ON DEMAND
                // and potentially optimizing its output or the loop that displays it.
                ob_start();
                woocommerce_output_related_products();
                $html = ob_get_clean();
            }

            // Restore global $product
            $product = $original_product;

        }
        // Example: Loading upsells
        elseif ( 'upsells' === $load_type ) {
            global $product;
            $original_product = $product;

            $product = wc_get_product( $post_id );
            if ( $product ) {
                ob_start();
                woocommerce_upsell_display();
                $html = ob_get_clean();
            }

            $product = $original_product;
        }
        // Add more load_type cases as needed (e.g., 'cross-sells', custom product queries)

        if ( ! empty( $html ) ) {
            wp_send_json_success( array( 'html' => $html ) );
        } else {
            wp_send_json_error( array( 'message' => 'No content found for the requested type.' ) );
        }
    } else {
        wp_send_json_error( array( 'message' => 'Invalid request parameters.' ) );
    }

    wp_die(); // This is crucial for AJAX handlers
}
add_action( 'wp_ajax_load_more_product_data', 'my_load_more_product_data_callback' );
add_action( 'wp_ajax_nopriv_load_more_product_data', 'my_load_more_product_data_callback' ); // For logged-out users

Step 4: Modifying Theme Templates

Finally, you need to modify your Understrap child theme’s WooCommerce template files (or override WooCommerce templates directly) to include the “lazy load trigger” elements where you previously had the bloat-inducing queries. For example, in your single-product.php or a custom template part for related products:

<?php
/**
 * Example: Displaying related products section on single product page.
 * This replaces the direct call to woocommerce_output_related_products()
 * that might have been here before.
 */

// Get the current product ID
$current_product_id = get_the_ID();

// Check if related products exist (optional pre-check to avoid unnecessary trigger)
// This pre-check itself might query, so balance performance vs. UX.
// For maximum optimization, you might omit this and let the AJAX handle empty states.
$related_products = wc_get_related_products( $current_product_id, 4 ); // Example: check for 4 related products

if ( ! empty( $related_products ) ) :
    // Add a placeholder element that the JavaScript will watch.
    // The 'data-post-id' and 'data-load-type' attributes are crucial for the JS.
    ?>
    <div class="lazy-load-trigger" data-post-id="<?php echo esc_attr( $current_product_id ); ?>" data-load-type="related-products">
        <!-- This div will be replaced by the loaded content -->
        <!-- You can add a placeholder graphic or text here if desired -->
        <p>Loading related products...</p>
    </div>
    <?php
endif;
?>

Similarly, for product archives, you might have a trigger at the bottom of the initial product loop to load more products via AJAX (pagination) or other related content sections.

Advanced Considerations and Optimizations

Caching Strategies

While lazy loading defers execution, it doesn’t inherently cache the results. For frequently accessed, non-personalized content (like related products for a popular item), consider implementing object caching (e.g., Redis, Memcached) or transient API caching within your AJAX handler to store the generated HTML. This prevents repeated database queries even after the AJAX call is made.

// Inside my_load_more_product_data_callback function, before returning:

$cache_key = 'product_data_' . $post_id . '_' . $load_type;
$cached_html = get_transient( $cache_key );

if ( false === $cached_html ) {
    // ... (generate $html as before) ...
    if ( ! empty( $html ) ) {
        set_transient( $cache_key, $html, DAY_IN_SECONDS ); // Cache for 1 day
        wp_send_json_success( array( 'html' => $html ) );
    } else {
        wp_send_json_error( array( 'message' => 'No content found for the requested type.' ) );
    }
} else {
    wp_send_json_success( array( 'html' => $cached_html ) );
}

Selective Query Optimization

Not all queries are candidates for lazy loading. Core product data (like the main product details on a single page) must load immediately. Focus lazy loading on elements that are secondary to the primary content: related products, upsells, cross-sells, recent reviews, or even secondary product grids on archive pages. For queries that *must* run initially but are still slow, investigate direct SQL optimization, using `WP_Query` arguments more efficiently (e.g., `fields = ‘ids’` when only IDs are needed), or leveraging WordPress transients.

JavaScript Performance

Ensure your JavaScript is as efficient as possible. The Intersection Observer API is generally performant. However, if you have hundreds of trigger elements, consider debouncing or throttling your observer callbacks, or implementing a “load more” button instead of pure scroll-based triggering for extreme cases. Minify and combine your JavaScript files for production.

Accessibility and Fallbacks

Always provide a fallback for users with JavaScript disabled. This means ensuring that the content you intend to lazy load is still present in the initial HTML, perhaps hidden by CSS, or that the page remains functional without it. For example, if related products are critical for discovery, they should ideally be present in the initial render, even if it means a slightly higher initial query count. The lazy loader then becomes an *enhancement* rather than a necessity.

By strategically applying lazy loading to non-essential, query-intensive sections of your Understrap-based WooCommerce site, you can significantly reduce initial page load times and database pressure, leading to a faster, more responsive e-commerce experience.

Primary Sidebar

A little about the Author

Having 12+ Years of Experience in Software Development, Vinay is a principal software architect, senior systems engineer, and elite technical consultant. He specializes in bespoke PHP/WordPress development, high-performance Magento 2 & Shopify architectures, custom plugin/theme development from scratch, and legacy code modernization (including VB6, VB.NET, PyQt, and Crystal Reports). Known for solving complex database bottlenecks, speed optimization (Core Web Vitals), and advanced security code auditing, Vinay engineers production-ready systems designed to scale under heavy concurrent load conditions.



Chat on WhatsApp

Recent Posts

  • Reducing database query bloat in Sage Roots modern environments layouts using custom lazy loaders
  • Performance Optimization: Tuning PHP-FPM and opcache pools for high-concurrency Firebase Realtime DB handlers
  • Reducing Largest Contentful Paint (LCP) by optimizing custom script enqueuing structures in legacy plugins
  • How to implement native Redis caching layers for high-volume custom taxonomy queries in Carbon Fields custom wrappers
  • Building secure B2B pricing grids with custom REST API Controllers endpoints and role overrides

Categories

  • apache (1)
  • Business & Monetization (390)
  • Centos (4)
  • Comparisons & Decision Making (55)
  • Debian (2)
  • Debugging & Troubleshooting (658)
  • Desktop Applications (14)
  • DevOps (7)
  • DevOps & Cloud Scaling (962)
  • Django (1)
  • Laravel (4)
  • Migration & Architecture (192)
  • Mobile Applications (24)
  • MySQL (1)
  • Performance & Optimization (872)
  • PHP (5)
  • PHP Development (48)
  • Plugins & Themes (244)
  • Programming Languages (9)
  • Python (20)
  • Ruby on Rails (1)
  • Security & Compliance (639)
  • SEO & Growth (492)
  • Server (23)
  • Ubuntu (9)
  • VB6 & VB.NET (8)
  • Web Applications & Frontend (19)
  • Web Assembly (Wasm) (2)
  • WordPress (22)
  • WordPress Plugin Development (182)
  • WordPress Plugin Development (197)
  • WordPress Plugin Development (330)
  • WordPress Theme Development (357)

Recent Posts

  • Reducing database query bloat in Sage Roots modern environments layouts using custom lazy loaders
  • Performance Optimization: Tuning PHP-FPM and opcache pools for high-concurrency Firebase Realtime DB handlers
  • Reducing Largest Contentful Paint (LCP) by optimizing custom script enqueuing structures in legacy plugins

Top Categories

  • DevOps & Cloud Scaling (962)
  • Performance & Optimization (872)
  • Debugging & Troubleshooting (658)
  • Security & Compliance (639)
  • SEO & Growth (492)
  • Business & Monetization (390)

Our Products

  • ERP & LMS Systems (4)
  • Directories & Marketplaces (4)
  • Healthcare Portals (3)
  • Point of Sale (POS) (2)
  • E-Commerce Engines (2)

Our Services

  • E-Commerce Development (10)
  • WordPress Development (8)
  • Python & Desktop GUI (7)
  • General Consulting (7)
  • Legacy Modernization (5)
  • Mobile App Development (4)

Copyright © 2026 · Vinay Vengala