Frontend: htmx-first, React islands¶
Status: decided · 2026-06-14
TL;DR¶
Danbyte commits to server-rendered Django + htmx as the primary frontend stack. React is reserved for interactive islands (drag-drop rack editor, topology canvas, Kanban-style change planner) that we'll mount into Django templates when — and only when — we actually hit them. No app-wide React migration is planned.
The earlier "Phase 3 = React frontend" line in the original setup guide is superseded by this doc.
Why htmx wins for this product¶
- IPAM/DCIM is server-authoritative. When you assign
10.0.10.1to a device, optimistic client state is a bug source: two users assigning addresses concurrently shouldn't each see "their" view of who got what until the DB says so. The htmx model — every interaction → server → authoritative HTML fragment → swap — eliminates a whole class of sync-state bugs. - Tenant isolation is easier when nothing crosses the wire that isn't already scoped. With React + JSON API we'd need to be paranoid about responses leaking another tenant's data. With htmx, every response is a fully-rendered fragment from a server-scoped queryset; the trust boundary is the server, where we can enforce it once.
- Most interactions are CRUD + table operations. Filter, sort, paginate, edit a row, bulk-update. htmx handles this with ~6 attributes per form; React would need 10× the code for the same UX.
- One language. Python + HTML is a much larger hiring pool than Django + React + TypeScript + Next.js + state-library-of-the-month. For a small project this is a meaningful multiplier on shipping speed.
- We've already built ~12 pages this way. Switching now means rebuilding all of them. htmx is a zero-rewrite upgrade from where we are today.
When React is the right tool¶
Reserved specifically for interactive islands — pages where the interaction model is genuinely client-rich:
| Page / feature | Why React |
|---|---|
| Rack editor (DCIM) | drag-drop devices into rack U slots, live re-stack, multi-select |
| Topology canvas | force-directed graph of devices + cables; pan/zoom; node selection |
| Kanban-style change planner (if scope grows) | column drag, optimistic reorder |
| Possibly: rich diff viewer for prefix imports | side-by-side, expand/collapse |
For each of these we'd:
- Add a
frontend/<feature>/dir at the project root with Vite + React + TypeScript - Build to a single bundle
- Mount it inside its Django template:
<div id="rack-editor" data-rack-id="…"></div> - The page chrome (sidebar, topbar, breadcrumb) stays Django templates
- Server still owns the data; React just owns the interaction on that page
This is the islands architecture GitHub uses (server-rendered Ruby + React islands for the editor and PR review). It's pragmatic, low-risk, and ratchets only forward — we never "migrate to React"; we add React where it's earned.
Decision matrix¶
| Question | Answer |
|---|---|
| Build a new CRUD page? | Server template + htmx |
| Add a filter / search / sort? | htmx (see Tree + sections) |
| Add real-time updates? | htmx + SSE (hx-ext="sse" + Django StreamingHttpResponse) |
| Add a drag-drop interaction? | Likely React island — open a discussion in the design doc |
| Build a graph / canvas view? | React island with D3 or Cytoscape |
| Rebuild an existing page in React? | No. Stay server-rendered. |
What htmx specifically gives us today¶
- Filter rail on every list page swaps inline (no full reload). Implementation:
_list_shell.htmlcarries sixhx-*attributes on the filter form;#table-wrapperis the swap target. - Active-filter
×chips drop just that filter without reload. - Browser back/forward + bookmarking work (
hx-push-url="true"). - Stripes preference re-applies on every htmx swap via
htmx:afterSwap. - Total client-side cost: ~14KB of htmx + the inline scripts already present.
What it explicitly does not give us¶
- Heavy client-side interactivity (drag-drop, canvas, complex form wizards). When we hit these, an island is the answer.
- Offline / PWA mode. The app stays online-only.
- Mobile-native (React Native). The app stays web-only.
- Sophisticated optimistic UI patterns. For an IPAM tool, this is a feature, not a bug — see point 1 above.
Migration / non-migration plan¶
There is no migration. The work is:
- Continue building features as server templates + htmx. Today's stack.
- When a feature actually needs React (first one is likely the rack editor):
- Create
frontend/rack-editor/with Vite scaffold - Add a build step to the Makefile (
make frontend-build) - Mount the built bundle in a Django template
- Pass the page-level data via
data-*attributes or a JSON<script type="application/json">tag
- Create
- No global state library on the client. Each island is self-contained.
Reviewing this decision¶
We should revisit this if any of these become true:
- A majority of new features are interaction-rich (drag-drop, canvas, complex selectors)
- We start a mobile native app and need code-sharing with the web
- The team grows and the people we hire are React-shaped, not Django-shaped
- We need true offline support
None of these are true today. Until they are, htmx-first.