본문으로 건너뛰기
Previous
Next
HTMX Complete Guide | Hypermedia, AJAX Without JavaScript

HTMX Complete Guide | Hypermedia, AJAX Without JavaScript

HTMX Complete Guide | Hypermedia, AJAX Without JavaScript

이 글의 핵심

HTMX brings AJAX, WebSockets, and server-sent events directly to HTML attributes — no build step, no framework. This guide covers the full HTMX toolkit with practical examples.

What This Guide Covers

HTMX lets you build dynamic web applications using HTML attributes instead of JavaScript. Your server returns HTML fragments; HTMX swaps them into the page. No build step, no npm, no framework.

Real-world insight: Replacing a React component with HTMX for a data table cut frontend complexity by 90% — the server already had the data, it just needed to return HTML instead of JSON.


Installation

No build step needed — just add the script tag:

<script src="https://unpkg.com/[email protected]"></script>

Or install via npm for bundled projects:

npm install htmx.org

1. Core Attributes

HTMX adds behavior to HTML via hx-* attributes:

AttributeDescription
hx-getSend GET request
hx-postSend POST request
hx-putSend PUT request
hx-deleteSend DELETE request
hx-triggerWhen to fire (default: natural event)
hx-targetWhere to put the response
hx-swapHow to insert the response

2. Basic Example: Load Content

<!-- Click button → GET /api/users → swap into #user-list -->
<button hx-get="/api/users" hx-target="#user-list" hx-swap="innerHTML">
  Load Users
</button>

<div id="user-list">Users will appear here</div>

Server returns an HTML fragment (not a full page):

# FastAPI example
@app.get("/api/users")
def get_users():
    return HTMLResponse("""
        <ul>
            <li>Alice</li>
            <li>Bob</li>
        </ul>
    """)

3. Swap Strategies

hx-swap controls how the response replaces content:

ValueEffect
innerHTMLReplace inner content (default)
outerHTMLReplace the element itself
beforebeginInsert before the element
afterbeginInsert at the start of the element
beforeendAppend at the end of the element
afterendInsert after the element
deleteRemove the element
noneDo nothing with the response
<!-- Append a new item to a list -->
<form hx-post="/api/todos" hx-target="#todo-list" hx-swap="beforeend">
  <input name="text" placeholder="New todo" />
  <button type="submit">Add</button>
</form>

<ul id="todo-list"></ul>

4. Triggers

By default, HTMX fires on the natural event (click for buttons, change for inputs). Use hx-trigger to customize:

<!-- Fire on input with 300ms debounce -->
<input
  hx-get="/api/search"
  hx-trigger="input delay:300ms"
  hx-target="#results"
  name="q"
  placeholder="Search..."
/>

<!-- Fire every 5 seconds (polling) -->
<div hx-get="/api/stats" hx-trigger="every 5s" hx-target="this">
  Loading stats...
</div>

<!-- Fire when element enters the viewport -->
<div hx-get="/api/more" hx-trigger="intersect once" hx-swap="afterend">
  Loading more...
</div>

5. Forms

HTMX serializes forms automatically:

<form hx-post="/api/login" hx-target="#result">
  <input name="email" type="email" />
  <input name="password" type="password" />
  <button type="submit">Login</button>
</form>

<div id="result"></div>

Server validates and returns either a success fragment or an error fragment:

@app.post("/api/login")
def login(email: str = Form(), password: str = Form()):
    if valid(email, password):
        return HTMLResponse('<div class="success">Welcome!</div>')
    return HTMLResponse('<div class="error">Invalid credentials</div>', status_code=401)

6. Infinite Scroll

<div id="posts">
  {% for post in posts %}
    <article>{{ post.title }}</article>
  {% endfor %}

  <!-- Trigger when this element enters the viewport -->
  <div
    hx-get="/api/posts?page={{ next_page }}"
    hx-trigger="intersect once"
    hx-target="#posts"
    hx-swap="beforeend"
  >
    <p>Loading more...</p>
  </div>
</div>

The server response includes the next batch of items AND a new trigger element with page={{ next_page + 1 }}.


7. Loading States with hx-indicator

<style>
  .htmx-indicator { display: none; }
  .htmx-request .htmx-indicator { display: block; }
</style>

<button hx-get="/api/slow-data" hx-target="#output" hx-indicator="#spinner">
  Load Data
</button>

<span id="spinner" class="htmx-indicator">Loading...</span>
<div id="output"></div>

8. Out-of-Band Swaps

Update multiple parts of the page from a single response using hx-swap-oob:

<!-- Server response can include OOB swaps for other elements -->
@app.post("/api/add-item")
def add_item():
    return HTMLResponse("""
        <!-- Main response: the new item -->
        <li>New Item</li>

        <!-- OOB: also update the counter -->
        <span id="item-count" hx-swap-oob="true">5 items</span>
    """)

9. WebSockets

<div hx-ws="connect:/ws/chat">
  <div id="messages"></div>

  <form hx-ws="send">
    <input name="message" placeholder="Type a message..." />
    <button type="submit">Send</button>
  </form>
</div>

Server sends HTML fragments over the WebSocket connection; HTMX swaps them in automatically.


10. Django Integration

# views.py
from django.http import HttpResponse

def search(request):
    q = request.GET.get("q", "")
    results = Product.objects.filter(name__icontains=q)
    html = "".join(f"<li>{p.name}</li>" for p in results)
    return HttpResponse(f"<ul>{html}</ul>")
<!-- template -->
<input
  hx-get="{% url 'search' %}"
  hx-trigger="input delay:300ms"
  hx-target="#results"
  name="q"
/>
<div id="results"></div>

HTMX Response Headers

Control HTMX behavior from the server via response headers:

HeaderEffect
HX-Redirect: /urlRedirect the browser
HX-Refresh: trueFull page refresh
HX-Trigger: event-nameTrigger a client-side event
HX-Retarget: #idOverride hx-target
HX-Reswap: strategyOverride hx-swap
response = HTMLResponse("<div>Done</div>")
response.headers["HX-Trigger"] = "itemAdded"
response.headers["HX-Redirect"] = "/dashboard"

Key Takeaways

  • HTMX returns HTML, not JSON — the server does the rendering
  • hx-trigger supports delays, polling, and intersection observers out of the box
  • hx-swap-oob lets one response update multiple page regions
  • HTMX pairs naturally with Django, FastAPI, Rails, and any server-side framework
  • No build step — drop in the script tag and start writing hx-* attributes

HTMX is the right tool when your app is content-heavy, your team prefers backend languages, and you want to avoid the complexity of a full JavaScript framework.


자주 묻는 질문 (FAQ)

Q. 이 내용을 실무에서 언제 쓰나요?

A. Build modern web apps with HTMX — no JavaScript framework required. Covers hx-get, hx-post, hx-swap, infinite scroll, We… 실무에서는 위 본문의 예제와 선택 가이드를 참고해 적용하면 됩니다.

Q. 선행으로 읽으면 좋은 글은?

A. 각 글 하단의 이전 글 또는 관련 글 링크를 따라가면 순서대로 배울 수 있습니다. C++ 시리즈 목차에서 전체 흐름을 확인할 수 있습니다.

Q. 더 깊이 공부하려면?

A. cppreference와 해당 라이브러리 공식 문서를 참고하세요. 글 말미의 참고 자료 링크도 활용하면 좋습니다.


같이 보면 좋은 글 (내부 링크)

이 주제와 연결되는 다른 글입니다.

  • [Django Complete Guide | MTV· ORM](/en/blog/django-complete-guide/
  • [FastAPI Complete Guide | Python REST API· Async](/en/blog/fastapi-complete-guide/

이 글에서 다루는 키워드 (관련 검색어)

HTMX, HTML, Web, Backend, Django, FastAPI, Hypermedia 등으로 검색하시면 이 글이 도움이 됩니다.