No More jQuery
I take a peek at life without jQuery and boost my website's PageSpeed Insight score by a massive 2 points!
jQuery is great. It offers handy features like DOM manipulation, AJAX, and animations with bad performance. I’ve used jQuery since my early days of frontend development because it made things easier, and there were always tons of plugins for it to explore.
For many tasks that jQuery handled in a single line, browsers lacked an equivalent. And that’s not even considering cross-browser compatibility — jQuery unified all browsers under one codebase. You didn’t need to write code differently for Safari and Firefox to get the same results.
It also made code more readable and understandable. Take AJAX, for example, which allows asynchronous data exchange between the browser and server. Here’s how to send form data and handle the server’s response:
// Post form data in native JavaScript via XMLHttpRequest
document.getElementById('button-submit').addEventListener('click', function(e){
var formData = new FormData(document.getElementById('form-signin'));
var xhr = new XMLHttpRequest();
xhr.open('POST', 'https://example.com/signin');
xhr.onreadystatechange = function(){
if(xhr.readyState === 4 && xhr.status === 200){
console.log(xhr.responseText); // When all went well
}
}
xhr.send(formData);
});
Now, here’s the jQuery equivalent:
// Post form data via jQuery
$('#button-submit').click(function(e){
$.post('https://example.com/signin', $('#form-signin').serialize())
.success(function(data){
console.log(data); // When all went well
});
});
The jQuery version is not only shorter but also easier to follow due to its clean API, which clearly shows what happens and when. Plus, jQuery automatically detects XML or JSON responses, giving you a JavaScript object to work with — no manual parsing needed.
In general, jQuery is a win-win: it saves you from reinventing the wheel and offers concise solutions for common tasks. It’s also so widely used that linking to it via a popular CDN, like Google’s, makes caching easy.
However, my website still didn’t score a perfect 100 on Google PageSpeed Insights for mobile. Sure, 98 is nearly perfect, but for a lightweight site like mine, it should be able to hit 100. So, I set out to find a solution.
Searching For a Solution
I quickly realized that jQuery was taking longer to load from Google’s servers than the whole rest of the page. The somewhat outdated version 3.3.1 is about 30KB compressed (85KB uncompressed). 85KB is not a trivial amount of JavaScript for the browser to parse, especially if we’re only using small parts of it! In addition, using jQuery from Google’s CDN adds extra overhead from the DNS request and SSL handshake.
One solution is to host jQuery myself. Or better yet, not use it at all. The latter solves both problems: no jQuery means no overhead downloading it, and no large script to parse.
GitHub famously removed jQuery from its frontend and replaced its functions with native JavaScript. So, I decided to also give life without jQuery a try.
Replacing jQuery
But jQuery has its perks! True, but modern browsers have evolved, and the features I relied on now ship in browsers from the get-go. For the rest, there are smaller libraries and wrappers that are much lighter than jQuery. I’m essentially tree shaking it at this point.
What I appreciated most about jQuery was the ease of event handling, DOM selection, class utilities, and AJAX. So let’s take a look at replacing these features.
Events
jQuery’s API for event handling is excellent, especially for binding multiple events to elements and doing event delegation. For instance, I want to add a red border to empty text input fields using a CSS class.
Here’s the jQuery version:
// Defining multiple events on multiple elements via jQuery
$('#name, #message').on('change input blur', function(e){
var el = $(e.target);
if(el.val() === ''){
el.addClass('form-control-error');
}else{
el.removeClass('form-control-error');
}
});
And in native JavaScript:
// Defining multiple events on multiple elements in native JavaScript
document.querySelectorAll('#name, #message').forEach(function(el){
['change', 'input', 'blur'].forEach(function(event){
el.addEventListener(event, function(){
if(this.value === ''){
this.classList.add('form-control-error');
}else{
this.classList.remove('form-control-error');
}
});
});
});
For such a simple task, the native solution requires two loops. There’s no real performance difference though, as jQuery hides these loops behind a cleaner API.
jQuery also supports event delegation, which in native JavaScript requires manually distinguishing the clicked element. For an alternative, check out events.js by Chris Ferdinandi. It’s only 1.24KB and simplifies event handling, though I skipped it for my small site.
Class Utilities
This one’s straightforward.
The native solution to use now is ClassList, which offers a similar API to jQuery, as seen in the example above.
$(element).addClass('example')
becomes element.classList.add('example')
.
ClassList works with some limitations in IE10, but without issues in modern browsers.
AJAX
As shown earlier, AJAX via native XMLHttpRequest isn’t exactly elegant.
On my site’s contact form, a server-side API checks if the email is real and whether it’s from a known disposable email provider.
To handle this check, I used ajax.js by Fernando Daciuk, which is only 1.97KB.
It works similarly to jQuery’s $.get()
, $.post()
, and $.ajax()
functions.
The corresponding AJAX call looks like this:
// AJAX Get via ajax.js
ajax().get('/contact/checkmail', {
email: email.value
}).then(function(data){
if(data == true){
// Email address is allowed
}else{
// Email address is not allowed
}
});
Alternatively, there’s the built-in Fetch API, which requires a bit more manual handling:
// AJAX Get via Fetch API
fetch('/contact/checkmail', {
cache: 'no-cache',
body: JSON.stringify({email: email.value})
}).then(function(response){
response.json().then(function(data){
if(data.json == true){
// Email address is allowed
}else{
// Email address is not allowed
}
});
});
Fetch only accepts plain text, requiring you to serialize the data you want to send if it’s not already a string.
It allows more control over the response format, like transforming the response into an object using response.json().then()
.
I went with ajax.js for broader browser support.
Result
The outcome was clear: by removing jQuery, I reduced data transferred on page load, eliminated a request (including DNS and SSL overhead), and cut out a large, mostly unused script. Now, my homepage hits 100 on the PageSpeed Insights test.
With jQuery:
Without jQuery:
Google assumes slower processors on mobile devices take longer to parse the page with jQuery, versus without it. Notably, the First Meaningful Paint (FMP) time dropped by a full second. FMP marks when visible elements and web fonts are fully rendered — important for both user experience and search rankings.
Here’s the load time comparison:
First with jQuery:
And without jQuery:
Another small optimization I made was inlining web fonts in the <head>
section of each page.
Thanks to HTTP/2, the fonts now load in parallel with the rest, without waiting for main.css.
Beyond the 30KB saved from removing jQuery, I shaved off about 10% of load time for first-time visitors, where DNS lookup and SSL handshake are needed.
On subsequent page loads, the savings are slightly higher.
While these changes weren’t strictly necessary for my site, I’m pleased with the perfect score and the realization that jQuery isn’t really needed anymore.
Thank you jQuery for your much needed service! But I think I’ll avoid using it in future projects — certainly can’t hurt.