✍️ 필사 모드: HTMX & Hyperscript — The Anti-SPA Camp in 2026, Carson Gross's Hypermedia Thesis Deep Dive (2026)
EnglishPrologue — SPA Fatigue, and Carson's One Sentence
Picture an average day for a frontend developer in May 2026.
You wait for pnpm install while making another coffee. You boot Vite dev server. You redesign the React Query cache invalidation policy. Yesterday's build that worked dies today with Module not found: Can't resolve 'crypto'. Someone added a webpack polyfill to next.config.js, so now Turbopack is angry. Building one form drags in seven abstractions — useState, useEffect, useMutation, a zod schema, a react-hook-form resolver, optimistic update logic, error boundaries, loading spinners. What that form actually does is POST /todos and refresh part of the screen.
Into this familiar landscape one person drove a knife. Carson Gross, professor of computer science at Montgomery College, rewrote intercooler.js in 2020, named it HTMX, and summarized an era in one sentence.
"HTML is already hypermedia. All we did was unlock its potential in 11 KB."
HTMX is not a new framework. It adds four attributes to HTML. hx-get, hx-post, hx-swap, hx-target. Forms no longer need JavaScript handlers. Buttons send POST directly to the server. The server returns an HTML fragment. That fragment replaces part of the page. That's it.
But the simplicity is more than syntax. Carson put a book on top of it. Hypermedia Systems — a re-reading of Roy Fielding's REST dissertation that takes HATEOAS seriously and asks "why did we build a JSON API, then a client-side router on top of it, then state management on top of that, then a cache on top of that, then an optimizing compiler on top of all that?" The answer is provocative. "We didn't need to."
This essay maps the HTMX camp — the anti-SPA camp, or the "boring web" renaissance — in 2026. HTMX 2.x core, _hyperscript as Carson's second creation, Datastar as the next-gen candidate, Alpine.js as the compromise position, the conceptual framework from Hypermedia Systems, production cases like 37signals, and — most importantly — where HTMX is wrong.
This is not an HTMX apologetic. The goal is to give you every tool needed to decide when HTMX is right and when React is right in a single breath.
1. The Hypermedia Thesis — Re-reading Roy Fielding
Every HTMX claim begins with one dissertation. In 2000, Roy Fielding's "Architectural Styles and the Design of Network-based Software Architectures" — the document we shorten to REST.
Fielding put six constraints on REST. Client-server, stateless, cacheable, uniform interface, layered system, and — HATEOAS: Hypermedia As The Engine Of Application State.
The last one is the kernel. HATEOAS says "the client must obtain information about the next action only from hypermedia returned by the server." Responses must contain "what you can do next" as links and forms.
Carson's diagnosis:
- 99% of what we call REST today follows the other five constraints and skips HATEOAS.
- JSON APIs give data but not actions. So clients must encode "what is possible next" in their own code.
- That makes the client a duplicate of the domain model, forcing two-way synchronization.
- HTML, by contrast, is hypermedia. The
formtag contains action. Theatag contains state transition.
HTMX's thesis is then simple.
"Browsers are already excellent hypermedia clients. HTMX extends that client's vocabulary."
Plain HTML triggers server requests only on link clicks and form submits. HTMX extends that.
- Any event can be a trigger (
hx-trigger) - Any HTTP verb is available (
hx-get,hx-post,hx-put,hx-patch,hx-delete) - Any part of the page can be the swap target (
hx-target,hx-swap)
That is all of HTMX. One line summary — "any element, any event, any HTTP verb, any swap target."
2. HTMX 2.x Core — Four Attributes That Cover 99%
HTMX 2.0 went GA in January 2024. 2.1 shipped in 2025, 2.2 in early 2026. Current stable is 11.4 KB gzipped from htmx.org. Zero dependencies. IE11 support remains on the 1.x line; 2.x targets evergreen browsers only.
2.1 Installation — One Script Tag
<script src="https://unpkg.com/htmx.org@2.0.4"></script>
That's it. An npm package exists, but HTMX explicitly recommends the script tag. No build step, no bundler, no tree-shaking anxiety.
2.2 First Example — A Search Form
In a traditional SPA, "live search results that update as you type" requires — useState, useDeferredValue, debounce hooks, cache invalidation, abort controllers, a loading state machine — at minimum 50 lines of React.
HTMX version:
<input type="search" name="q"
hx-get="/search"
hx-trigger="input changed delay:300ms, search"
hx-target="#results"
hx-indicator=".spinner" />
<div id="results"></div>
<div class="spinner htmx-indicator">Searching...</div>
Reading what happens line by line.
hx-get="/search"— On trigger, fireGET /search?q=.... Thename="q"attribute becomes the query parameter automatically.hx-trigger="input changed delay:300ms, search"— Trigger oninputevents when the value changes and 300ms passes without another event. Or when the user hits the search key.hx-target="#results"— Replace contents of#resultswith the response.hx-indicator=".spinner"— While the request is in flight, addhtmx-requestclass to.spinner. Show/hide via CSS.
The server? Express, FastAPI, Django, Rails, Phoenix, Spring — anything that can return HTML fragments.
@app.get("/search")
def search(q: str):
results = db.search(q)
return render_template("results_fragment.html", results=results)
What this pattern means is deep. Loading state, debounce, abort, race condition, caching — all handled by HTMX. You write only business logic.
2.3 The 13 Core Attributes
HTMX's surface area is small. Thirteen core attributes express almost everything.
| Attribute | Role |
|---|---|
hx-get / hx-post / hx-put / hx-patch / hx-delete | Fire HTTP request |
hx-trigger | What event triggers it (click, input, every 2s, revealed, ...) |
hx-target | Which element to apply the response to (CSS selector) |
hx-swap | How to apply it (innerHTML, outerHTML, beforebegin, afterend, ...) |
hx-vals | Extra values to send (JSON or JS function) |
hx-headers | Custom headers |
hx-indicator | Indicator during request |
hx-confirm | confirm() dialog before sending |
hx-push-url | Update browser URL on response |
hx-boost | Auto-upgrade plain links/forms to AJAX |
hx-boost alone is interesting. Add <body hx-boost="true"> and every a and form on the page automatically becomes AJAX, swapping only the body portion of responses. Turbo and Inertia-like effects in one attribute.
2.4 The Power of hx-swap — Declarative DOM Mutation
hx-swap decides how the response HTML is applied to the DOM.
innerHTML(default): replace target's contentsouterHTML: replace the target itselfbeforebegin/afterbegin/beforeend/afterend: insert at adjacent positionsdelete: remove the target (ignore response)none: no DOM change (side effects only)
Add modifiers like hx-swap="outerHTML swap:1s settle:1s scroll:#top" and you get — 1 second wait before swap (animation), 1 second settle (apply transition classes), scroll to #top when done — all in one line.
2.5 The Power of hx-trigger — An Event DSL
hx-trigger is a small DSL.
<!-- Auto-refresh every 5 seconds -->
<div hx-get="/notifications" hx-trigger="every 5s"></div>
<!-- Trigger once when entering viewport (lazy load) -->
<div hx-get="/heavy-section" hx-trigger="revealed"></div>
<!-- Trigger on another element's event -->
<div hx-get="/related" hx-trigger="updated from:#article"></div>
<!-- Keyboard shortcuts -->
<form hx-post="/save" hx-trigger="keyup[ctrlKey&&key=='s'] from:body"></form>
Polling becomes one line with every Ns. Infinite scroll becomes one line with revealed. from:#selector listens to events on other elements. In domains where polling is enough — notifications, stock tickers, live counters — HTMX answers immediately without WebSockets.
3. Hyperscript — Carson's Second Provocation
HTMX handles things that can use a server round-trip. But pure client-side actions like "toggle the class of the div next to this button when clicked" are also common. Bringing in React is overkill. jQuery feels dated.
Carson placed _hyperscript (underscore-prefixed, shortened to _hs) into that gap. An event-handler DSL that reads like English sentences.
<button _="on click toggle .active on #panel">Toggle</button>
Read it as a sentence. "On click, toggle the class .active on #panel." Plain English. The parser accepts it as-is. The inspiration is HyperTalk (the HyperCard scripting language).
3.1 A More Complex Example
<input _="on input
if my value's length < 3
hide #suggestions
else
show #suggestions
put 'Searching...' into #suggestions
end" />
What is this? If input length is under 3, hide #suggestions. Otherwise show it and fill it with "Searching...". The code reads as English.
3.2 fetch in One Line, Async
<button _="on click
fetch /api/heavy
put it into #result">
Load
</button>
After fetch /api/heavy, it on the next line refers to the response. put it into #result — drop the response into #result. No JavaScript Promise or async/await dissonance.
3.3 Common Patterns
- Toggle class:
on click toggle .open on .menu - Confirmation:
on click if confirm('Sure?') trigger submit on me - Timer:
on load wait 3s then add .visible to #toast - Drag:
on mousedown set $dragging to me on mousemove if $dragging then ... - Event delegation:
on click from .row in me ...
3.4 The Reality of Hyperscript Adoption — Honest in 2026
Let us be honest. Hyperscript has not been adopted nearly as widely as HTMX. GitHub stars sit around 1/10 of HTMX's. The reasons are clear.
- VS Code syntax highlighting is weak — strings within quotes do not get colored.
- Error messages are thin — being English sentences makes errors hard to localize.
- No TypeScript integration — no type safety.
- Alpine is more familiar — JavaScript-style expressions feel native to React/Vue veterans.
So most HTMX users pair HTMX with Alpine.js instead of Hyperscript. Which brings us to the next chapter.
4. Alpine.js — A Bridge for Those Who Want Reactivity
Built by Caleb Porzio, Alpine.js fuses "Vue 3's reactivity, jQuery's directness, Tailwind's inline approach." 15 KB. Zero dependencies. Established itself as the natural companion to HTMX.
4.1 Basics
<div x-data="{ open: false }">
<button @click="open = !open">Toggle</button>
<div x-show="open" x-transition>Hello!</div>
</div>
x-data— opens a component scope and defines a reactive state object@click(orx-on:click) — event handlerx-show— conditional rendering (display toggle)x-transition— automatic transitions
Five directives express 90%. The rest is x-text, x-html, x-bind, x-model, x-for, x-if.
4.2 HTMX + Alpine — The Golden Combo
HTMX for server round-trips. Alpine for local UI state. They do not conflict and merge naturally.
<div x-data="{ mode: 'list' }">
<button @click="mode = 'list'" :class="{ 'active': mode === 'list' }">List</button>
<button @click="mode = 'grid'" :class="{ 'active': mode === 'grid' }">Grid</button>
<div hx-get="/items" hx-trigger="load" hx-target="this">
<!-- Server-rendered HTML lands here -->
</div>
</div>
View-mode toggle (client state) goes to Alpine. Item loading (server state) goes to HTMX. Each does what it is good at.
4.3 alpine-ajax — The Other Side of the Coin
Interestingly, the Alpine camp has a third-party plugin called alpine-ajax. It does HTMX-like server communication on top of Alpine. The existence of this tool is itself a message. The HTMX thesis and the Alpine thesis see the same thing from different entry points.
5. Datastar — The Next-Gen Hypermedia Framework
Delaney Gillilan's Datastar is a provocative attempt to merge HTMX + Alpine into one library. 1.0 shipped in 2024. As of 2026, 1.3 is stable. ~10 KB.
5.1 The Key Difference — SSE First
HTMX receives ordinary HTTP responses and swaps the DOM. Datastar treats Server-Sent Events (SSE) as a first-class citizen. A single request receives multiple fragment updates as a stream.
<button data-on-click="@get('/calculate')">
Calculate
</button>
<div id="step1"></div>
<div id="step2"></div>
<div id="final"></div>
The server takes a single request and responds via SSE.
event: datastar-fragment
data: selector #step1
data: fragments <div id="step1">Step 1 done</div>
event: datastar-fragment
data: selector #step2
data: fragments <div id="step2">Step 2 done</div>
event: datastar-fragment
data: selector #final
data: fragments <div id="final">Result: 42</div>
LLM streaming, progress display, live dashboards — that is Datastar's home turf. HTMX can do similar things via the htmx-ext-sse extension, but Datastar was designed with SSE assumed from the start.
5.2 Expressions — Alpine Style
<input data-bind-name />
<div data-text="$name"></div>
<button data-on-click="$count++">+</button>
<p data-text="$count"></p>
data-bind-*, data-text, data-on-* — almost compatible vocabulary with Alpine. Signal-based reactivity. All state is accessed via $.
5.3 Datastar vs HTMX — How to Choose
- LLM streaming/real-time dashboards → Datastar (SSE first-class)
- Traditional CRUD/form-centric → HTMX (mature ecosystem, better compatibility)
- Non-trivial client-side state → Datastar (Alpine features built in)
- Rich server-side library integration → HTMX (helpers exist for Django, Rails, FastAPI, Express)
In 2026 HTMX still has the overwhelming majority. Datastar's "SSE-native" strength entered with the LLM era, but adoption is still early-adopter.
6. Hypermedia Systems — A Book That Built a Worldview
Written by Carson Gross, Adam Stepinski, and Deniz Akşimşek, Hypermedia Systems shipped in 2023 and is open-access on GitHub. The subtitle is "HTML, HTTP, and the Hypermedia Architecture."
6.1 Questions the Book Answers
- Why did we accept the JSON API + JavaScript client pattern as the default?
- Is HATEOAS actually practical, or only academic curiosity?
- Can hypermedia work on mobile? (The book also covers Hyperview, an Android/iOS hypermedia client.)
- How does a hypermedia-built system scale?
6.2 The LoB Principle — Locality of Behavior
A core principle the book introduces is Locality of Behavior (LoB). One sentence — "An element's behavior should be understandable by looking at the element."
React separates behavior into handlers somewhere in the component tree. CSS goes into external files. JS event delegation also pulls behavior elsewhere. We were taught this is good (Separation of Concerns), but Carson proposes a different principle. Not separated concerns, but co-located behavior.
<!-- Behavior visible in one place -->
<button hx-post="/like"
hx-target="#count"
_="on click toggle .liked on me">
Like
</button>
What this button does — POST request, count update, class toggle — you do not need to open another file. Designers, backend developers, and AI assistants alike understand this single line.
6.3 SoC vs LoB — Which Is Right?
The answer is both are right, context decides. In large React apps, component abstractions compensate for absent LoB. In HTMX apps, screens are small and LoB works directly. For an internal tool with 5 users, follow LoB. For a SaaS aiming at 100M users, component abstractions may be required.
The point — LoB should be the default, and abstraction should be justified.
7. Production Cases — Who Actually Uses This
HTMX is not a toy. In 2026 these cases are public.
- GitHub — parts of the new PR page and issue comments. Mostly Turbo-driven but HTMX-inspired patterns are common.
- NASA JPL — many internal tools on HTMX (public conference presentations).
- Contexte — a French political news site, fully redesigned on HTMX.
- Replicant.au — an Australian indie SaaS, full HTMX + Hyperscript.
- David Heinemeier Hansson and 37signals — Hotwire (Turbo + Stimulus) is their main stack, but as the loudest voice in "frontend simplification" they share the HTMX camp's worldview.
- Bunny.net — CDN company, admin dashboard on HTMX.
- Quanta Magazine — some interactive content.
DHH (David Heinemeier Hansson) is not an HTMX user, but he is the biggest voice in the "complexity is not fashion" campaign. His Hotwire is the Rails camp's HTMX, philosophically nearly identical. His 2024-2025 series of talks — "The One Person Framework", "What is Modern Web Development?" — are widely cited in HTMX circles.
7.1 How Big Can a Company Get?
Small SaaS, internal tools, content sites, admin panels, CMSes — HTMX is clearly strong here. PWAs that must behave like mobile apps, collaboration tools where offline is a first-class citizen, client-heavy games that mount a council of WebGL canvases — HTMX is weak there.
37signals' Basecamp sits somewhere between. It is collaboration software, but heavy client state like real-time sync is light.
8. Honest Weaknesses — Where HTMX Is Wrong
I said this was not an apologetic. HTMX is weak or wrong in the following areas.
8.1 Rich Client-Side State
- Figma-style collaborative whiteboards — many object states must be tracked on the client. Server round-trips are unrealistic.
- Notion-style block editors — going to the server on every keystroke is impossible.
- VS Code Web — editor state, syntax trees, LSP responses — all must be handled on the client.
For these, React/Vue/Solid are right.
8.2 Offline-First
PWAs, mobile-first, apps that must work on airplanes. HTMX assumes a server response, so offline breaks. Service Worker + IndexedDB + sync queue — that combination is SPA territory.
8.3 Heavy Client-Side Animation
- 60fps canvas drawing
- Physics simulation
- 3D interaction
- Timeline-based video editing
HTMX has little to offer here.
8.4 Server Load
HTMX hits the server on every interaction. The traffic pattern shifts from static asset-centric to dynamic HTML generation-centric. Server-side caching, edge rendering, CDN strategy become mandatory. SPAs offloaded some of that to CDN and client; HTMX servers must reabsorb it.
8.5 Mobile Networks
Response times over 200ms collapse the user experience. Optimistic update patterns are awkward; you risk the "I clicked but nothing happened" impression.
Mitigations exist — hx-disabled-elt, hx-indicator, local Alpine state for instant UI updates confirmed later by the server response. But this needs more care than an SPA that is "fast by default."
9. Decision Framework — When HTMX
Use the table below as a baseline. Sum the column scores. Positive total → HTMX. Negative → SPA.
| Question | HTMX score | SPA score |
|---|---|---|
| Is it form-centric? | +2 | 0 |
| Is it content-centric? | +2 | 0 |
| Is it an internal tool/admin? | +2 | 0 |
| Is the team 5 or fewer? | +1 | 0 |
| Is the server language strong (Django/Rails/Phoenix)? | +2 | 0 |
| Does SEO matter? | +1 | -1 |
| Is offline required? | -2 | +2 |
| Is there a lot of client-side state? | -2 | +2 |
| Are 60fps animations core? | -2 | +2 |
| Do you need to share code with a mobile app? | -1 | +2 |
| Over 50K concurrent users? | -1 | +1 |
| Designers work HTML/CSS first? | +1 | 0 |
| Existing codebase is React? | -2 | +1 |
The scores are not absolute. But they form a starting point. Near zero — you can mix the patterns. Build large regions in HTMX and isolate true SPA islands (editor, whiteboard) in React. This hybrid is increasingly common.
9.1 Migration Strategy
Existing React app, want to move to HTMX — total rewrite is almost always wrong. Use the Strangler Fig pattern.
- Write new pages in HTMX — new admin pages, new modules
- Target-migrate simple CRUD screens — form pages, list pages
- Keep client-heavy screens in React — isolate as islands (editor, dashboard)
- Shared header/navigation via server-side rendering — accelerate transitions with
hx-boost
10. The HTMX Camp on One Page
10.1 HTMX — Inline-Edit Form
<!-- Display mode -->
<div hx-target="this" hx-swap="outerHTML">
<p>Title: Hello, World</p>
<button hx-get="/posts/1/edit">Edit</button>
</div>
<!-- Server response: edit mode -->
<form hx-put="/posts/1" hx-target="this" hx-swap="outerHTML">
<input name="title" value="Hello, World" />
<button type="submit">Save</button>
<button hx-get="/posts/1">Cancel</button>
</form>
Server-side pseudocode (Python/FastAPI):
@app.get("/posts/{id}/edit")
def edit_form(id: int):
post = db.get_post(id)
return templates.TemplateResponse("post_edit.html", {"post": post})
@app.put("/posts/{id}")
def update_post(id: int, title: str = Form(...)):
db.update_post(id, title=title)
post = db.get_post(id)
return templates.TemplateResponse("post_display.html", {"post": post})
Mode transition logic, state machines, form libraries — all gone.
10.2 Hyperscript — Closing a Modal in One Line
<div id="modal" class="hidden">
<div class="overlay" _="on click hide #modal"></div>
<div class="content">
<button _="on click hide #modal">Close</button>
<p>Esc also closes.</p>
</div>
</div>
<script>
document.addEventListener('keydown', e => {
if (e.key === 'Escape') document.getElementById('modal').classList.add('hidden')
})
</script>
That JavaScript can also be one Hyperscript line.
<body _="on keydown[key=='Escape'] from window hide #modal">
10.3 Datastar — LLM Streaming
<button data-on-click="@post('/chat', { messages: $messages })">
Send
</button>
<div id="response"></div>
The server sends token-level fragments via SSE.
event: datastar-fragment
data: merge inner
data: selector #response
data: fragments Hello
event: datastar-fragment
data: merge inner
data: selector #response
data: fragments Hello,
event: datastar-fragment
data: merge inner
data: selector #response
data: fragments Hello, world!
Datastar assumes this use case from the start. HTMX can do similar via the htmx-ext-sse extension, but it is not first-class.
Epilogue — The Renaissance of the Boring Web
The first decade of the web was simple. There was HTML, forms, links, and servers. Then jQuery came, Ajax came, Backbone and Angular came, and React arrived — and everything we built suddenly had to be rendered twice. Once on the server, once on the client. Once in Python, once in JavaScript. And a larger and larger fraction of our energy went into reconciling the two.
Carson Gross stopped at some point and asked. "Did we really need all this?"
The answer — sometimes yes, sometimes no. HTMX is not right everywhere. But half the places we used React, React was not right either. And half of that half — admins, CRUD, internal tools, content sites — one line of HTMX was enough.
The reason the HTMX camp is gaining strength in 2026 is simple. In the AI era, simplicity becomes a more valuable resource. LLMs are weak at tracking state flow through a React component tree, but they instantly understand <button hx-post="/like">. Codebases that collaborate with AI assistants benefit disproportionately from LoB.
The boring web is not over. It was just briefly forgotten.
Checklist — Before Starting Your Next Project
- Is it form-centric or data-centric? Form-centric → start with HTMX
- Strong integration with a server language? Django/Rails/Phoenix → HTMX is natural
- Is offline a core requirement? → SPA or PWA
- Are 60fps canvas/3D the core? → SPA
- Do designers work directly in HTML/CSS? → HTMX is familiar to them
- Team size 5 or fewer? → simplicity ROI is highest
- Are you pair-programming with AI assistants? → LoB code is better understood
Anti-Patterns — Do Not
- HTMX as SPA mimicry — do not handle every page transition with
hx-boostand a client router. Keep server-side routing so URLs work consistently - Server round-trip for every interaction — pure UI actions like class toggles belong in Hyperscript/Alpine. Do not call the server where it is not needed
- Giant HTML fragments — response fragments must be small. If you replace half the page on every update, SoC is breaking down
- Hyperscript AND Alpine together — they cover similar territory. Pick one
- No tests — HTMX hits the server more often. Integration tests (Playwright-style) become more important
- JSON API design that ignores hypermedia — designing a REST API as JSON and then layering HTMX pages on top equals the two-way-sync SPA pattern. Make hypermedia the first-class response
- Starting without knowing the extensions — learn
htmx-ext-sse,htmx-ext-ws,htmx-ext-response-targets. The small core requires extensions often
Next Post Preview
The next post covers a practical layout for the HTMX + Django/FastAPI/Rails backend stack — template fragment management strategies, auth flow, file upload, form validation, CSRF, and "HTMX-friendly controller patterns." After that, hybrid patterns mixing HTMX and React islands on one page — a Notion-style editor mounted as React with HTMX wrapping around it.
References
- HTMX official — https://htmx.org/
- HTMX docs — https://htmx.org/docs/
- HTMX essays by Carson Gross — https://htmx.org/essays/
- "Hypermedia Systems" (full book, free) — https://hypermedia.systems/
- "How Did REST Come To Mean The Opposite of REST?" — https://htmx.org/essays/how-did-rest-come-to-mean-the-opposite-of-rest/
- "Locality of Behaviour" essay — https://htmx.org/essays/locality-of-behaviour/
- _hyperscript official — https://hyperscript.org/
- Alpine.js — https://alpinejs.dev/
- Datastar — https://data-star.dev/
- Hotwire (37signals) — https://hotwired.dev/
- Roy Fielding's REST dissertation — https://www.ics.uci.edu/~fielding/pubs/dissertation/top.htm
- "The One Person Framework" by DHH — https://world.hey.com/dhh/the-one-person-framework-711e6318
- HTMX GitHub — https://github.com/bigskysoftware/htmx
- HTMX 2.0 release notes — https://htmx.org/posts/2024-06-17-htmx-2-0-0-is-released/
- "HTMX in Production" conference talks (HTMX Conf 2025) — https://htmx.org/conf/
현재 단락 (1/305)
Picture an average day for a frontend developer in May 2026.