Skip to content

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

  1. IPAM/DCIM is server-authoritative. When you assign 10.0.10.1 to 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.
  2. 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.
  3. 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.
  4. 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.
  5. 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:

  1. Add a frontend/<feature>/ dir at the project root with Vite + React + TypeScript
  2. Build to a single bundle
  3. Mount it inside its Django template: <div id="rack-editor" data-rack-id="…"></div>
  4. The page chrome (sidebar, topbar, breadcrumb) stays Django templates
  5. 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.html carries six hx-* attributes on the filter form; #table-wrapper is 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:

  1. Continue building features as server templates + htmx. Today's stack.
  2. 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
  3. 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.