HomeMethod

Knowledge

Web VitalsTechniquesSnippetsAnswersImpactsBottlenecks

JavaScript Concatenation

Merge multiple JavaScript files into one bundle to cut HTTP request overhead.

Overview

JavaScript concatenation merges individual script files into a single bundle. The browser makes one HTTP request instead of many, eliminating the connection setup overhead that stacks up when loading scripts from a theme, 10–20 plugins, and custom additions.

Impact grade, A+ to D: how much of the Lighthouse score this technique can move. Unaffected: CLS, TTFB.

The Problem

Like CSS, multiple JavaScript files each require their own HTTP request. Unlike CSS, many scripts in WordPress are render-blocking without defer or async — they halt HTML parsing until downloaded and executed. With many scripts, the cumulative blocking time can easily exceed one second even on a fast connection.

Performance Impact

LCP

Indirect

less main thread contention during the LCP render window

TBT

Moderate

fewer blocking requests = shorter total blocking window

FCP

Moderate

combined script loads faster than many separate scripts

SI

fewer script requests reduces round trips before execution begins, slightly improving visual completion time

LCP

+50–200ms

the entire bundle must download before any script executes, delaying render

TBT

+100–500ms

one large bundle blocks the main thread longer than several smaller parallel files

Risk: High

dependency order and script isolation issues are common

How it works

  1. 01

    Identify and order all enqueued scripts

    Scripts must be concatenated in dependency order — jQuery must appear before any jQuery plugin. The concatenation process reads the dependency graph from WordPress’s wp_scripts object or from the rendered HTML.

  2. 02

    Combine content into a single file

    Each script’s source content is appended in order, typically with a semicolon separator between files to prevent accidental syntax merging (e.g., a file ending without a semicolon before an IIFE in the next file).

  3. 03

    Save with a hash filename for cache busting

    The combined content is hashed and saved as a uniquely named file (e.g., combined-a3f2b1.js). When any source file changes, the hash changes — forcing browsers to download the new bundle while still caching unchanged versions.

  4. 04

    Replace individual script tags with one reference

    The original <script> tags are removed from the HTML and replaced with a single <script src="combined.js">. Combined with defer, this delivers all JavaScript in one non-blocking download.

HTML buffering approach — PHP

Code
<?php
add_action( 'template_redirect', function() {
    ob_start( 'combine_js_from_html' );
} );

function combine_js_from_html( $html ) {
    libxml_use_internal_errors( true );
    $dom = new DOMDocument();
    $dom->loadHTML( $html, LIBXML_HTML_NOIMPLIED | LIBXML_HTML_NODEFDTD );
    $xpath   = new DOMXPath( $dom );
    $combined = '';

    foreach ( $xpath->query( '//script[@src]' ) as $node ) {
        $src   = strtok( $node->getAttribute( 'src' ), '?' );
        $local = ABSPATH . ltrim( str_replace( site_url(), '', $src ), '/' );
        if ( file_exists( $local ) ) {
            $combined .= file_get_contents( $local ) . ";
";
        }
        $node->parentNode->removeChild( $node );
    }

    if ( ! trim( $combined ) ) return $html;

    $hash     = md5( $combined );
    $dir      = wp_upload_dir();
    $filename = "combined-{$hash}.js";
    $path     = trailingslashit( $dir['basedir'] ) . $filename;
    if ( ! file_exists( $path ) ) file_put_contents( $path, $combined );

    $script = $dom->createElement( 'script' );
    $script->setAttribute( 'src', trailingslashit( $dir['baseurl'] ) . $filename );
    $script->setAttribute( 'defer', '' );
    $dom->getElementsByTagName( 'body' )->item( 0 )->appendChild( $script );

    return $dom->saveHTML();
}

Scripts with relative asset paths (e.g., ../images/sprite.png) will break when concatenated, because the combined file’s base URL differs from the original. Audit scripts for relative paths before enabling.

Limitations

  • If jQuery appears after a jQuery plugin in the combined file, the plugin will throw reference errors. The concatenation order must exactly match the original dependency graph — wrong order silently breaks functionality.
  • Some scripts load additional resources using paths relative to the script file’s location. When concatenated into a different directory, these relative paths resolve incorrectly. Inspect scripts for document.currentScript, import(), or relative resource paths before combining.
  • HTTP/2 allows multiple files to transfer concurrently over one connection. On HTTP/2 servers the latency advantage of concatenation shrinks — though compression ratio and header overhead improvements remain.

Common Mistakes

Combining third-party CDN scripts into the local bundle

Scripts hosted on external CDNs (Google Analytics, Stripe, etc.) may have their own update cycles, licences, or rely on cross-origin loading. Leave them separate and apply async/defer instead of bundling them locally.

Not testing across browsers and devices after enabling

JavaScript errors caused by concatenation (wrong order, broken relative paths, conflicting globals) can be intermittent and device-specific. Always test interaction-heavy flows — forms, sliders, cart — after enabling concatenation.

Combining scripts without also applying defer or async

Concatenation reduces request count but does not eliminate blocking. A large combined script without defer is still render-blocking. Pair concatenation with defer for maximum FCP benefit.

Test this techniqueScripts DiagnosticsRun the tool

Need help applying this to your site?

Targeted fixes, not guesswork. We identify which techniques move the needle for your specific WordPress stack.

Start an Engagement
Support our family now