#
Checkpoint: Troubleshooting + Pattern Review
#
Overview
This checkpoint is a structured pause between Labs 2 and 3. Before moving to more advanced patterns like real-time validation, you need to be confident that:
- Your development environment works reliably
- You can debug htmx interactions using browser DevTools
- You understand the common mistakes and how to fix them
- You have consistent conventions for handling htmx vs. non-htmx requests
Time estimate: 10–15 minutes
#
Checkpoint Outcomes
By the end of this checkpoint, you will be able to:
#
Part 1: Verify Your Development Loop (5–7 minutes)
Before debugging htmx-specific issues, confirm your basic development workflow is solid.
#
1.1 Run + Debug Checklist
Verify each of these items:
- Application runs locally (HTTPS or HTTP—either is fine)
- Breakpoints hit inside handlers:
OnPostCreateOnGetList
- Hot reload or rebuild works reliably
- Browser refreshes correctly after code changes
Debug Exercise:
Set a breakpoint on the first line of OnPostCreate. Submit the form and confirm:
- The breakpoint is hit
- You can inspect
Request.Headersin the debugger - You see
HX-Request: truein the headers (for htmx requests)
#
1.2 Network Requests: What "Good" Looks Like
Open browser DevTools → Network tab and perform these actions:
#
For hx-post Create (Form Submission)
Example Request Headers:
POST /Tasks?handler=Create HTTP/1.1
Host: localhost:5001
HX-Request: true
HX-Current-URL: https://localhost:5001/Tasks
Content-Type: application/x-www-form-urlencoded
#
For hx-get Refresh (List Refresh)
#
For Validation Error Responses
#
1.3 Inspect Returned Fragments in Elements Panel
Use the Elements tab in DevTools to verify swaps are working:
- Before submitting: Note the DOM node for
div#task-list - After submitting: The node should be replaced (different DOM reference)
- Confirm: The content inside
#task-listreflects the new data
Key Insight:
htmx swaps HTML, not JSON. Debugging htmx is about inspecting HTML fragments and target elements—not parsing API payloads.
#
Part 2: Common Gotchas and Fixes
This section covers the five most common htmx + Razor Pages integration mistakes. For each, you'll learn the symptoms, likely causes, and fastest diagnostic steps.
#
Gotcha A: Nothing Happens on Submit (or Button Click)
Symptoms:
- Clicking the button or submitting the form does nothing
- No network request appears in DevTools
- Page doesn't change at all
Likely Causes:
Fast Diagnostic Steps:
Check Console for htmx:
Open DevTools Console and type:
htmx- If you see an object → htmx is loaded
- If you see
undefined→ htmx is NOT loaded
View Page Source:
Press
Ctrl+Uand search forhtmx. Confirm the script tag exists:<script src="https://unpkg.com/htmx.org@1.9.12"></script>Verify File Location:
Confirm you edited
Pages/Tasks/Partials/_TaskForm.cshtml, notPages/Tasks/Index.cshtml.
Fix:
<!-- Add to _Layout.cshtml before </body> -->
<script src="https://unpkg.com/htmx.org@1.9.12"></script>
#
Gotcha B: Request Fires, But Wrong Part Updates (or Nothing Updates)
Symptoms:
- Network tab shows the request completed successfully
- Response contains HTML
- But the page doesn't update, or the wrong element updates
Likely Causes:
Fast Diagnostic Steps:
Check for Single ID:
In Elements panel, search for
#task-list. There should be exactly one element.Inspect Response Body:
In Network tab, click the request and view Response. Confirm it includes:
<div id="task-list"> <!-- list content --> </div>The response must include the wrapper element when using
hx-swap="outerHTML".Verify Target Selector:
Check that
hx-target="#task-list"matches the actual element ID (case-sensitive).
Fix:
Ensure your partial returns the complete wrapper:
@* _TaskList.cshtml - Must include the wrapper *@
<div id="task-list">
@* Content here *@
</div>
Common Mistake:
@* WRONG - Missing wrapper, will break outerHTML swap *@
@foreach (var task in Model)
{
<li>@task.Title</li>
}
#
Gotcha C: Handler Not Found / Wrong Handler Invoked
Symptoms:
- Network tab shows
404 Not Found - Or: A different handler runs than expected
- Or:
OnGetruns when you expectedOnPostCreate
Likely Causes:
Fast Diagnostic Steps:
Check Request URL:
In Network tab, verify the URL includes the correct handler:
/Tasks?handler=Create ← Should match OnPostCreate /Tasks?handler=List ← Should match OnGetListVerify Method Names:
Razor Pages naming convention:
Check HTTP Verb:
hx-post→ callsOnPost...handlershx-get→ callsOnGet...handlers
Fix:
Ensure handler names align:
<!-- Markup -->
<form hx-post="?handler=Create" ...>
<!-- Must match -->
public IActionResult OnPostCreate() { ... }
#
Gotcha D: Partial Path Errors (Runtime View Not Found)
Symptoms:
- Exception message: "The partial view '...' was not found"
- Or: Wrong partial renders
Likely Causes:
Fast Diagnostic Steps:
Verify File Exists:
If your code says:
return Fragment("Partials/_TaskList", model);Then this file must exist:
Pages/Tasks/Partials/_TaskList.cshtmlCheck Case Sensitivity:
On some systems,
_TaskList.cshtmland_Tasklist.cshtmlare different files.Use Consistent Paths:
Both of these should use the same path pattern:
// In handler return Fragment("Partials/_TaskList", Tasks);<!-- In Razor page --> <partial name="Partials/_TaskList" model="Model.Tasks" />
Fix:
Organize partials consistently:
Pages/Tasks/
├── Index.cshtml
├── Index.cshtml.cs
└── Partials/
├── _Messages.cshtml
├── _TaskForm.cshtml
└── _TaskList.cshtml
#
Gotcha E: Validation-as-You-Type Fails (403 Antiforgery or No Data Sent)
Symptoms:
- Keystroke validation returns
400 Bad Requestor403 Forbidden - Request payload is empty or missing fields
- Server-side validation doesn't receive the input value
Likely Causes:
Fast Diagnostic Steps:
Check Request Payload:
In Network tab, check if the request includes
__RequestVerificationToken:Input.Title=my+task&__RequestVerificationToken=CfDJ8...Verify Token in Form:
Ensure your form includes the antiforgery token:
<form method="post" asp-page-handler="Create"> @Html.AntiForgeryToken() <!-- or use: <input asp-antiforgery="true" /> -->Add
hx-includeif Needed:If
hx-postis on an input (not the form), add:<input asp-for="Input.Title" hx-post="?handler=ValidateTitle" hx-include="closest form" ... />
Fix:
Ensure the form has the token and inputs include it:
<div id="task-form">
<form method="post" asp-page-handler="Create"
hx-post="?handler=Create"
hx-target="#task-list"
hx-swap="outerHTML">
@Html.AntiForgeryToken()
<input asp-for="Input.Title"
hx-post="?handler=ValidateTitle"
hx-include="closest form"
hx-trigger="keyup changed delay:500ms"
hx-target="#title-validation"
hx-swap="outerHTML" />
<!-- rest of form -->
</form>
</div>
#
Part 3: Standardize Response Conventions (Mini-Refactor)
Now that you can diagnose issues, let's establish conventions that prevent them.
#
3.1 The Response Rule Set
Every handler should return exactly one of these response types:
Rule 1: A handler returns exactly one of:
// Non-htmx: Full page
return Page();
return RedirectToPage();
// htmx: Fragment only
return Fragment("Partials/_TaskList", Tasks);
Rule 2: Fragment handlers must return a wrapper with a stable ID:
Rule 3: Swap strategy must match fragment shape:
#
3.2 Standardize Helper Methods
Ensure your PageModel includes these helpers:
File: Pages/Tasks/Index.cshtml.cs
using Microsoft.AspNetCore.Mvc.ViewFeatures;
// Add inside your PageModel class:
/// <summary>
/// Checks if the current request was made by htmx.
/// htmx sends "HX-Request: true" header with every request.
/// </summary>
private bool IsHtmx() =>
Request.Headers.TryGetValue("HX-Request", out var value) && value == "true";
/// <summary>
/// Returns a partial view result for fragment responses.
/// Preserves ViewData context for validation messages, etc.
/// </summary>
/// <param name="partialName">Path to the partial view</param>
/// <param name="model">Model to pass to the partial</param>
private PartialViewResult Fragment(string partialName, object model) =>
new()
{
ViewName = partialName,
ViewData = new ViewDataDictionary(MetadataProvider, ModelState) { Model = model }
};
Why These Helpers:
IsHtmx()removes magic strings scattered throughout handlersFragment()ensures consistentViewDatapropagation (critical for validation)- Both are small enough to copy into any PageModel
#
3.3 Standardize Handler Naming
Adopt these naming conventions:
The Pattern:
- Handler name tells you which fragment it returns
OnGet*for reads,OnPost*for mutations- The
?handler=value maps directly to the method suffix
#
3.4 Standardize Retarget Usage
Policy: Use HX-Retarget + HX-Reswap only in two scenarios:
Everything else should use explicit hx-target in markup.
Example: Retargeting on Validation Error
if (!ModelState.IsValid)
{
Tasks = InMemoryTaskStore.All();
if (IsHtmx())
{
// Retarget from #task-list to #task-form
Response.Headers["HX-Retarget"] = "#task-form";
Response.Headers["HX-Reswap"] = "outerHTML";
return Fragment("Partials/_TaskForm", this);
}
return Page();
}
#
Part 4: Verification Exercise
Complete these checks before proceeding to Lab 3.
#
Quick Verification Script (10 minutes)
Step 1: Network Verification (2 minutes)
- Open DevTools → Network
- Submit a valid task
- Confirm:
- Request includes
HX-Request: true - Response is HTML fragment (not full page)
- Only
#task-listupdates
- Request includes
Step 2: Validation Error Verification (2 minutes)
- Submit an empty form
- Confirm:
- Response status is
200 - Response headers include
HX-Retarget: #task-form - Form fragment replaces
#task-formwith error message
- Response status is
Step 3: Refresh Button Verification (2 minutes)
- Click "Refresh All" button
- Confirm:
- GET request to
?handler=List - Response is
#task-listfragment - List updates without page reload
- GET request to
Step 4: Helper Method Verification (4 minutes)
- Open
Index.cshtml.cs - Verify
IsHtmx()helper exists - Verify
Fragment()helper exists - Verify all handlers follow the response rules
#
"Ready to Proceed" Checklist
You are ready for Lab 3 when you can answer "Yes" to all of these:
- I can see htmx requests in Network tab with
HX-Request: trueheader - My Create updates only
#task-listwithout a full page reload - Invalid Create swaps the form fragment and shows validation errors
- Refresh button updates only
#task-list - I can locate the handler and partial returned for any interaction
- I have
IsHtmx()andFragment()helpers in my PageModel - I understand when to use
HX-Retarget(and when not to)
#
Quick Reference: Diagnostic Commands
#
Browser Console
// Check if htmx is loaded
htmx // Should return an object
// Check htmx version
htmx.version // e.g., "1.9.12"
// Manually trigger a request (debugging)
htmx.trigger(document.querySelector('#refresh-btn'), 'click')
#
Network Tab Filters
- Filter by
XHRto see only AJAX requests - Search for
handler=to find htmx requests - Check
Response HeadersforHX-*headers
#
Elements Tab
- Search for
#task-listto find target element - Watch for DOM node replacement during swaps
- Check for duplicate IDs (should be exactly one)
#
Summary
This checkpoint covered:
- Development loop verification: Ensure debugging and hot reload work
- Network inspection: Understand what "good" htmx requests look like
- Common gotchas: Diagnose and fix the five most frequent issues
- Response conventions: Standardize how handlers respond to htmx vs. non-htmx requests
- Helper methods: Establish
IsHtmx()andFragment()as standard patterns
With these foundations solid, you're ready for Lab 3: Real-Time Validation and Form UX.
#
Troubleshooting Quick Reference
#
Appendix: Fragment Inventory Template
Add this comment to the top of your Pages/Tasks/Index.cshtml:
@*
Fragment Inventory
==================
| Target ID | Partial | Handlers That Return It |
|----------------|--------------------------|------------------------------|
| #messages | Partials/_Messages | OnGetMessages |
| #task-form | Partials/_TaskForm | OnGetEmptyForm, OnPostCreate (invalid) |
| #task-list | Partials/_TaskList | OnGetList, OnPostCreate (success) |
| #task-details | Partials/_TaskDetails | OnGetDetails |
Response Rules:
- Non-htmx → Page() or RedirectToPage()
- htmx → Fragment("...", model)
- Validation error → HX-Retarget to form
- Server error → 200 + HX-Retarget to messages
*@
This inventory prevents "where does this fragment come from?" confusion and documents your htmx contracts.
Proceed to Lab 3: Real-Time Validation and Form UX →