Kokil Thapa - Professional Web Developer in Nepal
Freelancer Web Developer in Nepal with 15+ Years of Experience

Kokil Thapa is an experienced full-stack web developer focused on building fast, secure, and scalable web applications. He helps businesses and individuals create SEO-friendly, user-focused digital platforms designed for long-term growth.

jQuery to Vanilla JS Migration Guide

By Kokil Thapa | Last reviewed: September 2026

Legacy sites still ship jQuery 1.x or 3.x for DOM helpers, plugins, and $.ajax calls that worked fine in 2015. In 2026, that extra HTTP request and parse cost hurts Core Web Vitals on mobile networks in Nepal and abroad. This jQuery to Vanilla JS Migration Guide walks you through a safe, incremental path using native browser APIs. You keep behaviour identical while shrinking bundle size. For interactive Blade templates, see Alpine.js for interactive Blade templates as a modern alternative when full rewrites are not practical.

Why should you migrate from jQuery to vanilla JavaScript in 2026?

jQuery solved real problems when IE6 quirks dominated front-end work. Modern browsers ship consistent DOM APIs, Promises, fetch, and classList. The library still adds roughly 30 KB gzipped on every page load.

On a law-firm portal or WooCommerce storefront, that weight competes with hero images and third-party scripts. Google treats page weight as a ranking signal through Core Web Vitals. Removing jQuery often improves LCP without touching backend code.

Security is another driver. Unmaintained jQuery plugins are a common XSS vector. Fewer dependencies mean fewer npm audit alerts and less supply-chain risk.

Framework teams have moved on. Laravel 12 and 13 default to Vite 8.x. WordPress 7.1 themes increasingly use ES modules. jQuery feels foreign inside that toolchain.

jQuery to Vanilla JS Migration OverviewLegacy StackjQuery 3.x + plugins$.ajax, .fadeIn()Target StackNative ES2022+fetch, classListAuditReplaceTestOutcome: smaller bundle, fewer CVEs, faster TTIWorks with Laravel 12, WordPress 7.1, static sites
jQuery to Vanilla JS migration overview — audit dependencies, replace APIs incrementally, then validate before removing the library.

How do you audit jQuery usage before starting a migration?

Never delete the script tag on day one. Inventory every jQuery call first. On real client projects, hidden plugin dependencies cause the most rollback pain.

Run a static grep across your assets

Search your theme, Blade partials, and public JavaScript for these patterns:

  • $( and jQuery( — direct invocations
  • $.ajax, $.get, $.post — network calls
  • .on(, .click(, .submit( — event bindings
  • $.fn. or custom plugins registered on jQuery
grep -rE '\$\(|jQuery\(|\.ajax|\.fn\.' resources/js public/js themes/ --include='*.js' --include='*.blade.php'

grep -r 'wp_enqueue_script.*jquery' wp-content/themes/

Sort results by file and group by feature area: navigation, forms, sliders, admin widgets. Priorise high-traffic pages first. Checkout flows and lead forms beat blog sidebars.

Map third-party plugins

Plugins like Slick, Select2, or old Bootstrap 3 jQuery components cannot be swapped line-for-line. Each needs a replacement strategy:

  1. Find a vanilla or Web Component alternative.
  2. Replace with CSS-only behaviour where possible.
  3. Isolate the plugin behind a dynamic import until the whole feature is rebuilt.

Document findings in a spreadsheet. Columns: file path, jQuery method, vanilla equivalent, risk level, owner. This becomes your migration backlog.

What are the direct jQuery-to-vanilla JavaScript API equivalents?

Most day-to-day jQuery maps cleanly to native APIs documented on MDN's DOM reference. The table below covers patterns I replace most often on production Laravel and WordPress sites.

jQueryVanilla JSNotes
$('#id')document.getElementById('id')Returns one element, not a collection
$('.cls')document.querySelectorAll('.cls')Returns NodeList; use forEach or spread
.addClass('x')el.classList.add('x')Supports multiple classes natively
.on('click', fn)el.addEventListener('click', fn)Remember { passive: true } for scroll
.html(content)el.innerHTML = contentSanitise user HTML to prevent XSS
.append(node)el.append(node)Same method name since 2015
$.ajax({...})fetch(url, opts)Returns a Promise; chain with async/await
.fadeIn()CSS opacity + transitionOr Web Animations API for complex motion
.each(fn)nodes.forEach(fn)NodeList.forEach is widely supported
$(document).ready(fn)document.addEventListener('DOMContentLoaded', fn)Or defer your module script tag

Selector and collection helpers

jQuery wraps results in a jQuery object with chainable methods. Vanilla returns raw nodes. A thin helper keeps migration readable without reintroducing jQuery:

const $ = (selector, context = document) => context.querySelector(selector);
const $$ = (selector, context = document) => [...context.querySelectorAll(selector)];

$$('.nav-link').forEach(link => {
  link.addEventListener('click', handleNavClick);
});

Remove these helpers once migration finishes. They are scaffolding, not architecture.

Event delegation without .on()

jQuery's $(parent).on('click', '.child', handler) maps to native delegation:

document.querySelector('#booking-form').addEventListener('click', (event) => {
  const target = event.target.closest('[data-action="remove-row"]');
  if (!target) return;
  target.closest('tr').remove();
});

closest() replaces jQuery's bubbling filter. It returns null when no match exists, so guard before acting.

Core API Replacement Map$()querySelectorAll.on()addEventListener$.ajaxfetch + JSONclassListadd / remove / toggleclosest()event delegationHeaders APICSRF, JSON bodyProductionZero jQueryES modules
Direct mapping from common jQuery DOM, event, and AJAX patterns to native browser APIs used in vanilla JS migrations.

How do you migrate jQuery AJAX calls to fetch in Laravel and WordPress?

$.ajax hid cross-browser XHR differences. fetch is standard in every browser you should support in 2026. The main gotcha is error handling: fetch only rejects on network failure, not HTTP 4xx or 5xx.

Laravel CSRF with fetch

Laravel expects the X-XSRF-TOKEN header on POST requests. Read the encrypted cookie and send it explicitly:

function getCookie(name) {
  const match = document.cookie.match(new RegExp('(^| )' + name + '=([^;]+)'));
  return match ? decodeURIComponent(match[2]) : null;
}

async function postJson(url, payload) {
  const response = await fetch(url, {
    method: 'POST',
    headers: {
      'Content-Type': 'application/json',
      'Accept': 'application/json',
      'X-XSRF-TOKEN': getCookie('XSRF-TOKEN'),
    },
    body: JSON.stringify(payload),
    credentials: 'same-origin',
  });

  if (!response.ok) {
    const error = await response.json().catch(() => ({}));
    throw new Error(error.message || `HTTP ${response.status}`);
  }

  return response.json();
}

Pair this with a Form Request on the server. Never trust client-side validation alone. That pattern appears on every booking portal I maintain.

WordPress admin-ajax.php replacement

Classic WordPress themes call admin-ajax.php through jQuery. Replace with fetch and FormData:

async function wpAjax(action, data = {}) {
  const body = new FormData();
  body.append('action', action);
  Object.entries(data).forEach(([key, value]) => body.append(key, value));

  const response = await fetch('/wp-admin/admin-ajax.php', {
    method: 'POST',
    body,
    credentials: 'same-origin',
  });

  return response.json();
}

For new WordPress 7.1 work, prefer the REST API over admin-ajax. It returns structured JSON and supports proper HTTP verbs. See WordPress development services for theme modernisation scope.

Validate JSON responses with the JSON formatter tool during development. Malformed payloads are easier to spot before they hit production logs.

What is the safest incremental rollout strategy for removing jQuery?

Big-bang rewrites break checkout flows and contact forms. Incremental migration keeps production stable. I follow this sequence on Laravel Livewire booking applications and older brochure sites alike.

Phase 1: Dual-load behind a feature flag

Keep jQuery on the page. Rewrite one module as an ES module loaded with type="module" and defer. Vite 8.x handles this cleanly in Laravel 12 and 13 projects.

npm install --save-dev vite@^8

vite.config.js:
export default {
  build: {
    rollupOptions: {
      input: {
        app: 'resources/js/app.js',
        nav: 'resources/js/modules/nav.js',
      },
    },
  },
};

Load the new module on staging first. Compare behaviour with the old jQuery script side by side.

Phase 2: Replace leaf nodes first

Start with isolated widgets: mobile menu toggles, accordion FAQs, copy-to-clipboard buttons. Leave complex sliders and date pickers for later. Each completed file drops jQuery references from your audit spreadsheet.

Phase 3: Remove the script tag

When grep returns zero hits outside vendor code, remove jQuery from your bundle. In WordPress, dequeue the script:

add_action('wp_enqueue_scripts', function () {
  if (!is_admin()) {
    wp_dequeue_script('jquery');
    wp_deregister_script('jquery');
  }
}, 100);

Test every page template after dequeue. Admin screens may still need jQuery for core WordPress UI. Only strip it on the public front end.

Incremental Rollout PhasesPhase 1Audit + mapPhase 2Dual-load testPhase 3Module rewritePhase 4Remove tagRollback trigger: broken form, console error, analytics dropKeep jQuery enqueued until staging sign-offPlaywright E2ECritical user pathsLighthouse CIBundle size delta
Four-phase incremental rollout for jQuery removal — audit, dual-load, rewrite modules, then dequeue with automated regression gates.

How do you test and measure success after migrating off jQuery?

Code that compiles is not done. Production JavaScript fails in ways unit tests miss: race conditions, double-bound events, and CSP blocks.

Automated browser tests

Write Playwright or Cypress tests for critical paths before touching jQuery. At minimum, cover:

  • Main navigation open and close on mobile
  • Contact and booking form submission with validation errors
  • Cart add/remove on eCommerce pages
  • Login and password reset flows

Run the same suite after each migrated module. A green CI pipeline is your rollback insurance. For broader QA scope, see testing and optimization services.

Performance benchmarks

Record baseline metrics before migration: total JS bytes, TTI, and LCP from Lighthouse. After jQuery removal, expect 20–80 ms TTI improvement on mid-range Android devices. The gain varies with page complexity.

On WooCommerce sites like international florist eCommerce builds, every kilobyte affects mobile buyers on 4G. Pair JS slimming with speed optimization work for image and cache wins too.

SEO and analytics validation

JavaScript regressions silently kill conversion tracking. Verify Google Tag Manager events, Meta pixels, and form goal completions in staging. Follow an SEO migration checklist for zero traffic loss if you deploy URL or template changes alongside the JS rewrite.

Watch Search Console for crawl errors and soft 404s in the two weeks after launch. A broken lazy-load script can hide content from Googlebot.

Polyfills and browser support

If you still support very old browsers, check replacements against Can I Use for fetch and ES6 features. For internal admin tools on modern Chrome, skip polyfills entirely. For public Nepali government-adjacent forms, test on common mobile browsers sold locally.

Node.js 26 LTS and npm 12 handle transpilation through Vite if you must support legacy targets. Set build.target in Vite config rather than shipping Babel blindly.

Before vs After jQuery RemovalBefore (with jQuery)JS: 142 KB gzippedTTI: 3.8 s mobileDeps: 4 pluginsCVE surface: highLCP: needs workAfter (vanilla JS)JS: 98 KB gzippedTTI: 3.1 s mobileDeps: 0 legacyCVE surface: lowLCP: improvedTypical gains on brochure + form sites — measure your own baseline
Before and after metrics from a jQuery to Vanilla JS migration — smaller bundles, faster TTI, and reduced security exposure on production sites.

When partial migration is enough

Not every site needs zero jQuery on day one. WordPress admin, legacy Magento 2.4.x checkout customisations, and third-party widgets may keep jQuery isolated. Wrap new code in IIFE modules that do not depend on $.

For full platform moves — WordPress to Laravel, or theme rebuilds — combine this guide with WordPress to Laravel migration planning and website redesign services. The JavaScript layer is one slice of a larger cutover.

On legal-tech portals such as Notary Nepal, document upload widgets and date pickers are high-risk migration targets. Migrate those only after lower-risk UI is stable. Use the regex tester to validate client-side input patterns you rewrite without jQuery Validate.

Long-term, support and maintenance retainers catch plugin drift before it blocks a full jQuery removal. Budget Rs 15,000–40,000/month (~USD 110–295) for monitoring, patches, and incremental modernisation on small business sites.

Key Takeaways

  • Audit every $(), $.ajax, and plugin call with grep before writing replacement code.
  • Map jQuery methods to querySelector, classList, addEventListener, and fetch using a tracked spreadsheet.
  • Rewrite leaf widgets first; leave complex sliders and admin-ajax integrations for later phases.
  • Dual-load jQuery and new ES modules on staging until Playwright tests pass on all critical paths.
  • Measure bundle size, TTI, and conversion tracking before and after — code changes alone prove nothing.
  • Keep jQuery in WordPress admin; dequeue only on the public theme after zero grep hits remain.

People Also Ask

Is jQuery still needed in 2026?

Public-facing sites rarely need jQuery for new work. Native DOM APIs cover selectors, events, and AJAX in all browsers worth supporting. WordPress admin, older Magento admin panels, and unmaintained plugins may still require it until those features are rebuilt.

How long does a jQuery to vanilla JS migration take?

A small brochure site with one theme and ten JavaScript files often takes one to two weeks. Large WooCommerce or Laravel apps with custom plugins can take six to twelve weeks. Incremental rollout spreads the work across sprints without a risky freeze.

What replaces jQuery animations like .fadeIn() and .slideDown()?

CSS transitions on opacity and max-height handle most show/hide effects with better performance. For sequenced animations, use the Web Animations API or a small library like Motion One. Avoid replicating every jQuery effect line-for-line unless UX depends on it.

Can you migrate jQuery and adopt Alpine.js at the same time?

Yes, but sequence matters. Remove direct jQuery DOM calls first, then introduce Alpine for reactive UI on Blade templates. Mixing $(), Alpine, and vanilla handlers on the same element causes double-binding bugs. One concern per component keeps debugging sane.

Ship a leaner front end without breaking production

This jQuery to Vanilla JS Migration Guide is a production playbook, not a syntax cheat sheet. Audit first, replace incrementally, test every critical path, and measure performance plus analytics after each phase. The sites that succeed treat jQuery removal as ongoing hygiene — the same way you upgrade PHP or patch dependencies.

If your theme still loads jQuery 3.x alongside Bootstrap 5 and three slider plugins, you already know the maintenance cost. A focused migration pays back in speed, security, and developer clarity within the first release cycle.

Need help auditing a legacy WordPress theme or Laravel Blade stack? Contact us for a migration scope review, or explore web development services and recent work on legal-tech portal projects. For related reading, see Laravel 12 migration practices and server migration step-by-step guides.

Frequently Asked Questions

Public-facing sites rarely need jQuery for new work. Native DOM APIs cover selectors, events, and AJAX in all browsers worth supporting. WordPress admin, older Magento admin panels, and unmaintained plugins may still require it until those features are rebuilt.

jQuery solved real problems when IE6 quirks dominated front-end work, but modern browsers ship consistent DOM APIs, Promises, and fetch. The library still adds roughly 30 KB gzipped on every page load, which competes with hero images and third-party scripts on law-firm portals and WooCommerce storefronts. Google treats page weight as a ranking signal through Core Web Vitals, so removing jQuery often improves LCP without touching backend code. Unmaintained jQuery plugins are also a common XSS vector, and fewer dependencies mean less supply-chain risk. Laravel 12 and 13 default to Vite 8.x, and WordPress 7.1 themes increasingly use ES modules, so jQuery feels foreign inside that toolchain.

Never delete the script tag on day one. Run grep across your theme, Blade partials, and public JavaScript for patterns like dollar-parenthesis, jQuery-parenthesis, dollar.ajax, dot-on, dot-click, dot-submit, and dollar.fn for custom plugins. Sort results by file and group by feature area such as navigation, forms, sliders, and admin widgets. Prioritise high-traffic pages first because checkout flows and lead forms beat blog sidebars. Map third-party plugins like Slick, Select2, or old Bootstrap 3 jQuery components to a replacement strategy: vanilla alternative, CSS-only behaviour, or dynamic import isolation. Document everything in a spreadsheet with file path, jQuery method, vanilla equivalent, risk level, and owner.

Most day-to-day jQuery maps cleanly to native APIs on MDN. Hash-id selectors become document.getElementById, class selectors become document.querySelectorAll returning a NodeList, addClass maps to classList.add, dot-on click handlers become addEventListener, html content maps to innerHTML with sanitisation for user HTML, append stays append, and dollar.ajax maps to fetch returning a Promise for async and await. Fade effects become CSS opacity plus transition or the Web Animations API. Each loops to NodeList forEach, and document ready maps to DOMContentLoaded or a deferred module script tag. jQuery wraps results in chainable objects; vanilla returns raw nodes, so thin querySelector helpers work as temporary scaffolding.

A small brochure site with one theme and ten JavaScript files often takes one to two weeks. Large WooCommerce or Laravel apps with custom plugins can take six to twelve weeks. Incremental rollout spreads the work across sprints without a risky freeze.

fetch is standard in every browser you should support in 2026, but it only rejects on network failure, not HTTP 4xx or 5xx, so you must check response.ok explicitly. Laravel expects the X-XSRF-TOKEN header on POST requests: read the encrypted XSRF-TOKEN cookie, send it in fetch headers with Content-Type application/json, Accept application/json, and credentials same-origin, then parse JSON and throw on error status. Pair this with a Form Request on the server because client-side validation alone is never enough. That pattern appears on every booking portal I maintain, and validating JSON responses during development catches malformed payloads before they hit production logs.

Classic WordPress themes call admin-ajax.php through jQuery. Replace those with fetch and FormData: append the action name and each data field to the body, POST to wp-admin/admin-ajax.php with credentials same-origin, and parse the JSON response. For new WordPress 7.1 work, prefer the REST API over admin-ajax because it returns structured JSON and supports proper HTTP verbs. Validate JSON responses with a formatter tool during development so malformed payloads are easier to spot before production. Keep jQuery in WordPress admin screens for core UI; only dequeue it on the public front end after grep returns zero hits outside vendor code.

Big-bang rewrites break checkout flows and contact forms, so migrate incrementally. Phase one dual-loads jQuery behind a feature flag while rewriting one module as an ES module with type module and defer, which Vite 8.x handles cleanly in Laravel 12 and 13 projects. Load the new module on staging first and compare behaviour side by side with the old jQuery script. Phase two replaces leaf nodes first: mobile menu toggles, accordion FAQs, and copy-to-clipboard buttons, leaving complex sliders and date pickers for later. Phase three removes the script tag when grep returns zero hits outside vendor code. In WordPress, wp_dequeue_script and wp_deregister_script on the front end only, then test every page template.

CSS transitions on opacity and max-height handle most show and hide effects with better performance. For sequenced animations, use the Web Animations API or a small library like Motion One. Avoid replicating every jQuery effect line-for-line unless UX depends on it.

Write Playwright or Cypress tests for critical paths before touching jQuery: mobile navigation open and close, contact and booking form submission with validation errors, cart add and remove on eCommerce pages, and login plus password reset flows. Run the same suite after each migrated module because a green CI pipeline is your rollback insurance. Record baseline Lighthouse metrics for total JS bytes, TTI, and LCP before migration; after jQuery removal expect 20 to 80 ms TTI improvement on mid-range Android devices. Verify Google Tag Manager events, Meta pixels, and form goal completions in staging, and watch Search Console for crawl errors in the two weeks after launch because a broken lazy-load script can hide content from Googlebot.

Yes, but sequence matters. Remove direct jQuery DOM calls first, then introduce Alpine for reactive UI on Blade templates. Mixing dollar-parenthesis, Alpine, and vanilla handlers on the same element causes double-binding bugs that are painful to debug in production. One concern per component keeps debugging sane. The article links Alpine.js for interactive Blade templates as a modern alternative when full rewrites are not practical, but treat it as a second phase after jQuery DOM manipulation is gone, not a parallel rewrite on the same widgets.

No. Only strip jQuery on the public front end after your audit spreadsheet shows zero grep hits outside vendor code. WordPress admin screens still need jQuery for core WordPress UI, so dequeue and deregister only on wp_enqueue_scripts for non-admin pages using priority 100. Test every page template after dequeue because admin widgets, media modals, and plugin settings panels depend on core scripts. Partial migration is often enough: legacy Magento 2.4.x checkout customisations and third-party widgets may keep jQuery isolated while new public code runs in IIFE modules that do not depend on dollar-sign.

jQuery AJAX treated HTTP 4xx and 5xx as errors automatically. fetch only rejects on network failure, not bad HTTP status codes, so you must check response.ok and throw or handle manually after await fetch. Parse error JSON with a catch fallback for empty bodies, then surface a meaningful message to the user or logging layer. This catches Laravel validation errors and WordPress admin-ajax failures that would silently pass as successful Promises if you only chain dot-then on response.json without status checks. I have seen production forms appear to submit successfully while nothing saved because this step was skipped during migration.

Not every site needs zero jQuery on day one. WordPress admin, legacy Magento 2.4.x checkout customisations, and third-party widgets may keep jQuery isolated while you wrap new code in IIFE modules that do not depend on dollar-sign. On legal-tech portals, document upload widgets and date pickers are high-risk migration targets; migrate those only after lower-risk UI like navigation and accordions is stable. Long-term support retainers catch plugin drift before it blocks full removal. Budget Rs 15,000 to 40,000 per month, roughly USD 110 to 295, for monitoring, patches, and incremental modernisation on small business sites.

jQuery parent dot-on click with a child selector maps to native delegation on the parent element. In the handler, use event.target.closest with your selector; closest returns null when no match exists, so guard before acting. This replaces jQuery bubbling filters cleanly for patterns like remove-row buttons inside dynamic booking forms. Remember passive true for scroll listeners where preventDefault is not needed. Double-bound events from running both jQuery and vanilla handlers on the same element are a common regression during incremental rollout, which is why dual-load staging comparison and Playwright tests on critical paths matter before you dequeue the library.

Share this article

0 Comments

Leave a comment

Your email is not published. Comments appear once they have been read. Sign in to have your details filled in.

Quick Contact Options
Choose how you want to connect me: