When to Remove jQuery
"Should we remove jQuery?" comes up in every team that maintains an older site. Sometimes the honest answer is "not yet": the site depends on plugins with no replacement, or it will be retired next year. Sometimes removal takes two afternoons and makes every page lighter. The mistake is deciding by habit or by fashion instead of by evidence.
This lesson gives you a checklist for the decision, a small tool that measures which jQuery methods a page actually uses, and a step-by-step plan for removal, plus middle paths when full removal is not worth it yet. The how-to of converting code is in Migrating to Plain JavaScript, and the JavaScript course teaches the plain DOM APIs you will switch to.
What jQuery costs and what it still gives
On the cost side, measured on the official files: jQuery 4.0.0 minified is about 79 KB, roughly 27 KB after gzip compression; the slim build is about 56 KB, roughly 19 KB gzipped. That download and the time to run it matter most on budget phones and slow mobile connections. There are also hidden costs: one more dependency to upgrade and patch, security audit findings for old versions, and new team members who learnt only modern JavaScript.
On the benefit side, jQuery still gives you a short, familiar API, working plugins your business depends on, and code that already works and is tested. Rewriting working code always carries a risk of new bugs.
The decision checklist
| Keep jQuery for now if... | Plan removal if... |
|---|---|
| The site is WordPress and core or plugins load jQuery anyway | Your own code is the only thing that uses jQuery |
| Bootstrap 4 components or other jQuery-only widgets have no replacement yet | You already moved to Bootstrap 5 or native elements |
| The application will be retired or rewritten within a year | The site will live for years and keeps getting new features |
| There are no tests and no time to check every screen | You can test each page with a checklist |
| The page makes heavy use of many jQuery plugins | The page uses a handful of selectors, events and AJAX calls |
Answering this needs facts about each page, not a guess. That is what the next step measures.
Step by step: measure what a page really uses
- Install a usage tracker right after jQuery loads, on a local or staging copy only (never on the live site).
- Use every feature on the page, following your test checklist.
- Read the report: which methods ran, and how often.
- Map each method to its plain JavaScript replacement with the cheat sheet in the migration lesson, and mark anything with no easy replacement (usually plugins).
- Estimate and decide per page: five methods and no plugins is an afternoon; forty methods and three plugins is a project.
The tracker wraps every function on $.fn and a few common $ utilities, counting each call before passing it to the original. Try the page's buttons, then press "Show report":
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="utf-8">
<title>jQuery usage tracker</title>
<style>
.done { text-decoration: line-through; color: #777; }
table { border-collapse: collapse; } td, th { border: 1px solid #ccc; padding: 0.2rem 0.5rem; text-align: start; }
</style>
</head>
<body>
<h3>Today's deliveries, Kochi</h3>
<ul id="list">
<li>Order 2201 <button class="tick">Delivered</button></li>
<li>Order 2202 <button class="tick">Delivered</button></li>
</ul>
<button id="load">Load one more order</button>
<button id="report">Show report</button>
<table id="usage" hidden><thead><tr><th>jQuery API</th><th>Calls</th></tr></thead><tbody></tbody></table>
<script src="https://cdn.jsdelivr.net/npm/jquery@4.0.0/dist/jquery.min.js"></script>
<script>
// ---- The usage tracker: paste right after jQuery, on staging only ----
const usage = new Map();
function track(owner, name, label) {
const original = owner[name];
owner[name] = function (...args) {
usage.set(label, (usage.get(label) || 0) + 1);
return original.apply(this, args);
};
Object.assign(owner[name], original); // keep properties such as plugin defaults
}
Object.keys($.fn).forEach(function (name) {
if (typeof $.fn[name] === "function" && name !== "init" && name !== "constructor") {
track($.fn, name, "." + name + "()");
}
});
["ajax", "get", "post", "getJSON", "each", "map", "extend", "when", "Deferred"].forEach(function (name) {
track($, name, "$." + name + "()");
});
// ---- End of tracker ----
// The page's normal jQuery code
$("#list").on("click", ".tick", function () {
$(this).closest("li").addClass("done");
$(this).prop("disabled", true);
});
$("#load").on("click", function () {
$.getJSON("https://jsonplaceholder.typicode.com/todos/3").done(function (todo) {
$("<li>").text("Order " + (2200 + todo.id) + " ").append($("<button>", { class: "tick", text: "Delivered" })).appendTo("#list");
});
});
$("#report").on("click", function () {
const rows = [...usage].sort((a, b) => b[1] - a[1])
.map(([api, count]) => $("<tr>").append($("<td>").text(api), $("<td>").text(count)));
$("#usage tbody").empty().append(rows);
$("#usage").prop("hidden", false);
});
</script>
</body>
</html>The report also lists helpers that jQuery calls internally, such as .pushStack(), $.extend() and $.each() (and $.getJSON() calls $.ajax() for you), so the counts are not exact. Ignore the internal helpers and focus on the methods you recognise from the page's own code. Even so, the list is usually much shorter than people expect: this page uses about ten methods, every one with a direct plain JavaScript equivalent.
Planning the removal
Once the report says "go", remove jQuery page by page:
- Convert one feature at a time and test it, keeping jQuery on the page while you work.
- Replace plugins with native elements or small modern libraries before removing jQuery from that page.
- Remove the script tag from the page or template only when the tracker shows no calls at all.
- Stop it coming back. Add a lint rule so new code cannot use jQuery by accident:
{
"rules": {
"no-restricted-globals": ["error",
{ "name": "$", "message": "jQuery was removed. Use document.querySelector()." },
{ "name": "jQuery", "message": "jQuery was removed. Use plain DOM APIs." }
],
"no-restricted-imports": ["error", { "paths": [{ "name": "jquery", "message": "jQuery was removed." }] }]
}
}This is an ESLint configuration in the older .eslintrc.json format; in the newer eslint.config.js format, the same two rules go inside a rules object.
Middle paths
Full removal is not the only good outcome:
- Freeze: agree as a team that new code is written in plain JavaScript, while old code keeps jQuery. The jQuery share shrinks every month without a big project.
- Load it only where needed: if only the admin area uses jQuery, remove it from public pages, which are the ones users and search engines load most.
- Use the slim build if a page uses no AJAX, effects or Deferreds (the jQuery 4 slim build also leaves out the queue and Callbacks).
- Upgrade instead: if removal must wait, at least move to jQuery 4.0.0 so the security findings go away.
Before switching to the slim build, check what it leaves out:
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="utf-8">
<title>What the slim build leaves out</title>
</head>
<body>
<pre id="out"></pre>
<script src="https://cdn.jsdelivr.net/npm/jquery@4.0.0/dist/jquery.slim.min.js"></script>
<script>
const checks = {
"$.fn.on (events)": typeof $.fn.on,
"$.fn.addClass": typeof $.fn.addClass,
"$.ajax": typeof $.ajax,
"$.getJSON": typeof $.getJSON,
"$.fn.fadeIn (effects)": typeof $.fn.fadeIn,
"$.Deferred": typeof $.Deferred,
};
$("#out").text(Object.entries(checks).map(([name, type]) => name.padEnd(24) + type).join("\n"));
</script>
</body>
</html>Anything that prints undefined must be gone from the page's code (run the usage tracker first) before the slim build can replace the full one.
Common mistakes
- Removing jQuery because it is "old", without measuring the benefit or the risk.
- Keeping jQuery because "everything uses it", when the tracker shows five methods.
- Deleting the script tag first and fixing whatever breaks in production.
- Forgetting plugins and inline scripts in templates that still call
$. - Running a tracker on the live site. It slows every call; keep it on staging.
Key points
- Decide per page, with evidence: dependencies, lifetime of the site, available testing, and actual jQuery usage.
- A usage tracker that wraps
$.fnmethods shows which jQuery APIs a page really calls. - Remove jQuery feature by feature, replace plugins first, remove the script tag last, and add lint rules to stop it returning.
- Middle paths (freeze, load only where needed, slim build, upgrade to 4.0) are valid decisions.
- Record the decision and the reasons for each part of the site.
Exercise
Use the tracker on the jQuery "before" example (tabs and an enquiry form) from Migrating to Plain JavaScript: paste the tracker after the jQuery script, use both tabs and send the form, then press a report button you add. List the jQuery methods it used and write the plain JavaScript replacement next to each one.
Show solution
// Add after the page's own code:
$("<button>", { text: "Show report" }).appendTo("body").on("click", function () {
console.table([...usage].map(([api, calls]) => ({ api, calls })));
});A typical report and replacements:
| jQuery used | Plain JavaScript |
|---|---|
.on() (delegated) | addEventListener plus event.target.closest() |
.addClass(), .removeClass(), .siblings() | classList.add/remove, a loop over parent.children |
.hide(), .show() | the hidden property |
.data() | dataset |
.find(), .prop() | querySelector(), button.disabled = true |
$.post(), .serialize() | fetch() with new URLSearchParams(new FormData(form)) |
.text(), .fadeIn() | textContent, a CSS transition on a class |
Every method has a direct replacement and there are no plugins, so this widget is a good candidate for removing jQuery.
Quick Quiz
1. Why should you measure jQuery usage before deciding to remove it?
A usage report shows whether removal is an afternoon or a project.
2. A WordPress site's theme uses $ in three places. Will removing that code remove jQuery from the pages?
In WordPress, other parts of the site often depend on the bundled jQuery.
3. Which is a valid middle path when full removal is not worth it yet?
Freezing jQuery usage and upgrading it reduces risk without a big rewrite.
4. How can you stop jQuery from returning to a page after removal?
A lint rule fails the build when someone uses the jQuery globals again.
Learn jQuery with a mentor
Free lessons get you started. Our Full Stack Web Development training adds real projects and code reviews from working developers.
- Project-based learning
- Mentor code reviews
- Free counselling call
