
Fri Aug 14 2026

Ben de bir zamanlar HTMX mi, React mi? diye gece yarısı GitHub issue'ları okuyup, Reddit thread'lerinde kaybolmuşum. İkisi de "modern web" diyor ama felsefeleri tam ters köşelerde duruyor.
Kısaca özetlemek gerekirse:
Bu fark sadece "hangi kütüphane" değil; takım yapınızı, deploy stratejinizi, hatta debugging alışkanlıklarınızı değiştiriyor.
Hazırsan hadi derinlemesine inceleyelim — karar senin, rehber benim.
HTMX, HTML’i bir hipermedya motoru gibi kullanmamızı sağlayan hafif bir JavaScript kütüphanesidir. Temel fikir şu: tarayıcıda karmaşık state yönetimi veya sanal DOM yerine, sunucudan hazır HTML parçaları alıp sayfaya doğrudan enjekte edersiniz. Böylece “JavaScript yazmadan dinamiklik” elde edersiniz .
hx-get ile /comments?page=2 isteği atar.<ul>…</ul>) döndürür.hx-target ve hx-swap sayesinde bu HTML, sayfadaki #comments-list elementinin içine eklenir.<button
hx-get="/comments?page=2"
hx-target="#comments-list"
hx-swap="beforeend">
Daha fazla yorum yükle
</button>
<ul id="comments-list">
<!-- Mevcut yorumlar burada -->
</ul>
Ne oluyor burada?
hx-get → hangi URL’den HTML alınacak.hx-target → yanıtın hangi elemente yerleştirileceği (#comments-list).hx-swap="beforeend" → yanıt, hedef elementin sonuna eklenir (içeriği koruyarak).Bu kadar basit! Sunucu tarafında sadece parçalı HTML (partial) üretiyorsunuz, client tarafında ise HTMX o parçayı yerleştiriyor. Böylece state yönetimi sunucuda kalır, tarayıcı sadece hypermedia kontrollerini takip eder .
React, bileşen tabanlı bir kütüphane olarak UI’yi küçük, bağımsız parçalara böler. Her bileşen kendi state’ini yönetir, böylece veri akışı tek yönlü ve öngörülebilir olur.
Kısaca: İstemci tarafında karmaşık durum, yönlendirme ve interaktif bileşenler gerektiğinde React, bileşen izolasyonu ve sanal DOM sayesinde performanslı ve sürdürülebilir bir yapı sunar.
Aşağıda useState hook’u ve onSubmit handler’ıyla küçük bir form görebilirsiniz. Kodun akışı şöyle:
name state’i input’taki değeri tutar.handleSubmit fonksiyonu form gönderildiğinde çalışır, varsayılan davranışı engeller ve mesaj gösterir.value ve onChange ile controlled component pattern’i uygulanır.import React, { useState } from 'react';
function SimpleForm() {
const [name, setName] = useState('');
const handleSubmit = (e) => {
e.preventDefault(); // Sayfa yenilenmesini engelle
alert(`Merhaba, ${name}!`); // Basit bir geri bildirim
};
return (
<form onSubmit={handleSubmit}>
<label>
İsim:
<input
type="text"
value={name}
onChange={e => setName(e.target.value)}
/>
</label>
<button type="submit">Gönder</button>
</form>
);
}
export default SimpleForm;
Ne oluyor burada?
useState sayesinde bileşen stateful hale gelir, veri bileşen içinde kalır.onSubmit handler’ı formun doğal gönderimini engelleyip, istemci tarafında işlem yapmamızı sağlar.Bu basit yapı, daha büyük formlarda validation, async submit veya global state entegrasyonu için de temel oluşturur .
Hepimiz bir zamanlar “Bu kütüphane sadece basit siteler için” ya da “React olmadan modern bir uygulama yapamazsın” dedik.
Gerçek hayatta bu mitler projeyi yanlış yola sokabiliyor. Hadi en yaygın yanlışları tek tek irdeleyelim
script etiketiyle veya Alpine.js ile hafif bir dokunuş yeterli.key eksikliği) performansı düşürür.React.memo, useMemo, useCallback, virtualized list (react‑window) gibi araçlarla kontrollü render yaparsınız.| Senaryo | Yaygın Yanlış | Neden Tuzağı? | Doğru Yaklaşım |
|---|---|---|---|
| Basit e‑ticaret sepeti | “React şart” | Bundle büyür, sunucu‑render avantajı kaybolur | HTMX + sunucu partial’ı |
| Canlı analitik dashboard | “HTMX yeterli” | WebSocket & client‑state yönetimi zordur | React + WebSocket + state lib |
| Karmaşık form doğrulama | “HTMX’de JS yok” | Sadece sunucu validasyonu UX’i kötüleştirir | HTMX + ufak Alpine.js/JS |
| Yüksek etkileşimli oyun arayüzü | “React yavaş” | Virtual DOM optimizasyonu unutulur | React + memoization + canvas |
Özet: Yanlış anlamalar projenizi şişirir, bakımı zorlaştırır. Gerçek ihtiyaçlarınızı netleştirin, araçları o ihtiyaçlara göre seçin.
Hadi pratik bir örnek üzerinden anlayalım. Bu matris, projeni HTMX veya React ile başlamak arasında bir karar verebilmen için altı temel kriteri özetliyor. Sadece doğru hücreye parmak at ve gör ne happen! 🎯
İpucu: Tabloyu doldururken, her kriteri kendi projenin gerçek ihtiyaçlarına göre değerlendir. Bu sayede kafan karışmasın ve doğru teknolojiyle başlayabilesin.
| Kriter | HTMX uygun mu? | React uygun mu? |
|----------------------------|----------------|-----------------|
| Proje Büyüklüğü | Evet / Orta / Hayır | Evet / Orta / Hayır |
| SEO Gereksinimi | Evet / Orta / Hayır | Evet / Orta / Hayır |
| Gerçek Zamanlı İhtiyaç | Evet / Orta / Hayır | Evet / Orta / Hayır |
| Takımın Backend/Frontend Dağılımı | Evet / Orta / Hayır | Evet / Orta / Hayır |
| Dağıtım Stratejisi (monorepo vs ayrı repo) | Evet / Orta / Hayır | Evet / Orta / Hayır |
Nasıl kullanacağın?
Şimdi senin sırası: tabloyu doldur, ardından hangi teknolojiye yöneleceğine hemen karar ver! ✔️
Hazırsan başlayalım! 🎯 Her proje için tek bir teknoloji pilavı olmadığını biliyorum. İnsan faktörü, kimi konuştuğumuza, kime eğittiğimize ve hatta ekibimizin odasının düzenine bağlı olarak değişir. Hadi pratik bir örnek üzerinden anlayalım.
HTMX'i React yerine tercih etmemizin nedeni 🐱
<button hx-get="/data" hx-target="#list">Verileri Göster</button>
<div id="list">…</div>
Ne oluyor burada? Butona tıkladığımızda Sunucu ile API isteği yapılır, JSON yanıtı alırız ve aynı DOM içine bir HTML parçası yerleştirilir. JavaScript framework'ü yüklemek ya da bağlamak gerekmez.
Test stratejisi 🎯
| ✅ | Full-stack (HTMX) | Ayrı Frontend (React) |
|---|---|---|
| Öğrenme eğrisi | Düşük ❗️ | Orta |
| Onboarding | Hızlı ✅ | Orta |
| Ekip boyutu | Küçük 🏡 | Büyük 🎯 |
| Özelleştirilebilirlik | Yüksek 🛠️ | Yüksek |
| Performans | İyi ✨ | İyi |
Sonuç olarak: Benim takımım X yapısında (full-stack, HTMX odaklı) olduğundan, daha fazla kod yazarken daha az geliştiriciye ihtiyaç duyabileceğimizi, ayrıca daha az konuşup daha fazla prototip çıkarabildiğimizi görüyorum. Senin takımın farklı bir yapıdaysa, senin "en sevdiğin" teknoloji de o olabilir. ✅
Nasıl çalışıyor? Teknik seçimi, hızlıca yeni teknolojileri deneyerek ve nihayetinde "ne kadar az infrastruktur, o kadar çok ürün" prensibine bağlı kalarak takımın ihtiyaçlarına göre belirleyin. En önemlisi, o seçimdeki insana güvenin. 🚀
HTMX ile sayfa yenilemeden form gönderip, sunucudan dönen kısmi HTML’i sayfaya basmak son derece basit. Aşağıda hem istemci tarafı (HTML) hem de sunucu tarafı (Express.js) kodunu tek bir blokta topladım. Her satırın ne yaptığını yorumlarla açıkladım — kopyala, npm i express çalıştır ve node server.js diyerek hemen test edebilirsin
<!-- ==== ISTEMI TARAFI (index.html) ==== -->
<!DOCTYPE html>
<html lang="tr">
<head>
<meta charset="UTF-8">
<title>HTMX Form Örneği</title>
<!-- HTMX kütüphanesini CDN'den çekiyoruz -->
<script src="https://unpkg.com/htmx.org@1.9.10"></script>
</head>
<body>
<h1> Basit Mesaj Formu</h1>
<!--
hx-post : Form verisini /api/mesaj endpoint'ine POST eder
hx-target : Sunucudan dönen HTML'in yerleştirileceği element (id="sonuc")
hx-swap : innerHTML -> mevcut içeriği değiştirir (outerHTML da olabilirdi)
-->
<form hx-post="/api/mesaj" hx-target="#sonuc" hx-swap="innerHTML">
<label for="mesaj">Mesajınız:</label><br>
<input type="text" id="mesaj" name="mesaj" placeholder="Bir şeyler yazın..." required>
<button type="submit">Gönder</button>
</form>
<!-- Sunucudan gelen cevap bu div'in içine basılacak -->
<div id="sonuc" style="margin-top:1rem; color:#2c3e50;"></div>
</body>
</html>
// ==== SUNUCU TARAFI (server.js) ====
const express = require('express');
const app = express();
// Gelen form verisini (application/x-www-form-urlencoded) parse etmek için
app.use(express.urlencoded({ extended: true }));
// Statik dosyaları (index.html) sunmak için
app.use(express.static('.'));
// POST /api/mesaj endpoint'i
app.post('/api/mesaj', (req, res) => {
// 1 Formdan gelen "mesaj" alanını al
const kullaniciMesaji = req.body.mesaj;
// 2 Basit bir doğrulama (boş mu?)
if (!kullaniciMesaji?.trim()) {
// Hata durumunda kullanıcıya gösterilecek kısmi HTML
return res.send('<span style="color:red;"> Mesaj boş olamaz!</span>');
}
// 3 Normal akış: geri dönen kısmi HTML (HTMX hedefine basılacak)
// Burada sadece bir <p> döndürüyoruz, ama isterseniz bir liste, kart vs. de dönebilirsiniz.
const html = `
<p> <strong>Sunucu cevabı:</strong> "${kullaniciMesaji}" alındı.</p>
<small style="color:gray;">(${new Date().toLocaleTimeString('tr-TR')})</small>
`;
res.send(html); // HTMX bu HTML'i #sonuc div'inin içine yerleştirir
});
// Sunucuyu 3000 portunda başlat
app.listen(3000, () => {
console.log(' Sunucu http://localhost:3000 adresinde çalışıyor');
});
index.html açılır./api/mesaj adresine POST eder (sayfa yenilenmez).req.body.mesaj ile veriyi okur.hx-target="#sonuc" ve hx-swap="innerHTML" sayesinde bu parçacığı #sonuc div'inin içine yerleştirir.İpucu:
hx-swapdeğeriniouterHTML,beforeend,afterbegingibi değiştirerek aynı yanıtı farklı yerleşim stratejileriyle kullanabilirsin. Deneme yanılma yapmak içinhx-swap="beforeend"yapıp birden fazla mesajı listeye eklemeyi deneyin
Hadi aynı formu React'te yazalım. HTMX tarafında tek bir HTML dosyası ve birkaç attribute vardı. React tarafında ise... biraz daha "seremoni" var
useState ile tutmakonChange handler'ları yazmak (her input için ayrı ayrı )handleSubmit içinde fetch/axios çağrısıİşte kod:
import { useState } from 'react';
function ContactForm() {
const [formData, setFormData] = useState({
name: '',
email: '',
message: ''
});
const [isLoading, setIsLoading] = useState(false);
const [error, setError] = useState(null);
const [success, setSuccess] = useState(false);
const handleChange = (e) => {
const { name, value } = e.target;
setFormData(prev => ({ ...prev, [name]: value }));
};
const handleSubmit = async (e) => {
e.preventDefault();
setIsLoading(true);
setError(null);
setSuccess(false);
try {
const response = await fetch('/api/contact', {
method: 'POST',
headers: { 'Content-Type': 'application/json' },
body: JSON.stringify(formData)
});
if (!response.ok) throw new Error('Sunucu hatası');
setSuccess(true);
setFormData({ name: '', email: '', message: '' });
} catch (err) {
setError(err.message);
} finally {
setIsLoading(false);
}
};
if (success) {
return (
<div className="success-message">
Mesajınız gönderildi! Teşekkürler.
<button onClick={() => setSuccess(false)}>Yeni Mesaj</button>
</div>
);
}
return (
<form onSubmit={handleSubmit} noValidate>
<div>
<label htmlFor="name">İsim</label>
<input
id="name"
name="name"
value={formData.name}
onChange={handleChange}
required
disabled={isLoading}
/>
</div>
<div>
<label htmlFor="email">E-posta</label>
<input
id="email"
name="email"
type="email"
value={formData.email}
onChange={handleChange}
required
disabled={isLoading}
/>
</div>
<div>
<label htmlFor="message">Mesaj</label>
<textarea
id="message"
name="message"
value={formData.message}
onChange={handleChange}
required
disabled={isLoading}
/>
</div>
{error && <div className="error"> {error}</div>}
<button type="submit" disabled={isLoading}>
{isLoading ? 'Gönderiliyor...' : 'Gönder'}
</button>
</form>
);
}
export default ContactForm;
useState — form verisi, loading, error, success... hepsi ayrı statehandleChange — her input için name attribute'una dayalı generic bir handler (ama yine de yazdık )handleSubmit — fetch çağrısı, try/catch/finally, state güncellemeleri... boilerplate festivalidisabled={isLoading} — her input ve buton için tek tek tekrar| Özellik | HTMX | React |
|---|---|---|
| Satır sayısı | ~15 | ~65 |
| State yönetimi | Sunucu tarafında (HTML) | 5 ayrı useState |
| Event handler | hx-post, hx-target attribute'leri |
onChange, onSubmit fonksiyonları |
| Yükleme/hata/başarı | hx-indicator, hx-swap-oob |
Manuel state + kondisyonel render |
| Kavramsal yük | HTML bilmek yeterli | Component lifecycle, hooks, controlled components, event pooling... |
İşte bu nedenle küçük formlar için HTMX daha az kod
React harika bir araç — ama her şeye çekiç getirmek bazen çekiçin kendisinden daha ağır gelir. Basit bir iletişim formu için bu kadar boilerplate yazmak... "kill a fly with a sledgehammer" demektir
Not: Tabii ki formunuz karmaşık client-side validasyon, özelleştirilmiş input bileşenleri, optimistic UI veya çok adımlı wizard gerektiriyorsa React'in sunduğu kontrol şart. Ama "basit bir POST atıp mesaj göster" ise? HTMX tam görevi görür.
Hadi durun, hepsini bir anda değiştirmeyin . Mevcut bir React projesine HTMX sokmak (veya tersi) "big bang" rewrite demek değil. Aslında en sağlıklı yol kademeli benimseme . Ben bunu "yağmur damlası" stratejisi diyorum: önce bir köşeyi ıslatırsınız, sonra yayarsınız.
hx-boost ile test edinMevcut sayfanızda zaten çalışan bir <a> veya <form> var mı? Onun üzerine hx-boost ekleyin. Bu, sayfa yenilemesi olmadan navigasyonu HTMX'e devrediyor.
// Mevcut React bileşeninizde minimal değişiklik
export function EskiListe() {
return (
<div>
<a href="/api/urunler" hx-boost="true" hx-target="#liste">
Ürünleri HTMX ile Getir
</a>
<div id="liste">{/* Sunucudan gelecek HTML buraya düşer */}</div>
</div>
);
}
Ne oluyor burada?
hx-boost="true" → normal link tıklaması HTMX isteğine dönüşür.hx-target="#liste" → gelen HTML, id="liste" olan div'in içine yerleşir.Astro'nun yaptığı gibi, sayfayı izole edilmiş interaktif adalara bölün. React bileşenleriniz "adalar" olur, geri kalanı HTMX (veya sunucu HTML'i) yönetir.
| Adak İsmi | Sorumluluk | Teknoloji |
|---|---|---|
SepetAdasi |
Sepet toplamı, miktar artı/azalt | React (stateful) |
UrunListesi |
Filtreleme, sayfalama, arama | HTMX + Sunucu HTML |
BildirimAdasi |
Gerçek zamanlı bildirimler | React (WebSocket) |
Pratik kural: Sadece gerçekten interaktif olan kısımlar React kalsın. Listeleme, detay, basit formlar → HTMX.
İki dünya bir arada olduğunda en büyük baş belası paylaşılan state. Çözüm? Event-driven iletişim.
// React tarafı: özel event fırlat
function SepetButonu({ urunId }) {
const ekle = () => {
window.dispatchEvent(new CustomEvent('sepet:ekle', { detail: { urunId } }));
};
return <button onClick={ekle}>Sepete Ekle</button>;
}
// HTMX tarafı: event'i dinle ve isteği tetikle
<div hx-post="/api/sepet"
hx-trigger="sepet:ekle from:window"
hx-vals='js:{urunId: event.detail.urunId}'>
</div>
Avantaj: React state'i HTMX'e, HTMX yanıtı React'e prop drilling veya context karışıklığı olmadan akar.
dangerouslySetInnerHTML TuzağıReact içinde HTMX snippet'ları render etmek isterseniz bu prop gelir aklınıza. Dikkat!
// Kötü örnek: XSS riski + HTMX bağlaması kopar
<div dangerouslySetInnerHTML={{ __html: sunucudanGelenHtml }} />
Neden riskli?
hx-on:* veya hx-trigger gibi öznitelikler React'in sanal DOM'una bağlanmaz.hx-swap sonrası yeniden bağlanmayı dener ama React DOM'u yenilediğinde event listener'lar kaybolur.Güvenli alternatif: HTMX fragment'ını ayrı bir endpoint'ten çekip hx-include / hx-target ile doğrudan DOM'a yerleştirin. React sadece konteynır olsun.
| Aşama | Ne Yapılır? | Risk |
|---|---|---|
| 0 | Mevcut projeyi dokunmaz, yeni sayfalar HTMX ile yazılır | Düşük |
| 1 | hx-boost ile mevcut link/form'ları yükseltirsiniz |
Orta (CSS/JS çakışması) |
| 2 | Island'ları tanımlayıp React bileşenlerini izole edersiniz | Orta |
| 3 | Paylaşılan state için event bus / localStorage / URL sync kurarsınız | Yüksek (test edin!) |
| 4 | Eski React sayfalarını parçalar halinde HTMX'e taşırsınız | Düşük (artık alışkınız) |
hx-boost harika bir ilk adım).CustomEvent veya URL/state sync kullanın; prop drilling'den kaçının.dangerouslySetInnerHTML son çare olsun; HTMX'i DOM'un sahibi yapın, React sadece "ada" olsun.Hazırsanız bir sonraki bölümde test stratejileri ve CI/CD entegrasyonu üzerine konuşalım.
Hepimiz "doğru" aracı bulmak isteriz ama gerçek şu: evrensel bir doğru yok. Projenin doğası, takımınızın deneyimi, zaman kısıtlamaları ve hatta şirket kültürü... Hepsi kararınızı etkiler. Dogmaya yer yok, sadece takaslar var.
HTMX size şunları sunar:
React ise şunları getirir:
Kod yazmadan karar vermeyin. Deneyim, teoriden her zaman daha çok öğretir.
Siz hangi tarafdasınız? HTMX'in sadelik mi, React'in gücü mü kazandırdı sizi? Ya da belki ikisini de bir arada kullandınız? Yorumlarda deneyimlerinizi paylaşın, birbirimizden öğrenelim.
Bir sonraki yazıda görüşmek üzere — mutlu kodlamalar!
Bu içerik tamamen yapay zeka destekli otomasyon sistemi ile üretilmiştir.
All rights reserved