WESLEY WITSKEN

HTMX + Astro Search

Wed May 29 2024

Removing React and implementing HTMX on my Search Posts page.

Written by: Wesley Witsken

A man with an invisible torso holding a wooden plank.

Refactoring my Blog Post Search Component to use HTMX

In my first post, I mentioned that I wanted to use React a little bit with my website. Initially, I created the posts page to provide that functionality. I also mentioned that React is a pretty bloated tool that is best suited for more complex rendering tasks, and because my post search page isn’t all that complicated, I decided it was time to step up my game.

Astro Tooling

Recently, Astro (the framework used to generate this website) added a new feature called “partials”, which is tailored for use with HTMX. According to their press release:

A page component can now be identified as a partial page, which will render its HTML content without including a <!DOCTYPE html> declaration nor any <head> content. Partials are best used in conjunction with a rendering library, like htmx or Stimulus that is built around fetching HTML fragments.

Normally, Astro implements a strategy called “file-based routing,” meaning that every file within the /pages directory becomes an end-point. This means that every time a client issues a GET request for a page, it returns with all the class html metadata that will render - you guessed it - a whole web page.

However, with partials, the developer now has a way to tell Astro that certain files don’t need to contain all of the extra page data. This will make more sense later on when we talk about HTMX, but for now, the takeaway is that we can create HTML fragments that can be accessed just like normal file-based routes, but with a fragment of a normal route.

HTMX Tooling

I’ve finally gotten cocky enough to jump on the React-hate train, and now I’m all-aboard with HTMX. Similar to Astro, HTMX is technically not a framework, but is a very bare-bones extension of AJAX requests to every-day HTML DOM elements. In fact, many call it “an extension of HTML”, born out of questions like:

Why can’t other elements issue HTTP requests as well? Why only click & submit events? Why only GET and POST?

It follows the HATEOAS web architecture philosophy, and even though it has “hate” in the name, there’s a lot to love about it.

The restrictions imposed by HATEOAS decouple client and server. This enables server functionality to evolve independently.

This is easiest to comprehend when we compare it to the React framework. React implements the concept of state to manage the appearance of the app, meaning that when a user makes changes on the page, the client is managing those changes directly and updating the view. If any new data is needed from the server, it must be requested, interpreted (from JSON), stored in state, and then the view can update (I.E., the page HTML is changed). This means that the client is heavily responsible for the logic of the application, and has the responsibility of knowing how to interpret server JSON requests.

In contrast, in HTMX, HTML is the application state. When a user makes a change to the website, a new request is made to the server (no state is changed on the client-side). The server responds with an HTML fragment (sound familiar?), and with a little Javascript magic, that HTML get directly inserted into the clients DOM exactly where it needs to be. No JSON, no virtual DOM - all state is managed on the server. The client doesn’t need to know how to parse requests into views, because the view is the request.

Putting the Puzzle Together

So, how do these tools integrate for a search component?

  1. We set up an HTMX input element to search with, and create a target list for results when they come through.
    • The user-input value is the parameter in the GET request.
    • The target is the <ul> (unordered list) component, which we will append to.
 <input
    placeholder="Search posts"
    type="search"
    name="query"
    hx-trigger="load, input changed delay:500ms, search"
    hx-get="/partials/search-results"
    hx-target="#results-container"
/>
  1. We set up an Astro partial page that is SSR (server-side rendering) enabled.
    • In this case, I used Fuse.js for fuzzy searching on the server side.
    • To create a partial, SSR page, we make the selection in our prerender = false and partial = true variable exports.
posts.map((post) => (
<li>
	<BigBlogCard post={post}>
		<a
		href={`/posts/${post.slug}`}
		hx-get={`/partials/blogcard-images/${post.slug}`}
		hx-target="this"
		hx-swap="innerHTML"
		hx-trigger="load once"
		/>
	</BigBlogCard>
</li>
))
  1. We set up an SSG (static site generated) dynamic route for all blog posts cover images, since they take a while to render.
    • In the previous step, the hx-get={`/partials/blogcard-images/${post.slug}`} is what fetches the correct post image once it is loaded on the client.
    • Because the image locations never change, we can statically render all HTML image elements to their own endpoints and serve them as fragments.
    • This is possible because of Astro’s dynamic routing - basically, we take our entire post collection and for each post, create an endpoint of it’s image.
  2. I also added pagination to create an infinite scrolling effect, but I’ll talk about that more in detail in a later post.

Conclusion

And there you have it! HTMX and Astro are a match made in heaven. With these tools, I was able to re-factor my search page to stop using React, which helped simplify my codebase, restrict the amount of data shipped to the client, and boost my site’s performance. Because of this refactor, the user experience is more seamless than before, but more importantly, I gained valuable new knowledge working with two frameworks (extensions?) which are immensely enjoyable.