HTMX 革命:后端全栈的文艺复兴
话说 2021 年,有个叫 Big Sky Software 的公司发了一个库叫 [HTMX](https://htmx.org/),当时前端圈几乎没人当回事。彼时 React 生态正如日中天,"一切皆组件"的口号喊得震天响,谁会多看一眼这个看起来像是在 HTML 里塞了一堆 `hx-` 前缀属性的奇怪东西?
话说 2021 年,有个叫 Big Sky Software 的公司发了一个库叫 [HTMX](https://htmx.org/),当时前端圈几乎没人当回事。彼时 React 生态正如日中天,"一切皆组件"的口号喊得震天响,谁会多看一眼这个看起来像是在 HTML 里塞了一堆 hx- 前缀属性的奇怪东西?
结果呢?四年过去,HTMX 的 GitHub Star 从几千飙升到近五万,成了 2024-2025 年最受关注的 Web 技术之一。这背后不是营销胜利,而是一次对 Web 开发根本性错误的集体反思。
前端焦虑的根源:混淆了关注点
我们先退一步想想:为什么 React/Vue 一统江湖之后,Web 开发反而更累了?
看看一个典型 SPA 的问题:
1. 后端只提供 JSON API,变成了"数据管道"
2. 前端承担所有 UI 逻辑,复杂度爆炸
3. 每次改个小功能,要改 API、改 TypeScript 类型、改前端组件、考虑状态管理
4. SEO?SSR 水太深
5. 加载性能?bundle size 越滚越大
问题的根源在于:我们把本该属于后端的 UI 渲染责任强行外包给了浏览器。HTTP 本来就是为文档交换设计的,我们却非要用它传 JSON 再让 JS 渲染成 HTML。这不是技术的胜利,这是架构的妥协。
HTMX 的核心洞察:HTTP 原语即 UI
HTMX 的设计哲学极其简单:让服务器返回 HTML 片段,而不是 JSON,让浏览器直接替换页面局部。
`html
fetch('/api/users')
.then(r => r.json())
.then(data => {
document.getElementById('user-list').innerHTML = renderUsers(data);
});
Loading...
`
服务器返回的只是 partials/users 这个 URL 对应的 HTML 片段,HTMX 自动拿到后替换 #user-list 容器。没有 JS、没有构建工具、没有状态管理。
这意味着什么?你的后端模板引擎(Jinja2、Blade、Twig、Go templates)就是你的 UI 框架。
实战:Flask + HTMX 构建真实应用
光说不练是耍流氓。我用一个真实的 TODO 应用来展示这个模式有多高效。
项目结构
`
todo_app/
├── app.py # Flask 后端
├── templates/
│ ├── base.html # 布局模板
│ ├── index.html # 主页面
│ └── _todo_row.html # 单条 TODO 的局部模板
└── static/
└── htmx.min.js
`
后端:Flask 应用
`python
from flask import Flask, render_template, request, redirect
from flask_sqlalchemy import SQLAlchemy
from datetime import datetime
app = Flask(__name__)
app.config['SQLALCHEMY_DATABASE_URI'] = 'sqlite:///todo.db'
app.config['SQLALCHEMY_TRACK_MODIFICATIONS'] = False
db = SQLAlchemy(app)
class Todo(db.Model):
id = db.Column(db.Integer, primary_key=True)
title = db.Column(db.String(200), nullable=False)
completed = db.Column(db.Boolean, default=False)
created_at = db.Column(db.DateTime, default=datetime.utcnow)
# 主页
@app.route('/')
def index():
todos = Todo.query.order_by(Todo.created_at.desc()).all()
return render_template('index.html', todos=todos)
# 添加 TODO(返回 HTML 片段,不是 JSON)
@app.route('/todos/add', methods=['POST'])
def add_todo():
title = request.form.get('title', '').strip()
if not title:
return '', 400
todo = Todo(title=title)
db.session.add(todo)
db.session.commit()
# 返回新行的局部模板,用于 HTMX 替换
return render_template('_todo_row.html', todo=todo)
# 切换完成状态
@app.route('/todos/
def toggle_todo(id):
todo = Todo.query.get_or_404(id)
todo.completed = not todo.completed
db.session.commit()
return render_template('_todo_row.html', todo=todo)
# 删除
@app.route('/todos/
def delete_todo(id):
todo = Todo.query.get_or_404(id)
db.session.delete(todo)
db.session.commit()
return '', 200
`
关键点在这里:三个路由返回的都是 render_template() 渲染的 HTML 片段。后端天然知道怎么渲染自己的数据,不需要额外的序列化层。
前端:HTMX 模板
`html
HTMX TODO
`
`html
hx-swap="outerHTML">
{{ todo.title }}
`
注意 hx-swap="delete":HTMX 会把被替换的元素从 DOM 中删除。
完整效果
整个应用只有:
- 1 个 Python 文件(Flask 应用)
- 2 个 HTML 模板 + 1 个 base 布局
- 几行 CSS(甚至可以没有)
- **0 行 JavaScript 业务逻辑**(除了加载 htmx.min.js)
对比同样功能的 React SPA:至少 10 个文件、200+ 行 JS/TS 代码、状态管理、API 层、TypeScript 类型定义...
HTMX 的进阶能力:不只是替换
你以为 HTMX 只能做简单的 swap?它支持的能力远超你的预期:
1. WebSocket 实时更新
`html
`
2. SSE(Server-Sent Events)推送
`html
`
3. 历史记录管理
`html
hx-get="/page/2" hx-push-url="true" hx-target="#content"> 第2页
`
加一个 hx-push-url="true",HTMX 自动帮你管理浏览器历史记录,前进后退按钮正常工作。这是很多"简易方案"踩的坑。
4. 请求指示器
`html
⏳
`
.htmx-indicator 类在请求期间自动显示,再也不需要手动写 loading 状态。
架构视角:为什么这是正确的方向
HTMX 不是一个玩具,它代表了分布式系统设计的某种回归:
1. 关注点分离回归
后端负责数据、业务逻辑、渲染模板——它本来就最擅长这些。前端只需要处理网络请求和局部 DOM 更新——HTMX 把这个职责降到最低。
2. 渐进增强(Progressive Enhancement)
即使 HTMX JS 加载失败或被 CSP 阻止,带有 hx- 属性的元素会优雅降级,表单依然可以通过传统方式提交。这是前端框架很少认真对待的问题。
3. SEO 天生友好
每个页面都是完整的 HTML,后端直接渲染,搜索引擎没有任何障碍。不需要 SSR、不需要预渲染、不需要 meta 标签同步。
4. 极低的学习曲线
团队里的后端 Python/Go/Java 开发者,不需要学习组件化、虚拟 DOM、状态管理,就能参与前端开发。技术债务大幅降低。
适用场景:不适合所有人
说完优点也要诚实:HTMX 不是银弹。
适合的场景:
- 内部工具、管理系统(对性能要求不高,对开发速度要求高)
- 内容为主的网站(CMS、企业网站)
- 团队以后端为主,前端资源有限
- 快速原型开发
不太适合的场景:
- 高度交互的复杂应用(看板、设计工具)
- 需要复杂客户端状态(Figma、Notion 那种)
- 离线优先的应用(PWA)
- 对 bundle size 极度敏感(HTMX 本身 14kb gzipped,也不大)
结论:工具应该匹配问题
技术选型最大的错误是:用"最流行"而不是"最适合"。React/Vue 解决的是 SPA 时代的真实问题,但当 Web 标准(HTTP/HTML/CSS)本身已经进化到能更好解决这些问题时,我们是不是该重新评估?
HTMX 不是一个倒退,而是一次拨乱反正。它提醒我们:最好的架构是让擅长的人做擅长的事,后端渲染 HTML、前端处理交互,本该如此。
下一次当你准备新建一个"前后端分离"的项目时,问自己一个问题:如果我只需要一个 CRUD 管理后台,真的需要动用 React 全家桶吗?
也许,你只需要一个 Flask + HTMX。
---
*附:本文完整代码示例可在 [htmx.org/examples](https://htmx.org/examples/) 找到。*