Home > Blog > tech

HTMX + Alpine.js คืออะไร? Modern Web Stack ที่ไม่ต้องเขียน JavaScript มาก 2026

htmx alpine js modern stack guide
HTMX Alpine.js Modern Web Stack 2026
2026-04-16 | tech | 3500 words

ปี 2026 Developer จำนวนมากเริ่มเบื่อกับ JavaScript Frameworks ที่ซับซ้อน (React, Vue, Angular) และหันมาใช้แนวทาง HTML-First ที่เรียบง่ายกว่า HTMX + Alpine.js คือ Stack ที่ได้รับความนิยมสูงสุดในกลุ่มนี้ เขียน JavaScript น้อยที่สุด แต่ได้ UX ที่ดีเทียบเท่า SPA

ปรัชญา: HTML-First, Sprinkle JS

# แนวคิดเดิม (SPA):
# Backend (API) → JSON → Frontend (React/Vue) → Render HTML
# ข้อเสีย: ซับซ้อน, JS Bundle ใหญ่, ต้อง Build, State Management ยาก

# แนวคิดใหม่ (HTMX + Alpine.js):
# Backend → Render HTML → HTMX ดึง HTML Fragment → Alpine.js จัดการ Client State
# ข้อดี: เรียบง่าย, ไม่ต้อง Build, HTML อ่านง่าย, SEO ดี

# สรุป:
# HTMX = ให้ HTML Attributes ทำ AJAX ได้ (Server Interaction)
# Alpine.js = ให้ HTML Attributes ทำ Client-side Reactivity (Client State)
# ทั้งคู่ = ไม่ต้องเขียน JavaScript ยาว ๆ

HTMX Recap — HTML Attributes สำหรับ AJAX

<!-- HTMX: ทำ AJAX ด้วย HTML Attributes -->

<!-- hx-get: ดึงข้อมูล -->
<button hx-get="/api/users" hx-target="#result">
    Load Users
</button>
<div id="result"></div>

<!-- hx-post: ส่งข้อมูล -->
<form hx-post="/api/users" hx-swap="afterbegin" hx-target="#user-list">
    <input name="name" placeholder="Name">
    <button type="submit">Add User</button>
</form>

<!-- hx-swap modes: -->
<!-- innerHTML (default): แทนที่เนื้อหาใน target -->
<!-- outerHTML: แทนที่ element ทั้งหมด -->
<!-- afterbegin: เพิ่มที่ต้นของ target -->
<!-- beforeend: เพิ่มที่ท้ายของ target -->
<!-- delete: ลบ target -->

<!-- hx-trigger: กำหนดเงื่อนไขที่ trigger -->
<input hx-get="/api/search"
       hx-trigger="keyup changed delay:300ms"
       hx-target="#search-results"
       name="q" placeholder="Search...">

<!-- hx-indicator: Loading indicator -->
<button hx-get="/api/data" hx-indicator="#spinner">
    Load Data
</button>
<span id="spinner" class="htmx-indicator">Loading...</span>

Alpine.js Basics — Client-side Reactivity

<!-- Alpine.js: Reactivity ด้วย HTML Attributes -->

<!-- x-data: กำหนด State -->
<div x-data="{ count: 0, name: '' }">
    <p x-text="'Count: ' + count"></p>
    <button @click="count++">+1</button>
    <button @click="count--">-1</button>
</div>

<!-- x-show: แสดง/ซ่อน -->
<div x-data="{ open: false }">
    <button @click="open = !open">Toggle</button>
    <div x-show="open" x-transition>
        Content that shows/hides
    </div>
</div>

<!-- x-if: Render/Remove -->
<template x-if="showAdvanced">
    <div>Advanced Options</div>
</template>

<!-- x-for: Loop -->
<div x-data="{ items: ['A', 'B', 'C'] }">
    <template x-for="item in items">
        <li x-text="item"></li>
    </template>
</div>

<!-- x-bind: Dynamic Attributes -->
<button :class="active ? 'bg-blue-500' : 'bg-gray-500'">
    Click Me
</button>

<!-- x-model: Two-way Binding -->
<input x-model="name" placeholder="Your name">
<p>Hello, <span x-text="name"></span>!</p>

Combining HTMX + Alpine.js

<!-- HTMX = Server Interaction, Alpine.js = Client State -->

<!-- Example: Search with Live Results + Client-side Filter -->
<div x-data="{ query: '', showFilters: false }">
    <!-- Search Input: HTMX sends to server -->
    <input x-model="query"
           hx-get="/api/search"
           hx-trigger="keyup changed delay:300ms"
           hx-target="#results"
           name="q"
           placeholder="Search...">

    <!-- Filter Toggle: Alpine.js client-side -->
    <button @click="showFilters = !showFilters">
        Filters
    </button>

    <div x-show="showFilters" x-transition>
        <select hx-get="/api/search"
                hx-include="[name='q']"
                hx-target="#results"
                name="category">
            <option value="">All</option>
            <option value="tech">Tech</option>
            <option value="finance">Finance</option>
        </select>
    </div>

    <div id="results">
        <!-- Server returns HTML fragments here -->
    </div>
</div>

Practical Examples

1. Tabs Component

<div x-data="{ activeTab: 'tab1' }">
    <div class="tabs">
        <button @click="activeTab = 'tab1'"
                :class="activeTab === 'tab1' ? 'active' : ''"
                hx-get="/api/tab1"
                hx-target="#tab-content">Tab 1</button>
        <button @click="activeTab = 'tab2'"
                :class="activeTab === 'tab2' ? 'active' : ''"
                hx-get="/api/tab2"
                hx-target="#tab-content">Tab 2</button>
    </div>
    <div id="tab-content">
        <!-- Content loaded from server -->
    </div>
</div>

2. Modal Dialog

<div x-data="{ showModal: false }">
    <button @click="showModal = true"
            hx-get="/api/user/123"
            hx-target="#modal-content">
        Edit User
    </button>

    <div x-show="showModal"
         x-transition
         class="modal-overlay"
         @click.self="showModal = false">
        <div class="modal-box">
            <button @click="showModal = false">X</button>
            <div id="modal-content">
                <!-- Form loaded from server -->
            </div>
        </div>
    </div>
</div>

3. Infinite Scroll

<div id="post-list">
    <!-- Posts rendered server-side -->
    <div class="post">Post 1</div>
    <div class="post">Post 2</div>

    <!-- Load More trigger -->
    <div hx-get="/api/posts?page=2"
         hx-trigger="revealed"
         hx-swap="afterend"
         hx-indicator="#load-spinner">
    </div>
    <div id="load-spinner" class="htmx-indicator">
        Loading more...
    </div>
</div>
<!-- Server returns next batch of posts + new trigger div with page=3 -->

HTMX + Alpine + Tailwind Stack

# The HAT Stack (HTMX + Alpine + Tailwind):
#
# HTMX: Server Communication (AJAX)
# Alpine.js: Client-side Reactivity
# Tailwind CSS: Utility-first Styling
#
# ไม่ต้อง:
# - npm install (ใช้ CDN ได้)
# - Webpack / Vite Build
# - node_modules (600MB+)
# - State Management Library
# - Router Library
#
# Setup (3 บรรทัด):
# <script src="https://unpkg.com/htmx.org@1.9"></script>
# <script src="https://unpkg.com/alpinejs@3"></script>
# <link href="https://cdn.tailwindcss.com" rel="stylesheet">
#
# Total JS: ~30KB (HTMX 14KB + Alpine 15KB)
# React + ReactDOM: ~140KB
# Vue 3: ~80KB
# → 4-5x เล็กกว่า!

Comparison: HTMX + Alpine vs React/Vue

เกณฑ์HTMX + AlpineReactVue 3
Bundle Size~30KB~140KB~80KB
Build Toolไม่ต้องVite/WebpackVite/Webpack
Learning Curveต่ำ (รู้ HTML พอ)สูง (JSX, Hooks, State)ปานกลาง
SEOดี (Server-rendered HTML)ต้อง SSR (Next.js)ต้อง SSR (Nuxt)
State ManagementServer = State, Alpine = UIRedux/Zustand/ContextPinia/Vuex
API FormatHTML FragmentsJSONJSON
Backendใดก็ได้ (Django, Go, PHP)Node.js (ส่วนใหญ่)Node.js (ส่วนใหญ่)
Ecosystemเล็ก (เรียบง่าย)ใหญ่มากใหญ่
เหมาะกับContent sites, Admin, CRUDComplex SPA, Mobile (RN)Medium-Large SPA

Backend Integration

# HTMX ทำงานกับ Backend ใดก็ได้
# เพราะ Backend แค่ส่ง HTML Fragment กลับมา

# Django (Python):
# views.py
def search(request):
    q = request.GET.get('q', '')
    results = Product.objects.filter(name__icontains=q)[:20]
    return render(request, 'partials/search_results.html',
                  {'results': results})

# Go (Gin):
# handler.go
func SearchHandler(c *gin.Context) {
    q := c.Query("q")
    results := db.SearchProducts(q)
    c.HTML(200, "partials/search_results.html",
           gin.H{"results": results})
}

# FastAPI (Python):
@app.get("/api/search")
async def search(request: Request, q: str = ""):
    results = await db.search_products(q)
    return templates.TemplateResponse(
        "partials/search_results.html",
        {"request": request, "results": results}
    )

# PHP (Laravel):
Route::get('/api/search', function (Request $request) {
    $results = Product::where('name', 'like', "%$request->q%")->get();
    return view('partials.search_results', compact('results'));
});

When This Stack is Perfect

เหมาะกับไม่เหมาะกับ
Content Websites / BlogsReal-time Collaboration (Google Docs)
Admin Panels / DashboardsComplex Offline-first Apps
E-commerce (สินค้าปกติ)Heavy Client-side Processing
CRUD ApplicationsComplex Drag-and-Drop UI
Landing PagesMobile Apps (ใช้ React Native)
Internal ToolsGames / Canvas-heavy Apps
Prototype / MVPDesktop Apps (Electron)
Solo/Small Team ProjectsLarge Team (ต้องการ Type Safety)

Limitations

# ข้อจำกัดของ HTMX + Alpine.js:
#
# 1. Offline Support:
#    HTMX ต้องเชื่อมต่อ Server เสมอ (ไม่เหมือน PWA)
#    → ถ้าต้องการ Offline → ใช้ Service Worker เพิ่ม
#
# 2. Complex Client State:
#    Alpine.js จัดการ State ง่าย ๆ ได้ดี
#    แต่ State ซับซ้อน (Nested, Shared) → อาจจะลำบาก
#    → ถ้า State ซับซ้อนมาก → อาจต้อง Alpine Store หรือ Vue/React
#
# 3. Type Safety:
#    ไม่มี TypeScript Support เต็มรูปแบบ
#    → ถ้าทีมใหญ่ต้องการ Type Safety → React + TS อาจดีกว่า
#
# 4. Testing:
#    การ Test HTMX Components ยากกว่า React/Vue
#    → ต้อง Integration Test แทน Unit Test
#
# 5. Ecosystem:
#    Library/Component Library น้อยกว่า React/Vue มาก
#    → ต้องสร้างเองบ่อยกว่า
สำหรับมือใหม่: ถ้าคุณกำลังเลือก Stack สำหรับโปรเจกต์ใหม่ ลองเริ่มด้วย HTMX + Alpine.js + Tailwind ก่อน ถ้าพบว่าซับซ้อนเกินไป ค่อยย้ายไป React/Vue ภายหลัง การเริ่มจากเรียบง่ายแล้ว Scale Up ดีกว่าเริ่มจากซับซ้อนแล้ว Scale Down

สรุป

HTMX + Alpine.js คือ Modern Web Stack ที่เหมาะกับ Developer ที่ต้องการ ความเรียบง่าย, Performance ดี, และ SEO ดี โดยไม่ต้องเขียน JavaScript มาก เหมาะสำหรับ Content Sites, Admin Panels, CRUD Apps, และโปรเจกต์ของทีมเล็ก-Solo Developer ลองใช้แล้วจะรู้ว่า "เขียน JavaScript น้อยลง" ไม่ได้หมายความว่า "ทำอะไรได้น้อยลง"


Back to Blog | iCafe Forex | SiamLanCard | Siam2R