Enqueing Scripts…
Enqueuing scripts is the standardised framework WordPress uses to manage and load JavaScript files across a website. By channeling all code through a centralised registration queue rather than hardcoding tags, it prevents duplicate files, preserves site performance, and cleanly maps out script dependencies.
It’s tempting, especially early on, to enqueue a script once in functions.php and let it load on every single page. It’s simple, it works, and nothing looks obviously wrong. The problem is that “nothing looks obviously wrong” is exactly the trap — every unnecessary script your browser downloads, parses, and executes is time you’re not getting back, on pages where that script was never actually going to do anything.
Getting this right can appear daunting as it seems complicated, but all it means is asking one extra question every time you add a script or style: where does this actually need to run? Here’s how that played out in practice for me, using real examples from the BMCS rebuild rather than hypothetical ones.
I must point out that I did ask for AI assistance in doing this task (and now, with the explanations) and I’m happy I did. Whilst I was not far off the mark it did flag some errors and knowledge gaps I had from previous attempts to get this right.
The basics: dependencies and cache-busting
Before getting into scoping, two habits worth having on every enqueue, illustrated by how GSAP loads site-wide:
Scoping is the set of rules that decides where specific variables, functions, or objects can be seen and used within your code. By creating clear boundaries—dividing code into public (global) or private (local) spaces—it keeps different parts of your program from accidentally interfering with each other, protects your data, and keeps your code organised.
php
function bmcs_enqueue_gsap_scripts() {
wp_enqueue_script(
'gsap',
'https://cdnjs.cloudflare.com/ajax/libs/gsap/3.12.5/gsap.min.js',
array(),
'3.12.5',
true // Load in footer
);
wp_enqueue_script(
'gsap-scrolltrigger',
'https://cdnjs.cloudflare.com/ajax/libs/gsap/3.12.5/ScrollTrigger.min.js',
array( 'gsap' ), // Depends on GSAP core
'3.12.5',
true
);
$js_file = get_stylesheet_directory() . '/js/gsap-container-scroll-effects.js';
$version = file_exists( $js_file ) ? filemtime( $js_file ) : '1.0.0';
wp_enqueue_script(
'bmcs-scroll-effects',
get_stylesheet_directory_uri() . '/js/gsap-container-scroll-effects.js',
array( 'gsap', 'gsap-scrolltrigger' ),
$version,
true
);
}
add_action( 'wp_enqueue_scripts', 'bmcs_enqueue_gsap_scripts' );
Two things worth noticing here. First, the dependency array (array( ‘gsap’ ), array( ‘gsap’, ‘gsap-scrolltrigger’ )) tells WordPress to load these scripts in the correct order automatically — I don’t have to worry about my custom scroll effects file trying to run before GSAP itself has loaded. Second, the version number for my own script isn’t hardcoded — it’s pulled from the file’s last-modified time with filemtime(). That means every time I edit the file, the version string changes automatically, and browsers/caches pick up the new version without me needing to remember to bump a number by hand. GSAP itself does load on every page here, since animations built with it appear across most of the site — but everything downstream of it is scoped much more tightly, which is where the real savings happen.
Scoping to a specific page type
The homepage has its own animation file that has no reason to load anywhere else:
php
function bmcs_home_page_scripts() {
if ( is_front_page() || is_home() ) {
$js_file = get_stylesheet_directory() . '/js/home-animations.js';
$version = file_exists( $js_file ) ? filemtime( $js_file ) : '1.0.0';
wp_enqueue_script(
'bmcs-home-animations',
get_stylesheet_directory_uri() . '/js/home-animations.js',
array(),
$version,
true
);
}
}
add_action( 'wp_enqueue_scripts', 'bmcs_home_page_scripts' );
Nothing complicated — just a conditional wrapped around the enqueue. But it means every other page on the site skips downloading and parsing a file that would never run anything on them anyway. Multiply that across a site with a handful of page-specific scripts, and it adds up to a meaningfully lighter load everywhere except where it’s actually needed.
Scoping to a custom post type
My portfolio single pages (a custom post type) use their own font stylesheet for a specific content purpose, which has no business loading on a feature article or a category archive:
php
function bmcs_enqueue_portfolio_fonts() {
if ( is_singular( 'portfolio' ) ) {
wp_enqueue_style( 'bmcs-portfolio-fonts', get_stylesheet_directory_uri() . '/css/portfolio-fonts.css', [], '1.9' );
}
}
add_action( 'wp_enqueue_scripts', 'bmcs_enqueue_portfolio_fonts' );
The pattern’s identical to the homepage example — a conditional around the enqueue — but it’s worth flagging is_singular( ‘portfolio’ ) specifically, since it’s easy to reach for is_single() here instead and get it subtly wrong. is_single() checks the post slug or title, not the post type, so it won’t reliably catch every portfolio item the way is_singular( ‘portfolio’ ) does. Small distinction, easy mistake, and one that’s invisible until you’re troubleshooting why a style isn’t loading somewhere it should be.
Scoping to archive types, with an exclusion
Through the setup stage I only needed Astra’s live search to run on category archive pages (necessary in case there was no available content):
php
add_action( 'wp_enqueue_scripts', function() {
if ( is_category() ) {
wp_enqueue_script(
'astra-live-search',
get_template_directory_uri() . '/assets/js/minified/live-search.min.js',
array(),
wp_get_theme()->get( 'Version' ),
true
);
wp_localize_script(
'astra-live-search',
'astra_search',
array(
'rest_api_url' => get_rest_url(),
// ...additional config
)
);
}
} );
wp_localize_script() here is worth a mention on its own — it’s how PHP hands data (in this case, the REST API URL) across to JavaScript safely, rather than trying to echo values directly into inline <script> tags. It only runs when the live search script itself is actually enqueued, which keeps that data out of the page entirely when it isn’t needed.
The same “only where needed” thinking extends past scripts and styles too. The animated canvas background effect on my archive pages (the black hole) is explicitly excluded from the portfolio archive as I want that to use the main site background of the bouncing blobs:
php
if ( is_archive() && ! is_post_type_archive( 'portfolio' ) ) {
// load the effect
}
And the CDN preconnect hint for the library those effects depend on follows the identical condition — so the browser doesn’t even open an early connection to a domain it won’t need on the one archive type that opts out. Same logic, applied consistently across three different layers: the script, the DOM element it needs, and the network hint that speeds up loading it in the first place.
CDN preconnect is a performance-optimisation technique that instructs a web browser to set up an early network connection to a Content Delivery Network (CDN). By handling the initial handshake steps—such as resolving the domain name and establishing secure communication—in the background, it clears a direct path for the browser to download images, fonts, or scripts instantly the moment they are requested later.
Why this is worth the extra five minutes
None of these conditionals are complicated on their own. The value isn’t in any single one — it’s in treating “where does this need to run?” as a default question rather than an optional optimisation you get to later. Every script that only loads where it’s actually used is one less thing competing for parse time, execution time, and — on a canvas-heavy background effect running requestAnimationFrame in a loop — genuine CPU cycles on pages that never even see it.
This connects directly back to Getting Your WordPress Setup Right, From Day One… — conditional loading isn’t something you retrofit easily once a site has grown and you’ve lost track of what’s actually enqueued where. It’s far easier to build the habit in from the first script you add than to audit fifteen of them later and work out which ones are quietly loading everywhere.
A quick mental checklist before you enqueue anything
- Does this need to load on every page, or only a specific page/post type/archive?
- If it’s scoped, am I using the right conditional (is_singular() vs is_single(), is_post_type_archive() vs is_archive())?
- Have I declared the correct dependencies, so load order is handled automatically?
- Is the version number dynamic (filemtime()) or will I forget to bump it by hand?
- If related assets exist alongside the script (DOM markup, preconnect hints, localized data), are they scoped with the same condition, or could they drift out of sync over time?
