Welcome back! In our previous lesson, we explored how to replace client-side state management patterns like React Context with server-driven out-of-band (OOB) swaps in HTMX. This demonstrated how the hypermedia model can handle UI updates that affect multiple, distant parts of the page.
Now, we'll address a crucial aspect of any real-world technology transition: gradual migration. It's rarely practical or wise to rewrite a large application from scratch. This lesson will equip you with the techniques to manage a graceful, incremental migration from React to HTMX. Your learning outcome is to run React and HTMX side by side during an incremental migration by mounting React components inside HTMX-managed fragments.
This approach allows you to introduce the benefits of HTMX into your existing projects piece by piece, tackling the low-hanging fruit first while keeping your more complex React components operational until you're ready to replace them.
The "Islands" Architecture for Incremental Migration
The guiding principle for a gradual migration is often called the "Islands" or "Strangler Fig" pattern. Imagine your application as a sea of server-rendered HTML, managed and updated by HTMX. Within this sea, you have "islands" of rich interactivity that remain as self-contained React components.
Initially, your application might be one large React "continent." Your goal is to progressively shrink this continent by replacing sections of it with server-rendered, HTMX-powered "water," leaving only the most complex, stateful components as small, isolated islands.

The key challenge is bootstrapping these React islands. When HTMX fetches and swaps a piece of HTML from your Express server, that HTML is inert. If it contains a <div> meant to host a React component, you need a mechanism to trigger React to mount its component into that newly arrived div.
The Core Mechanism: Dynamic Mounting with htmx.onLoad
HTMX provides a perfect hook for this exact scenario: the htmx.onLoad function. This function allows you to register a callback that will execute every time HTMX adds new content to the DOM. This includes the initial page load, content from a standard AJAX swap, and content from an out-of-band swap.
This is the central nervous system of our coexistence strategy. We can use this callback to scan any new content for placeholders and dynamically mount our React components.
The following resource provides documentation and a clear code example for this integration pattern.
npm-htmx-org@2.0.0 • tessl • Registry • Tessl
This documentation shows the official way to integrate with frameworks by using the htmx.onLoad callback to process newly loaded content.
First, read the section on Content Load Callbacks to understand the htmx.onLoad function. Then, review the React integration example. It demonstrates exactly how to scan new content for placeholders and render React components into them.
Let's adapt that example into a practical, standalone script.
The Workflow:
-
Express Server: Your route handler will render an HTML fragment. Inside this fragment, you'll define a placeholder element for your React component, using
data-*attributes to specify which component to load and what props to pass.<!-- Server response fragment for, e.g., /partials/chart --> <div id="chart-container" data-react-component="ComplexChart" data-react-props='{"dataSet": [10, 45, 23, 89]}'> <!-- React will mount here --> </div> -
Client-Side JavaScript: You'll have a script that runs on your pages. This script will contain your "component registry" and the
htmx.onLoadlogic.// In a main JS file loaded on your page // Import your React components that will act as "islands" import ComplexChart from './components/ComplexChart'; import InteractiveDataGrid from './components/InteractiveDataGrid'; import { createRoot } from 'react-dom/client'; import React from 'react'; // 1. Create a registry mapping string names to component definitions const componentRegistry = { ComplexChart, InteractiveDataGrid, }; // 2. Use htmx.onLoad to scan for placeholders htmx.onLoad(function(target) { // Find placeholders within the newly loaded content (or on the whole // document for the initial load) const placeholders = target.querySelectorAll('[data-react-component]'); placeholders.forEach(el => { const componentName = el.dataset.reactComponent; const props = JSON.parse(el.dataset.reactProps || '{}'); const Component = componentRegistry[componentName]; if (Component) { // 3. Mount the React component using the modern API const root = createRoot(el); root.render(React.createElement(Component, props)); } else { console.error(`React component "${componentName}" not found.`); } }); });
With this setup, any HTMX request that returns a fragment containing a data-react-component placeholder will automatically have that placeholder hydrated into a fully interactive React component.
Alternative Pattern: Encapsulation with Web Components
For an even more robust and self-contained approach, you can wrap your React components inside standard Web Components (Custom Elements). This encapsulates the mounting logic and provides a cleaner interface for your server-side templates.
Instead of a generic div placeholder, your Express server would simply render a custom tag:
<my-chart data-set='[10, 45, 23, 89]'></my-chart>
The logic to mount the React component would live inside the Web Component's connectedCallback method, which fires automatically when the element is added to the DOM. This removes the need for a global htmx.onLoad scanner.
The following article series provides a fantastic deep dive into this three-way integration.
HTMX + Webcomponents + React — the series [Part 1] | by Cubode Team | Medium
This article provides a step-by-step guide on how to integrate React inside Web Components and then use HTMX to manage those components.
First, quickly skim the section on adding React to Web Components to see how a React component is rendered inside a custom element. Then, focus on Example 3. This is the key part, as it combines all three technologies, showing how HTMX can fetch data that is then used to render or update a React component living inside a Web Component.
This pattern is powerful because it creates truly modular "black box" components. The rest of your application, including HTMX, doesn't need to know or care that React is running inside; it just interacts with a standard HTML element.
A Real-World Migration Story
These patterns are not just theoretical. They form the backbone of successful, real-world migrations. The following conference talk details a team's journey from a complex, slow React application to a nimble, server-rendered application powered by HTMX (and Django). Their story provides invaluable context for the "why" behind the techniques we've just discussed.
This video is a case study of a team migrating a production SaaS product from React to HTMX, highlighting the challenges, process, and remarkable outcomes.
Please watch the final segment of the talk, beginning from the story of the migration. Pay close attention to the speaker's description of their initial React struggles, the proof-of-concept with HTMX, the performance improvements, the shift in team dynamics, and the dramatic reduction in codebase size. This is a powerful testament to the viability of the migration path you are planning.
As the speaker noted, this migration allowed them to delete over 20,000 lines of JavaScript, dramatically improve performance, and empower their entire team to work across the full stack. This is the potential that an incremental migration strategy unlocks.
Conclusion
You now have two robust, practical patterns for running React and HTMX in the same application. This is the key that unlocks a low-risk, high-reward migration path for your existing projects.
Key Takeaways:
- Incremental is Key: Avoid a "big bang" rewrite. Use the "Islands" or "Strangler Fig" pattern to migrate gradually.
- Dynamic Mounting is the Mechanism: You need a way to bootstrap React components into HTML fragments delivered by HTMX.
- Pattern 1:
htmx.onLoad: Use a global callback to scan fordata-*placeholders in new content and mount the corresponding React components. This is a direct and effective approach. - Pattern 2: Web Components: Encapsulate React components within Custom Elements for better modularity. The component's
connectedCallbackhandles its own mounting. - Proven Strategy: This is a viable, real-world migration strategy that can lead to significant improvements in performance, code simplicity, and team velocity.
In our next and final lesson in this module, we will build upon this foundation. Now that you have the tools to run both frameworks together, we'll focus on how to plan the strategic decommissioning of React, creating a roadmap for completing the migration and retiring the React build pipeline from your project.
Can't find a good explanation? Sign up and we'll make it for you
Sign up