Skip to main content
RefoundRefound
All articles
ux modernizationfrontendreactjquerymigration

Replacing a jQuery Frontend Without Touching Your Backend

Niclas Kusenbach

Your backend is solid. It processes orders, manages users, handles payments — and it does it reliably. The problem is everything in front of it: a jQuery frontend built in 2014 that takes six seconds to load, can't run on a phone, and requires a four-hour onboarding session for every new team member.

This is one of the most common modernization patterns we encounter. The business logic works. The database is fine. But the interface is a productivity trap that costs more in support tickets and developer time than anyone wants to admit.

The good news: you can replace the frontend without touching a single line of backend code.

Why jQuery frontends become liabilities

jQuery was the right tool in 2010. It smoothed over browser incompatibilities, made DOM manipulation tolerable, and enabled interactions that plain JavaScript couldn't easily achieve at the time. But jQuery applications have no component model, no state management, and no build pipeline. Every page is a collection of scripts that manipulate the DOM directly.

The consequences compound over time:

  • No reusability. The same date picker is reimplemented on every page. The same form validation logic exists in nine different files.
  • Implicit state. Application state lives in the DOM — in hidden inputs, in CSS classes, in data attributes. There's no single source of truth.
  • No testing. jQuery code that directly manipulates document.getElementById() is nearly impossible to unit test.
  • Performance decay. Without a virtual DOM or any diffing mechanism, every interaction triggers full page reflows.

The migration approach

Step 1: Map the interface

Before writing any code, document every page, every user flow, and every API call the frontend makes. This is your scope.

For most applications, the Pareto principle applies: 80% of user time is spent on 20% of the pages. Start with those.

Page inventory:
├── /dashboard          (daily use, high complexity)
├── /orders             (daily use, high complexity)
├── /orders/:id         (daily use, medium complexity)
├── /reports            (weekly use, medium complexity)
├── /settings           (monthly use, low complexity)
└── /admin              (admin only, medium complexity)

Migration order: dashboard → orders → orders/:id → reports → settings → admin

Step 2: Set up the new frontend alongside the old

The new React/Next.js application runs at a separate path or subdomain. A reverse proxy (nginx, Caddy, or your API gateway) routes requests to one or the other:

# New React app handles migrated pages
location /app/dashboard {
    proxy_pass http://nextjs:3000;
}

location /app/orders {
    proxy_pass http://nextjs:3000;
}

# Everything else stays on the old jQuery app
location / {
    proxy_pass http://legacy:8080;
}

Both applications hit the same API. The backend doesn't know or care which frontend is calling it.

Step 3: Build a component library first

Don't start by building pages. Start by building the components those pages are made of:

// Button component with design tokens
interface ButtonProps {
  variant: 'primary' | 'secondary' | 'danger';
  size?: 'sm' | 'md' | 'lg';
  children: React.ReactNode;
  onClick?: () => void;
  disabled?: boolean;
}

export function Button({ variant, size = 'md', children, ...props }: ButtonProps): React.ReactElement {
  return (
    <button className={`btn btn-${variant} btn-${size}`} {...props}>
      {children}
    </button>
  );
}

A design system built once is used everywhere. It's the single biggest time saver in the migration.

Step 4: Migrate page by page

Each page follows the same pattern:

  1. Audit the jQuery page. What API calls does it make? What state does it manage? What interactions does it handle?
  2. Build the React equivalent. Use the component library. Add proper state management. Write tests.
  3. Route traffic. Update the proxy to send that page's traffic to the new frontend.
  4. Validate. Monitor error rates, load times, and user feedback.
  5. Delete the old page. Once the new version is stable, remove the jQuery code.

Step 5: Handle shared state

If the old and new frontends need to share authentication state (they almost certainly do), use a shared cookie or token:

  • The backend issues the auth token. Both frontends read it.
  • Session cookies work across both if they share the same domain.
  • If using a token, store it in an HttpOnly cookie — not localStorage.

What the outcome looks like

We replaced a jQuery-based HR management tool for a 200-person company. The before and after:

MetricjQuery (before)React (after)
Page load time (LCP)6.2s1.1s
Steps to submit a leave request9 steps, 4 pages3 steps, 1 page
Mobile supportNoneFully responsive
Accessibility (WCAG 2.1 AA)FailPass
Support tickets (UI confusion)3.1/manager/month0.4/manager/month
Developer onboarding time4 hours45 minutes

The backend Rails API was unchanged. Same database, same endpoints, same business logic. The only thing that changed was what users see and interact with.

Common mistakes

1. Migrating everything at once. Treat this like a strangler fig — one page at a time, in production, with real users.

2. Ignoring the design system. If you build pages without shared components, you'll end up with the same duplication problem you had with jQuery.

3. Rewriting the API. The whole point of this approach is that the backend stays the same. If you find yourself wanting to "fix the API too," stop. That's a separate engagement.

4. Choosing a framework for its popularity. Pick the framework your team can maintain. React and Next.js have the largest ecosystem, but Vue or Svelte may be more appropriate for smaller teams.


If your team is spending more time fighting the frontend than building features, a UX modernization engagement starts with a friction audit of your current interface and delivers a component-based replacement your team can extend without a four-hour onboarding session.