2026-05-11 · 6 min read · Engineering
Why mb is HTMX over Go, not React over a JSON API
Every SaaS template since ~2017 has used the same architecture: React (or Vue / Svelte / whatever) talking to a JSON API. The frontend ships a 1+ MB JS bundle; the backend serves JSON; the glue layer maintains two type systems.
mb doesn't. It's HTMX over Go's html/template, with
zero JS framework on the browser. Total client-side JavaScript on
the dashboard page: under 25 KB (HTMX itself plus a few inline
handlers for forms). Here's why.
Who actually uses mb
The target user is an Indian SMB owner or their accountant. The device is a 2–3 year-old Android phone or a Windows 10 laptop in a small office. The network is typically 4G with intermittent dead zones; in tier-3 cities and small towns it's slower than that.
The frontend has to render fast on cold start, work on a sub-100 Lighthouse score is a hard fail. JS bundles measured in megabytes are not on the table.
The HTMX bet
HTMX is a small JS library (~15 KB gzipped) that adds attributes
to HTML elements: hx-get, hx-post,
hx-target, hx-swap. The server returns
HTML fragments; HTMX swaps them into the page. There is no
JavaScript framework, no virtual DOM, no client-side router.
The pattern works because the user's needs on a tax-software screen are simple:
- Submit a form, see the result.
- Click a row, see a detail.
- Filter a list, see a smaller list.
- Type a number, see a helper update.
None of this requires client-side state management. The server knows what to render. HTMX is the wire that connects them.
Concrete: the ITC eligibility hint
On the new-purchase form, when the user types an HSN code, mb suggests a default ITC eligibility (eligible / ineligible / blocked) based on §17(5) of the CGST Act. Motor vehicle HSN codes get auto-flagged as Blocked, food/beverage codes likewise, and so on.
In a React SaaS, this would be: client-side JS module imports a list of blocked HSN codes, runs the regex match in the browser, updates a piece of state, the component re-renders. Bundle size cost: a few KB for the rule table, the regex engine is free.
In HTMX, this is:
<input name="line_hsn_sac" hx-get="/purchases/_itc_suggest" hx-include="closest tr" hx-target="closest tr find .itc-suggest-hint" hx-trigger="change, keyup changed delay:500ms" ... /> <span class="itc-suggest-hint"></span>
The endpoint /purchases/_itc_suggest is a 30-line
Go handler that returns an empty body or a tiny <small>
snippet. The browser receives 0 to ~50 bytes per keystroke, with
a 500 ms debounce. The "rule table" lives only on the server, so
updating the §17(5) interpretations is a one-line code change with
no bundle re-deploy.
What we give up
Honesty time. HTMX is the wrong choice for:
- Real-time collaboration (multi-cursor editing, live presence).
- Offline-first workflows where the user expects to keep working without network.
- Drag-and-drop dashboard builders, kanban boards, anything with rich gesture handling.
- Streaming media or low-latency animation.
None of these matter for GST invoicing. If they did, we'd reach for a SPA. Probably Svelte or solid-js — but not React, which is a separate post.
What we get
- One language end-to-end. Go for handlers, Go for tests, Go for the cli tools, html/template for views.
- One type system. No frontend DTO drift from the backend schema.
- Fast cold-start. Under 50 KB total transferred for an empty dashboard page.
- No build step on the frontend.
go buildproduces a single binary that embeds the templates. - Server-rendered HTML is indexable and printable out of the box. Quote share URLs work as-is on link unfurlers without any "OG image" hacks.
Numbers from staging
On staging.billmybill.com today, the dashboard page
loads in ~120 ms on a Mumbai-Bangalore Indian-broadband connection,
transfers about 47 KB total (HTML + CSS + HTMX + fonts subset),
and renders without any client-side hydration step. The same flow
on a typical Indian Jio 4G with 2-bar reception is around 350 ms.
A comparable invoicing SaaS built in React tends to ship 1–3 MB of JS and render in 2–4 seconds on the same network. The user sees a spinner during that gap.
When we'd reconsider
If we built a mobile app, we'd ship native or a real React Native front. If we added real-time multi-user collaboration on the same invoice draft, we'd reach for WebSockets and a frontend framework to manage the state machine. Neither is on the roadmap.
The point
The choice of architecture has consequences for who can use your product. SPA-by-default has gradually but firmly excluded users on slow networks and old devices — which in India is most users. HTMX is the right tool for the workflow we're building, for the people we're building it for.
Want the longer version with numbers per page? Drop us a line.