Contents
Overview
This article explains, in complete detail, how to remove jQuery from the WordPress front end and replace its usage with vanilla JavaScript without breaking your site. It covers auditing dependencies, safe removal (and exceptions), creating a compatibility shim if needed, converting common jQuery patterns to vanilla JS, handling AJAX, animations and event delegation, troubleshooting, and testing. Every code example is provided so you can copy and paste into your theme/plugin for systematic migration.
Why remove jQuery?
- Reduce payload size and HTTP requests for faster page load times.
- Avoid shipping a large legacy library when modern browsers provide native alternatives.
- Reduce third-party dependencies and potential conflicts.
Before you start: important cautions
- Do not disable jQuery in the admin dashboard (is_admin()). WordPress admin and many core features (plus some plugins) still expect jQuery.
- Test on a staging environment first. Removing jQuery can break plugins and theme scripts that you cannot modify.
- Keep a rollback plan: a small compatibility shim that satisfies script dependencies can be a temporary fallback while you progressively convert code.
- Use Query Monitor and browser console to catch missing function errors after removal.
High-level strategy (recommended)
- Audit the theme and plugins to find all uses of jQuery.
- Convert theme scripts to vanilla JS first (you control these). Use best practices (module bundling, defer/async, modern APIs).
- For plugins you cant change: either update/replace those plugins or provide a compatibility shim that implements a small, carefully chosen subset of jQuery features they rely on.
- Finally, dequeue and deregister WordPresss front-end jQuery and jquery-migrate safely on non-admin pages and load your vanilla scripts in its place (or register your shim as the jquery handle so dependencies remain satisfied).
Step 1 — Audit: find all jQuery usage
Search theme and plugin files for common patterns:
- jQuery(
- (
- .on(, .off(, .bind(, .delegate(
- .ajax(, .get(, .post(, .getJSON(
- .animate(, .fadeIn(, .slideToggle(
- wp_enqueue_script(jquery), wp_add_inline_script(…, jQuery)
Tools to help:
- Search in your code editor (global search)
- WP-CLI grep or ripgrep on the site directory
- Query Monitor plugin to list enqueued scripts and inline scripts
- Browser DevTools console errors after temporarily removing jQuery
Step 2 — Convert theme and custom scripts to vanilla JS
Convert all code you control before touching core jQuery enqueuing. Below are one-to-one replacements for common patterns and idioms.
Document ready
jQuery:
jQuery(function() { // code })
Vanilla JS:
document.addEventListener(DOMContentLoaded, function () { // code })
Selector and iteration
jQuery:
(.items).each(function(index, el) { // this or el })
Vanilla JS:
document.querySelectorAll(.items).forEach(function (el, index) { // el })
Event binding
jQuery:
(.btn).on(click, function(e) { // handler })
Vanilla JS (for direct binding):
document.querySelectorAll(.btn).forEach(function (el) { el.addEventListener(click, function (e) { // handler }) })
Vanilla JS (delegation):
document.addEventListener(click, function (e) { var target = e.target.closest(.btn) if (target) { // handler, target is the matched element } })
Class manipulation
jQuery:
(.item).addClass(active).removeClass(inactive).toggleClass(open)
Vanilla JS:
document.querySelectorAll(.item).forEach(function (el) { el.classList.add(active) el.classList.remove(inactive) el.classList.toggle(open) })
Attributes, properties, values
jQuery:
var href = (a.link).attr(href) (input).val(hello) (.el).data(id)
Vanilla JS:
var a = document.querySelector(a.link) var href = a ? a.getAttribute(href) : null var input = document.querySelector(input) if (input) input.value = hello // dataset: var el = document.querySelector(.el) var id = el ? el.dataset.id : undefined
AJAX: .ajax / .get / .post
jQuery:
.ajax({ url: /wp-json/myplugin/v1/data, method: POST, data: {foo: bar}, dataType: json, success: function(data) {}, error: function(err) {} })
Vanilla JS using fetch (JSON POST):
fetch(/wp-json/myplugin/v1/data, { method: POST, headers: { Content-Type: application/json, X-WP-Nonce: wpData.nonce // if you used wp_localize_script to provide nonce }, body: JSON.stringify({ foo: bar }) }).then(function (res) { if (!res.ok) throw new Error(res.statusText) return res.json() }).then(function (data) { // success }).catch(function (err) { // error })
Vanilla JS for form-encoded submissions:
var formData = new URLSearchParams() formData.append(foo, bar) fetch(/wp-admin/admin-ajax.php?action=my_action, { method: POST, body: formData, headers: { X-WP-Nonce: wpData.nonce } }).then(function (r) { return r.json() }).then(function (data) { // handle })
Animation alternatives (fade / slide)
Recommendation: Prefer CSS transitions/animations toggled with classes. If you need scripted height animations (slide), use requestAnimationFrame and explicit height math. Example simple slideToggle:
function slideToggle(el, duration) { if (!el) return var computed = window.getComputedStyle(el) if (computed.display === none el.style.height === 0px) { // slide down el.style.removeProperty(display) var display = window.getComputedStyle(el).display if (display === none) display = block el.style.display = display var height = el.scrollHeight px el.style.overflow = hidden el.style.height = 0px requestAnimationFrame(function () { el.style.transition = height duration ms el.style.height = height setTimeout(function () { el.style.removeProperty(height) el.style.removeProperty(overflow) el.style.removeProperty(transition) }, duration) }) } else { // slide up el.style.overflow = hidden el.style.height = el.scrollHeight px requestAnimationFrame(function () { el.style.transition = height duration ms el.style.height = 0px setTimeout(function () { el.style.display = none el.style.removeProperty(height) el.style.removeProperty(overflow) el.style.removeProperty(transition) }, duration) }) } }
Step 3 — Safe removal of jQuery from front end (PHP)
Use the WordPress enqueuing system to dequeue and deregister jQuery only on the front end. The example below is a safe starter you can place into your themes functions.php (preferably a child theme) or a small mu-plugin. It ensures admin pages and REST endpoints remain unchanged.
Notes:
- Use a low priority (1) so this runs early and you can re-register a compatibility script if needed before other scripts are enqueued.
- For older WP versions only jquery exists for newer versions WP splits jquery-core and jquery-migrate. We attempt to remove all three safely.
Option A — Register a compatibility shim that satisfies dependencies (temporary)
If you cannot convert some plugins immediately, register a tiny compatibility script under the jquery handle before other scripts print. This script can implement a minimal subset of jQuery methods used by those plugins (like on, off, addClass, removeClass, ajax wrapper). Place the shim in your theme and register it as jquery. Example:
The shim file js/jquery-shim.js should define window.jQuery and window. and expose basic APIs. See the recommended shim example below.
Option B — Register an empty handle to satisfy dependencies (not recommended)
Registering an empty or dummy script is possible, but it risks runtime errors since code will try to call methods that dont exist. Prefer a minimal shim or converting the dependent scripts.
Example: a minimal compatibility shim (jquery-shim.js)
Below is a small compatibility shim that implements a very small subset: event binding (.on, .off), addClass/removeClass/toggleClass, simple attr/text/html, and a wrapper for AJAX using fetch for common cases. This is only a stop-gap while you migrate. It is intentionally limited and should be extended only for the features your remaining plugins need.
(function (window, document) { function (selector) { if (typeof selector === function) { document.addEventListener(DOMContentLoaded, selector) return } var nodeList = (selector instanceof Node) ? [selector] : document.querySelectorAll(selector) var obj = Array.prototype.slice.call(nodeList) obj.on = function (event, handler) { this.forEach(function (el) { el.addEventListener(event, handler) }) return this } obj.off = function (event, handler) { this.forEach(function (el) { el.removeEventListener(event, handler) }) return this } obj.addClass = function (cls) { this.forEach(function (el) { el.classList.add(cls) }) return this } obj.removeClass = function (cls) { this.forEach(function (el) { el.classList.remove(cls) }) return this } obj.toggleClass = function (cls) { this.forEach(function (el) { el.classList.toggle(cls) }) return this } obj.attr = function (name, value) { if (value === undefined) return this[0] ? this[0].getAttribute(name) : null this.forEach(function (el) { el.setAttribute(name, value) }) return this } obj.text = function (value) { if (value === undefined) return this[0] ? this[0].textContent : this.forEach(function (el) { el.textContent = value }) return this } obj.html = function (value) { if (value === undefined) return this[0] ? this[0].innerHTML : this.forEach(function (el) { el.innerHTML = value }) return this } return obj } // Minimal ajax wrapper using fetch for common scenarios .ajax = function (opts) { var method = (opts.type opts.method GET).toUpperCase() var headers = opts.headers {} var body = null if (opts.data method !== GET) { if (opts.processData === false) { body = opts.data } else if (headers[Content-Type] headers[Content-Type].indexOf(application/json) !== -1) { body = JSON.stringify(opts.data) } else { var params = new URLSearchParams() for (var k in opts.data) { if (opts.data.hasOwnProperty(k)) params.append(k, opts.data[k]) } body = params.toString() headers[Content-Type] = application/x-www-form-urlencoded charset=UTF-8 } } if (opts.data method === GET) { var url = new URL(opts.url, window.location.href) for (var kk in opts.data) { if (opts.data.hasOwnProperty(kk)) url.searchParams.append(kk, opts.data[kk]) } opts.url = url.toString() } return fetch(opts.url, { method: method, headers: headers, body: body, credentials: opts.xhrFields opts.xhrFields.withCredentials ? include : same-origin }).then(function (res) { if (!res.ok) throw res if (opts.dataType === json res.headers.get(content-type) res.headers.get(content-type).indexOf(application/json) !== -1) { return res.json() } return res.text() }).then(function (data) { if (typeof opts.success === function) opts.success(data) }).catch(function (err) { if (typeof opts.error === function) opts.error(err) }) } // Expose globally window. = window.jQuery = })(window, document)
Place this file in your theme at js/jquery-shim.js and register it as shown earlier. Extend the shim as necessary, but keep it minimal and temporary — the goal is to migrate away from it.
Step 4 — Replace jQuery-dependent plugin scripts or swap plugins
For plugins you control, convert their JS. For third-party plugins:
- Check if an updated version removes jQuery dependency — update the plugin.
- Contact the plugin author or open a PR to modernize the code.
- If no update is available, consider replacing the plugin with a modern alternative that doesnt require jQuery.
- As a last resort, use the compatibility shim registered as jquery for that limited functionality.
Debugging and testing checklist
- Enable WP_DEBUG and display errors on staging if safe. This will surface fatal JS/PHP issues that may be related to missing dependencies.
- Use Query Monitor to view all enqueued scripts and verify jquery, jquery-core and jquery-migrate are no longer enqueued on front-end pages.
- Open browser DevTools Console and reload pages fix any jQuery is not defined or is not defined errors by converting the relevant scripts or ensuring the shim is present.
- Use Lighthouse (Chrome DevTools) and WebPageTest to measure performance improvement and ensure no regressions.
- Test interactive components (menus, sliders, modals, forms, infinite scroll, comments submission) across devices and browsers you support.
Advanced: replacing inline scripts and localized data
Inline scripts that reference jQuery (via wp_add_inline_script tied to the jquery handle) will be removed or orphaned if you deregister jquery. To handle these:
- Search for wp_add_inline_script(…, jquery, …) and update the inline script to run after a different handle (or rewrite inline code to vanilla JS).
- wp_localize_script used to pass PHP data to JS is associated with a script handle. If you re-register a shim as jquery, the data will still attach to that handle otherwise reattach to an appropriate front-end script handle.
Edge cases and compatibility notes
- Some themes/plugins expect jQuery to be loaded in the header others enqueue it in the footer. When replacing with a shim, match the intended location (last parameter of wp_register_script) to keep execution order consistent.
- If inline scripts run before your shim prints, youll still see missing-jQuery errors. Ensure your shim is registered early and dependencies resolved.
- Third-party libraries bundled in plugins may call jQuery deep inside prefer contacting the author or replacing the plugin if you cant easily patch it.
- jQuery Migrate: if you remove jQuery core but jquery-migrate remains enqueued by some plugin, youll still have extra payload. Deregister both where safe.
Measuring impact
Compare before/after payloads and performance:
- Check the network tab to see the size of jquery.js and its effect on total transfer size.
- Run Lighthouse to measure first contentful paint (FCP), largest contentful paint (LCP), and the Total Blocking Time (TBT) improvements.
- Monitor Real User Metrics (RUM) if available for your site to see real-world improvements.
Common jQuery idioms and quick vanilla patterns (cheat sheet)
jQuery | Vanilla JS |
(selector) | document.querySelectorAll(selector) / document.querySelector(selector) |
(fn) | document.addEventListener(DOMContentLoaded, fn) |
(el).on(click, fn) | el.addEventListener(click, fn) (or delegation) |
.ajax({…}) | fetch() |
.addClass / .removeClass | element.classList.add/remove |
.val() | element.value |
.html() / .text() | element.innerHTML / element.textContent |
Complete end-to-end example: migrate theme and remove jQuery
- Audit and convert all theme JS to vanilla. Replace with querySelector and event listeners. Bundle and minify your new JS file (example: assets/js/main.js).
- Place any temporary shim at assets/js/jquery-shim.js if you need to keep compatibility.
- functions.php: deregister WordPress jQuery and optionally register your shim as jquery with appropriate dependency and location.
admin_url(admin-ajax.php), nonce => wp_create_nonce(wp_rest) )) } ?>
Final checklist before you deploy to production
- All theme scripts converted and tested in target browsers.
- Third-party plugins either updated or covered by shim (temporary) or replaced.
- Query Monitor shows no missing dependencies or console errors on representative pages.
- Performance metrics (Lighthouse, network tab) show expected improvements.
- Rollback plan in place (re-enable jQuery quickly if a critical issue appears).
Useful references
Summary
Removing jQuery from the front end of a WordPress site is a worthwhile optimization when done deliberately. The safe approach is:
- Audit and convert theme scripts first.
- Handle third-party plugins by updating, replacing, or using a minimal compatibility shim temporarily.
- Dequeue/deregister jQuery only on the front end with careful attention to script order and dependencies.
- Replace jQuery idioms with vanilla JS and modern APIs like fetch, classList, dataset, and addEventListener.
- Test thoroughly and measure improvements.
Following the examples and patterns in this article will allow you to progressively remove jQuery while keeping your site functional and improving performance.
|
Acepto donaciones de BAT's mediante el navegador Brave 🙂 |