The script that activates the search option on Savvy Blog is loaded only when the user clicks on the search icon in the top bar. The functionality of the search is not particularly complex, but do you see a reason to load it when the visitor doesn’t intend to use the blog’s search option at all?
The purpose of this post is to demonstrate a technique for scripts that are much more complex. This way, you can significantly reduce the page weight and shorten the time required for JavaScript execution.
Consequently, you can improve loading time and achieve better Core Web Vitals metrics, especially Interaction to Next Paint (INP) which is directly affected by how much JavaScript runs on the page.
Before implementing on-demand loading, consider whether simpler approaches like deferring JavaScript with the defer or async attribute would be sufficient. On-demand loading is best for scripts tied to specific user interactions.
When to Use On-Demand Script Loading
There are three main strategies for loading non-critical JavaScript:
deferattribute – Downloads in the background and executes after HTML parsing completes, maintaining execution order. Best for most scripts that interact with the DOM.asyncattribute – Downloads in the background and executes immediately when ready, in no particular order. Best for independent scripts like analytics.- On-demand injection – Loads scripts only when a specific user interaction or event occurs. Best for features that most visitors never use.
This post focuses on the third approach. It is the most effective for Core Web Vitals because it completely removes unused JavaScript from the initial page load.
The loadScript Function
For our purposes, to use this Lazy Loading technique, let’s define a function called loadScript:
function loadScript(url) {
let isLoaded = document.querySelectorAll('.search-script');
if(isLoaded.length > 0) {
return;
}
let myScript = document.createElement("script");
myScript.src = url;
myScript.className = 'search-script';
document.body.appendChild(myScript);
}The function first checks if the script is already loaded. If it is, the function simply ends without trying to load the script multiple times.
Then, we create an element of type script using document.createElement and pass the URL that will be used for the src attribute of the lazily loaded script.
We add the relevant class for the previous check (whether the script is already loaded or not), and then append the script to the body element of the page.
Triggering the Script on User Interaction
The last step is to define an event listener that listens for a click on the search icon. When this happens, we call the loadScript function with the URL of our script.
let searchInput = document.querySelector('#trigger-overlay');
searchInput.addEventListener('click', function (e) {
loadScript('/path/to/search.js');
});In the case of this blog, I’m also displaying the search popup before calling the script using loadScript.
We saved loading a specific script that doesn’t need to be loaded all the time.
By the way, the WP-Rocket plugin has a similar option called Delay Javascript Execution. It works slightly differently – it is based on any user interaction on the site, not a specific interaction you can define.
You can also exclude specific scripts from WP Rocket’s deferred loading when needed.
Loading Scripts with Intersection Observer
Another powerful approach is loading scripts when a specific element becomes visible in the viewport. This uses the Intersection Observer API:
function loadScriptOnVisible(selector, url) {
const target = document.querySelector(selector);
if (!target) return;
const observer = new IntersectionObserver((entries) => {
entries.forEach((entry) => {
if (entry.isIntersecting) {
loadScript(url);
observer.disconnect();
}
});
});
observer.observe(target);
}This is useful for scripts tied to elements below the fold, like maps, comment sections, or embedded widgets. The script only loads when the user scrolls to that section.
Adding a Callback to loadScript
The basic loadScript function has no way to know when the script has finished loading. You can add a callback or return a Promise:
function loadScript(url) {
return new Promise((resolve, reject) => {
const isLoaded = document.querySelector(`script[src="${url}"]`);
if (isLoaded) {
resolve();
return;
}
const script = document.createElement('script');
script.src = url;
script.onload = resolve;
script.onerror = reject;
document.body.appendChild(script);
});
}Now you can chain actions that depend on the script being loaded:
searchInput.addEventListener('click', async function() {
await loadScript('/path/to/search.js');
initializeSearch();
});WordPress Integration
If you are working with WordPress, you will want to properly enqueue your scripts and then dequeue them from the normal loading flow. You can use wp_deregister_script to prevent the default loading and then load the script on demand using the technique shown above.
For a broader approach to reducing JavaScript on specific pages, consider using Plugin Organizer to selectively load plugins only where they are needed.
FAQs
Common questions about lazy loading JavaScript:
Summary
Lazy loading JavaScript on demand is one of the most effective ways to improve page performance. By loading scripts only when a specific user interaction occurs – like a click, scroll, or element becoming visible – you eliminate unnecessary JavaScript from the initial page load.
This directly benefits Core Web Vitals, especially INP and LCP. Combine this technique with deferred JavaScript loading for scripts that must load on every page, and you have a comprehensive strategy for JavaScript optimization.

