Finding the real WordPress performance bottleneck

Got an email the other day. Subject line: “Our site is SUPER slow.” Every developer gets this kind of message: vague, urgent, and you can tell the client is at the end of their rope. This is where real WordPress performance troubleshooting starts, and it’s rarely a matter of installing another caching plugin. The client said pages were taking 5-7 seconds to load, which is painful for any business.

My first instinct, and this is where I wasted a solid hour, was to hunt for a monster query. I figured some junior dev had probably stuck a get_posts() call inside a loop, the classic N+1 problem. So I dug through theme files, running grep commands, looking for that obvious, ugly piece of code. I found a few things that weren’t perfect, but nothing that would justify a 7-second page load. Dead end, and a humbling one.

Stop guessing and start measuring

When you’re stuck, go back to basics. The first step is to open your browser’s developer tools and look at the “Network” tab. Forget everything else for a minute and watch one number: the Time To First Byte (TTFB). It’s that first long bar in the waterfall. If your server takes 4-5 seconds just to start sending the page, no amount of frontend optimization will fix it. That’s a server problem.

In this client’s case the TTFB was very high. That told me the problem wasn’t images, CSS, or JavaScript loading; the bottleneck was PHP or the database. My next move is always the same: install the Query Monitor plugin. If you build WordPress sites without it, you’re working blind. It’s the most useful debugging tool I know of, something I first picked up from a good overview at carlalexander.ca.

The cause wasn’t a slow database query at all. Query Monitor has a section for “HTTP API Calls,” and there it was: a social media feed plugin making an external API call on every single page load. The external service was slow to respond, so it held up the entire page render for seconds, with no caching and no timeout.

The simple fix for external API calls

Deleting the plugin wasn’t an option; the client needed it. The answer was to cache the result of that API call. The WordPress Transients API is built for this: it stores the result in the database for a set amount of time. Instead of hitting the slow service on every page load, you hit it once an hour.

function get_cached_social_feed() {
    // Check for the cached data first
    $feed_data = get_transient( 'my_social_feed' );

    // If the cache is empty, go get the data
    if ( false === $feed_data ) {
        $response = wp_remote_get( 'https://api.example.com/slow-feed/' );

        if ( is_wp_error( $response ) || 200 !== wp_remote_retrieve_response_code( $response ) ) {
            return false; // Bail if the request failed
        }

        $feed_data = wp_remote_retrieve_body( $response );

        // Cache the result for 1 hour
        set_transient( 'my_social_feed', $feed_data, HOUR_IN_SECONDS );
    }

    return $feed_data;
}

The takeaway

The point is to have a process instead of guessing or blaming the host. Nine times out of ten the bottleneck is right there in the code, often a third-party plugin making an uncached request. My process runs like this:

  • Check the browser Network tab to identify where the delay is (frontend vs. TTFB).
  • If TTFB is high, use Query Monitor to inspect database queries and HTTP API calls on the server.
  • Once you find the slow part, fix it at the source. Often, that means caching data with transients.
author avatar
Ahmad Wael
I'm a WordPress and WooCommerce developer with 15+ years of experience building custom e-commerce solutions and plugins. I specialize in PHP development, following WordPress coding standards to deliver clean, maintainable code. Currently, I'm exploring AI and e-commerce by building multi-agent systems and SaaS products that integrate technologies like Google Gemini API with WordPress platforms, approaching every project with a commitment to performance, security, and exceptional user experience.