Your HTMX app worked fine in development. Now it's production and nothing's happening when users click buttons. Welcome to the club.
Network Tab Is Your Best Friend
Forget fancy debuggers. 90% of HTMX issues show up in the Network tab. Filter by XHR and watch what's actually being sent.
Common network tab failures:
- Empty responses: Your server is returning 200 but blank HTML
- Wrong Content-Type: Server sending
application/json
instead oftext/html
- CORS errors:
selfRequestsOnly=true
blocking legitimate requests in HTMX 2.0 - 404s on relative URLs: Base URLs fucked, HTMX can't find your endpoints
I spent forever debugging this contact form, probably 5 or 6 hours, maybe more. Worked fine on localhost, deployed to staging and just... nothing. Buttons looked normal but clicking did nothing. Took way too long to check the Network tab - turns out the POST was going to /contact
locally but /staging/contact
in production. Nginx was mangling the URLs and I had no idea.
The Silent Failure Problem
HTMX fails silently. No JavaScript console errors, no obvious visual feedback. Your button just stops working and users think your site is broken.
Enable HTMX logging in production (temporarily):
<script>
htmx.config.debug = true; // Shows request/response in console
htmx.logAll(); // Verbose event logging
</script>
Turn this shit off after debugging or you'll flood production logs.
Server Response Debugging
HTMX expects HTML fragments, not JSON. If your API is returning:
{\"error\": \"User not found\"}
HTMX will try to insert that JSON into your DOM. It won't work and you'll get no error message.
Fix: Return HTML error responses:
<div class=\"error\">User not found. Try again.</div>
The CSRF Token Nightmare
Django/Rails developers know this pain. HTMX 2.0 made CSRF harder because selfRequestsOnly
defaults to true.
Error in console: CSRF token missing or invalid
Network tab shows: 403 Forbidden
Quick fix for Django:
<meta name=\"csrf-token\" content=\"{{ csrf_token }}\">
<script>
htmx.config.responseHandling[403] = function(xhr, responseInfo) {
console.log('CSRF failed:', xhr);
location.reload(); // Nuclear option
};
</script>
Better fix: Use the django-htmx package which handles CSRF tokens automatically.
Form Encoding Hell
HTML forms encode data as application/x-www-form-urlencoded
. Your API expects JSON. HTMX sends form data, your backend chokes.
The problem:
<form hx-post=\"/api/user\">
<input name=\"email\" value=\"test@test.com\">
</form>
Backend expects:
{\"email\": \"test@test.com\"}
HTMX actually sends:
email=test%40test.com
Solutions:
- Configure your backend to handle form data (recommended)
- Use
hx-headers
to send JSON (pain in the ass) - Use the JSON extension for HTMX