Recommended resources
The freely available resources this lesson will direct you to.
In our previous lesson, we moved from the abstract principles of the migration rubric to a practical analysis, deconstructing a complex React data table into its future hypermedia components. This exercise gave you a feel for how a single, stateful React feature dissolves into a collection of server-rendered fragments and HTMX-driven interactions.
Today, we will formalize that process. Your goal is to draft a component-by-component migration plan for a small but complete React application. This plan will serve as your architectural blueprint for the migration, translating the familiar world of React components into a concrete set of server-side templates and Express endpoints. This document is the critical link between strategy and execution, and it will guide our hands-on work in the lessons to come.
Case Study: A React Contact Manager
To make this planning exercise concrete, let's imagine a small but realistic CRUD application you might have built: a "Contact Manager". Its feature set is straightforward:
- A main view with a list of contacts in a sidebar.
- Clicking a contact displays their details in a main content area.
- Functionality to create, edit, and delete contacts.
As an experienced developer, you would likely structure this in React with a clear separation of concerns. A plausible component hierarchy could look like this:
App.js: The root component, housing React Router to define the application's client-side routes (/,/contacts/:id,/new,/edit/:id).MainLayout: A presentational component that creates the two-column layout (sidebar and main content).ContactList: A "smart" component that fetches the list of all contacts (perhaps on mount usinguseEffect) and manages that list in its state. It rendersContactListItemcomponents.ContactListItem: A simple component that displays a contact's name and uses React Router's<Link>to navigate to the details view.ContactDetails: A component that fetches the details for a specific contact based on the URL parameter and displays them. It would contain buttons to trigger the "edit" or "delete" flows.ContactForm: A reusable controlled form component for both creating and editing a contact. It would manage form state, handle input changes, and manage submission logic.
Our task is to systematically map this React component tree to a new server-driven architecture and document the plan.
Drafting the Migration Plan
The migration plan is the central artifact that translates your analysis into an actionable engineering backlog. A clear, tabular format works best. For each conceptual piece of the React application, we will define its destination in the new world.
Here is the structure of our plan:
| React Component / Concept | Migration Target | HTMX / Server-Side Implementation | Required Express Endpoint(s) |
|---|---|---|---|
(e.g., ContactDetails) |
(e.g., HTMX Partial) | (e.g., contact.pug) |
(e.g., GET /contacts/:id) |
Let's now populate this plan, piece by piece.
1. Routing and Layout
| React Component / Concept | Migration Target | HTMX / Server-Side Implementation | Required Express Endpoint(s) |
|---|---|---|---|
App.js (React Router) |
Server-Rendered Page / Express Router | The concept of a single-page shell disappears. Each URL corresponds to a server route that can render a full page. | app.get('/'), app.get('/contacts/:id'), etc. |
MainLayout |
Server-Side Base Template | A base template (e.g., layout.ejs) defines the main HTML structure, including the head, header, and the two-column layout containers. Other templates will extend this. |
N/A (Used by other routes) |
The most fundamental shift is the elimination of client-side routing. What was once the responsibility of the App.js shell is now handled entirely by your Express server.
2. Displaying Data
| React Component / Concept | Migration Target | HTMX / Server-Side Implementation | Required Express Endpoint(s) |
|---|---|---|---|
ContactList |
HTMX Partial | An _sidebar.ejs partial contains the logic to iterate over a contacts array and render the list. The data-fetching logic from useEffect moves into the Express route handler. |
GET / (initial render) |
ContactListItem |
HTMX-enhanced Link | Each <li> in the sidebar becomes an <a> tag with hx-get, hx-target, and hx-push-url. This is the core hypermedia control. |
GET /contacts/:id |
ContactDetails |
HTMX Partial | A _contact.ejs partial renders the details of a single contact object. This partial is the target of the links in the sidebar. |
GET /contacts/:id |
Here we see the pattern from the last lesson repeated. "Smart" components like ContactList have their logic absorbed by the server. Presentational components like ContactListItem and ContactDetails become server-rendered partials, wired together with HTMX attributes instead of React Router <Link> components.
The video "Htmx application structure" provides a fantastic visualization of this process. It demonstrates how to deconstruct a page into logical sections and then extract individual list items into their own partials, which can then be swapped or updated independently.
This video shows the fundamental thought process of structuring an HTMX application by breaking down a static page into server-renderable partials.
Watch the section from this point where the presenter breaks the main page into sections and then extracts each row of a list into its own partial file. This is the foundational step for enabling dynamic updates, where the server can re-render just one part of the page (the partial) instead of the whole thing.
3. Creating, Updating, and Deleting Data (CRUD)
| React Component / Concept | Migration Target | HTMX / Server-Side Implementation | Required Express Endpoint(s) |
|---|---|---|---|
ContactForm (Create) |
HTMX Partial | An _form.ejs partial is rendered. The form uses hx-post to submit. On success, the server responds with an updated sidebar partial, using hx-swap-oob if needed for notifications. |
GET /contacts/newPOST /contacts |
ContactForm (Edit) |
HTMX Partial | The same _form.ejs partial is used. The server pre-populates it with contact data. The form uses hx-put to submit to a different endpoint. |
GET /contacts/:id/editPUT /contacts/:id |
| "Delete" button | HTMX-enhanced Button | A <button> inside a <form> tag uses hx-delete. On success, the server can either respond with an updated sidebar or an empty response that triggers the removal of the contact from the details view. |
DELETE /contacts/:id |
This is where the power of the hypermedia approach truly shines. A single, complex, state-managing ContactForm component in React is replaced by a much simpler _form.ejs partial whose logic is differentiated entirely by the server. As the article "From React to HTMX—Migration Strategy" notes, props become context variables passed to the template, and onClick handlers become HTMX attributes.
This component-to-template mapping is a core activity in your migration.
From React to HTMX—Migration Strategy and Risk Assessment ...
This article provides a high-level strategic overview of migration. We'll focus on the section that explicitly discusses mapping components to templates.
Read the section titled "How Do You Migrate from JSX to Server-Side Templates?" Pay close attention to the paragraph that begins "For example, a React user profile card maps to a server-side template partial." This directly describes the process we are undertaking. Also, review the FAQ question What happens to my React component library during migration?
The Completed Server-Side Blueprint
After completing our plan, the "Required Express Endpoints" column gives us a complete blueprint for our server-side implementation. It's the API contract for our new application.
Let's consolidate it:
GET /contacts: Renders the full page, including the layout and the initial contact list sidebar.GET /contacts/new: Renders the full page with the create form in the content area.POST /contacts: Handles new contact creation. Responds with a partial (e.g., the updated sidebar) and potentially an Out-of-Band swap for a success message.GET /contacts/:id: Responds with a full page if it's a direct navigation. Responds with only the_contact.ejspartial if it's anHX-Request.GET /contacts/:id/edit: Responds with a full page, or just the pre-filled_form.ejspartial on anHX-Request.PUT /update/:id: Handles contact updates. Responds with multiple OOB partials (the updated sidebar and the updated details view).DELETE /delete/:id: Handles contact deletion. Responds with an updated sidebar partial.
This list of routes and behaviors is not just theoretical. It's almost exactly the architecture built in the "Build a Full-stack App with Node.js and htmx" tutorial. This gives us high confidence that our plan is sound and follows established best practices. I strongly recommend you review this article, as it serves as a practical, working example of the very plan we've just drafted.
Build a Full-stack App with Node.js and htmx - SitePoint
This tutorial is a complete implementation of the Contact Manager application we've been planning. It will serve as an excellent reference for the patterns we've discussed.
I recommend skimming the entire article to see how the pieces fit together. Pay particular attention to: The Express server setup with the Pug templating engine. How index.pug acts as the base layout, and how partials like sidebar.pug, contact.pug, and form.pug are included. The route handler for displaying a single contact, which checks for the HX-Request header. The use of out-of-band swaps for showing flash messages. The implementation of the Edit and Update logic, which reuses the same form template.
Conclusion
In this lesson, you have taken a significant step from analysis to concrete planning. By systematically deconstructing a sample React application and mapping its parts to a server-centric architecture, you've created a formal migration plan.
Key Takeaways:
- A migration plan is an essential document that translates your React component hierarchy into a set of server routes and templates.
- Client-side routing (React Router) maps directly to server-side routing (Express Router).
- Stateful React components often dissolve, with their logic moving into server-side route handlers.
- Presentational components and controlled forms become server-rendered partials, wired together with HTMX attributes.
- The "Required Endpoints" column of your plan becomes the technical specification for your new backend.
You now possess the blueprint for your migration. In our next lesson, we will begin the hands-on work of building out this plan, starting with the foundation: replacing React Router with Express routes and serving our main application layout.