How Replacing 12,000 Lines of React with HTMX Cut Load Times by 70% and Simplified Our Architecture

A team swapped a heavyweight React front‑end for HTMX, removing over twelve thousand lines of code, shrinking bundle size, speeding up first‑paint and interaction times, and shifting complexity back to the server while preserving functionality and improving developer onboarding.

DevOps Coach
DevOps Coach
DevOps Coach
How Replacing 12,000 Lines of React with HTMX Cut Load Times by 70% and Simplified Our Architecture

Background and Motivation

Our legacy React front‑end had become bloated: every change required a new hook, bugs required traversing three layers of abstraction, dependency upgrades caused Webpack errors, and new hires struggled with a simple form spread across five files and a global store. By the end of a sprint, React was no longer the critical path.

Experiment with HTMX

During a weekend we tried HTMX on a low‑risk page—a loan table with a filter and a detail panel. Within two days the single page grew into half a dashboard, and by sprint end React was replaced by HTMX.

Implementation Details

HTMX required only a few HTML attributes, eliminating the need for a virtual DOM, API layer, or extensive client‑side state. The core HTML looked like this:

<table id="loan-table">
  <thead>
    <tr><th>Id</th><th>Customer</th><th>Status</th></tr>
  </thead>
  <tbody hx-get="/loans"
         hx-trigger="load, change from:#filters"
         hx-target="#loan-table tbody"
         hx-swap="innerHTML">
  </tbody>
</table>
<div id="details"
     hx-get="/loans/first"
     hx-trigger="load"
     hx-target="#details"
     hx-swap="innerHTML">
</div>

The server side was a simple Spring controller handling all logic:

@GetMapping("/loans")
public String loans(Model model, @RequestParam Map<String,String> filters) {
    List<Loan> loans = loanService.find(filters);
    model.addAttribute("loans", loans);
    return "fragments/loan-table-body";
}

@GetMapping("/loans/{id}")
public String loanDetails(@PathVariable Long id, Model model) {
    Loan loan = loanService.get(id);
    model.addAttribute("loan", loan);
    return "fragments/loan-details";
}

No JSON, no client‑side state—only server‑rendered HTML fragments swapped into the page.

Performance Gains

Initial HTML load time dropped from ~2.4 s to under 0.9 s.

JavaScript transfer size fell from ~350 KB to <40 KB.

95th‑percentile latency for a simple filter went from 400 ms to 80 ms.

These improvements were measured with Grafana dashboards, not marketing slides.

Architectural Changes

Complexity moved from the browser to the backend. The UI no longer needed a separate API; server‑rendered fragments handled routing, state, and business logic. Debugging became easier because issues could be traced directly in server templates.

When to Use HTMX vs React

HTMX shines when:

The app consists mainly of forms, lists, and buttons.

All critical business rules reside on the server.

You want to keep the mental model simple.

React remains a good choice when:

You need a desktop‑like experience in the browser.

Complex client‑side interactions require frequent server round‑trips.

Shared component libraries across multiple teams/products are essential.

Practical Advice for Trying HTMX

Select a problematic page—typically a simple table or multi‑step form with excessive JavaScript. Rewrite it using only HTMX attributes and server‑rendered fragments. Measure bundle size, time‑to‑first‑meaningful‑paint, and time‑to‑first‑interaction. If the numbers improve and the code feels lighter, you’ve validated the approach.

frontendPerformanceWebHTMX
DevOps Coach
Written by

DevOps Coach

Master DevOps precisely and progressively.

0 followers
Reader feedback

How this landed with the community

Sign in to like

Rate this article

Was this worth your time?

Sign in to rate
Discussion

0 Comments

Thoughtful readers leave field notes, pushback, and hard-won operational detail here.