Why Teams Still Reach for jQuery
When jQuery arrived in 2006, it solved real problems: consistent DOM manipulation across browsers, simple AJAX helpers, and a plugin ecosystem that extended its reach. At the time, it made JavaScript genuinely productive for building interactive pages and early single-page applications. That legacy persists—roughly 80% of the most popular websites still include jQuery today.
Its staying power comes down to several factors. The API remains easy to use for DOM and CSS manipulation, event handling, effects, and AJAX calls. It also works identically across browsers, which was a major selling point before the web standardized around modern JavaScript. But two practical realities keep it embedded in production sites: WordPress ships jQuery by default, and popular UI libraries like Bootstrap historically depended on it. Templates built on those foundations carry jQuery along as a transitive dependency.
The other reason is migration cost. Rewriting a working application onto a new framework is expensive, time-consuming, and risky. Yet the JavaScript ecosystem has moved on. ECMAScript standards now provide native replacements for many jQuery conveniences—querying, traversal, animation, and fetch-based AJAX—without the 30KB+ library overhead. And the dominant UI architecture has shifted from imperative DOM mutation to component-based frameworks.
Where jQuery Stops Being the Right Tool
React and similar frameworks approach UI construction differently. jQuery mutates the real DOM directly and imperatively; React keeps a virtual DOM in memory, diffs changes, and applies updates efficiently. That model also gives you declarative state management as a first-class concept rather than something you bolt on with custom code and event handlers.
On top of React, Next.js layers opinions that many projects eventually need anyway: static site generation, server-side rendering, or a blend of both within one application. It also lets you define serverless API endpoints alongside your pages. For teams building new features or maintaining long-lived products, these capabilities often outweigh the comfort of jQuery's procedural approach.
That doesn't mean every project must be a React or Next.js app. There are plenty of cases where a framework's architectural constraints aren't desirable. But if you're maintaining a substantial jQuery codebase, the question is how to escape the maintenance burden without a full rewrite or a disruptive cutover.
A Practical Migration Strategy
The key insight is that migration doesn't have to be all-or-nothing. You can move incrementally, replacing jQuery-based modules with React or Next.js components piece by piece. The goal is to reduce jQuery's surface area over time while keeping the application functional at every step.
Before writing new code, audit what's actually using jQuery. Many projects only reach for it out of habit—selecting elements, toggling classes, or firing an AJAX request. Those can be replaced with native querySelector, classList, and fetch calls without bringing a framework into the picture. That alone can shrink the dependency's footprint significantly.
For interactive widgets that genuinely need componentization, start extracting them into React components. In a Next.js app, you can render those components alongside legacy code on the same page. Communication between the old and new worlds can go through plain DOM events or a shared state store, letting you retire jQuery wrappers only when their consumers have been fully converted.
Throughout the process, keep a clear inventory: which scripts still reference $, what third-party plugins are still required, and which pages depend on them. That clarity tells you when you can finally remove the library and its plugins from the bundle—the signal that the migration is done.
Choosing a Migration Approach
Before rewriting anything, it’s worth asking whether a framework is needed at all. Modern browsers have closed much of the gap with jQuery. DOM selection via $(selector) can be replaced with querySelectorAll(), CSS class manipulation is available through Element.classList, many animations can be expressed purely in CSS, event handling is covered by the addEventListener function, and AJAX calls can be made with XMLHttpRequest. If the application's interactivity is light, a vanilla JavaScript rewrite may be the simplest path, with no framework overhead at all.
When a framework is warranted, an incremental migration is usually safer than a full rewrite. The first step is to identify the application's distinct components, grouping related markup and behavior. For example, a button with a click handler that fetches data and updates the DOM can be isolated and rewritten as a React component. The key is to ensure each piece is independent: removing one component should not break the others.
Full Rewrite vs. Gradual Migration
A complete rewrite is the most straightforward option. It keeps the entire codebase on one stack and avoids the complexity of mixing frameworks, but it is time-consuming and risky. A full rewrite is practical mainly for small, non-critical projects that don't change often.
A faster alternative is to migrate piece by piece, shipping each converted section as soon as it's ready. This delivers features sooner and surfaces bugs earlier through real user feedback. The trade-off is that the production application will temporarily contain a mixture of tools, libraries, and framework versions. This can cause conflicts, especially when global styles or policies, such as CSS cascade rules, are applied across both the old and new sections.
The Frankenstein Migration
An intermediate strategy, popularized as the Frankenstein Migration, combines the completeness of a full rewrite with the cadence of an incremental approach. It relies on two concepts: microservices for the frontend and Web Components as the integration layer.
The process consists of several steps:
- Identify microservices. Split the existing application into small, independent functional units. If one can be removed without affecting the others, it's a candidate.
- Allow host-to-alien access. The existing app (host) and the new one (alien) must be independently deployable while maintaining communication between them.
- Write an alien component. Re-implement an identified service in the new framework. For a grocery list app, an "add item" feature written with jQuery could be recreated as a React
AddItemcomponent. - Create a Web Component wrapper. Build a bridge that imports the alien component and exposes it as a custom element so the host app can render it. This step may require a bundler to produce compatible JavaScript.
- Replace the host service. Swap the original markup in the host application with the new wrapper component. The production site now mixes host code with wrapped alien components.
- Rinse and repeat. Handle each identified microservice the same way until all have been migrated.
- Switch to alien. Once the host is only a shell of wrappers, point users to the new application.
This approach yields usable updates quickly while working toward full migration. The main downside is the extra overhead: you must develop and maintain wrapper elements, and the host application carries duplicated dependencies, which can affect performance.
Strangler Pattern
Another incremental approach is application strangulation. Instead of actively converting existing code, you identify the edges of the legacy system and implement all new functionality in the new framework. Over time, the old system is "strangled" and replaced. This reduces risk because you never change existing, working code.
A concrete example: assuming you have existing HTML and JavaScript that implements an accordion backed by standard web APIs, you can wrap it in a React component that uses useEffect for the DOM logic. The component renders null because it only needs to mount the existing behavior—it adds no new features. This safely introduces React without altering the user experience, and sets the stage for all future development in the new framework.
Rendering Strategies in Next.js
Next.js offers three rendering modes, and each page can choose the most appropriate one.
Client-side rendering generates content directly in the browser, which is the default React behavior. But the original application likely renders initial content on page load, perhaps through a server-side language like PHP or ASP.NET. Next.js can replace that with its own server-side rendering, keeping all logic within one project. For a dashboard whose markup depends on live data, server-side rendering delivers a fully populated page at request time.
Static generation, in contrast, produces pages at build time. For content that’s predictable and data that can be fetched during the build, this yields a faster, more secure, and more scalable site. A blog can use a generic [blog-slug].js file so that every post becomes a static page at build time. If parts of a page are dynamic, React's client-side rendering can still fetch and render that data after load.
Replacing AJAX with API Routes
Next.js also provides API Routes, which let you build serverless functions in Node.js within the same project as the frontend. This consolidation eliminates cross-origin resource sharing (CORS) issues and simplifies deployment.
If the application uses jQuery AJAX to call a backend, those calls can be replaced with API Routes. This is especially useful for masking external services or for keeping everything in one repository. A file like /pages/api/get/[id].js can expose the desired endpoint, and the page code can then fetch from it directly.
Getting Your Next.js App Onto Netlify
Netlify is a full-featured platform for automating, managing, building, testing, deploying, and hosting web applications. Its key features include a global CDN, support for serverless functions, deploy previews based on GitHub Pull Requests, webhooks, instant rollbacks, and role-based access control. It’s an excellent choice for hosting Next.js projects, and the setup process is straightforward.
The first requirement is that your Next.js app code lives in a Git repository. Netlify connects to your chosen Git provider (such as GitHub). Any time a change is committed to a branch or a Pull Request is opened, Netlify automatically triggers a build and deploy task.
With your repository ready, you create a “Netlify Site” for it using one of two approaches:
- Using the Netlify CLI
Install the CLI withnpm install -g netlify-cli, log in withntl login, then runntl initin your application’s root directory and follow the prompts. - Using the Netlify web app
Go to https://app.netlify.com/start, connect to your Git provider, pick your application’s repo from the list, configure the build settings, and deploy.
For both methods, remember that your build command should be next build and the publish directory is out.
Netlify automatically installs the Essential Next.js plugin, which enables support for API routes, dynamic routes, and Preview Mode. Once that’s in place, your Next.js application is live on Netlify’s fast and stable CDN hosting service.
Final Thoughts on the Migration Path
Migrating from jQuery to a modern framework like Next.js requires a clear evaluation of your current website’s needs and the benefits a new tool can bring. Choosing the right strategy—whether a full rewrite or an incremental approach—depends on your project’s specific constraints. Once the migration is complete, deploying to Netlify gives you a reliable, globally distributed hosting solution with minimal ongoing maintenance.



