Effortless Page Routing Using HTMX

Effortless Page Routing Using HTMX

React can often be excessive for your web application, and there are instances when utilizing only a web server alongside HTMX can produce equivalent results for creating an interactive application.

In this blog post, we will illustrate how HTMX can be used to build interactive, flicker-free page navigation:

Server Setup

1mkdir no-react-app 2cd no-react-app 3npm init -y 4npm install express nunjucks 5

then we create a server file and run

1//File: app.js 2const express = require("express") 3const app = express() 4 5 6const nunjucks = require('nunjucks'); 7nunjucks.configure("views", { 8 autoescape: true, 9 express: app 10}); 11 12 13app.get("/", (req, res) => { 14 res.render("pages/home.html") 15}) 16 17app.get("/users", (req, res) => { 18 res.render("pages/users.html") 19}) 20 21app.get("/posts", (req, res) => { 22 res.render("pages/posts.html") 23}) 24 25app.listen(3000, () => { 26 console.info(`Application running http://localhost:3000`) 27})

We are using nunjucks as a templating engine. All templates, layouts, and partials are to be stored in the "views" directory. Our project structure will therefore be as follows

app.js
views
  layouts
    main.html
  partials
    sidenav.html
  pages
    user.html
    home.html
    posts.html

Because we're using a template engine, let's add a layout in which all views will extend

1<!--File: views/layouts/main.html--> 2<!DOCTYPE html> 3<html lang="en"> 4 5<head> 6 <meta charset="UTF-8"> 7 <meta name="viewport" content="width=device-width, initial-scale=1.0"> 8 <link rel="stylesheet" 9 href="https://cdnjs.cloudflare.com/ajax/libs/tailwindcss/2.0.2/tailwind.min.css" /> 10 <title>HTMX App</title> 11</head> 12 13<body class="bg-gray-200"> 14 <div class="flex h-screen"> 15 <!-- Side Navigation --> 16 {%- include('partials/sidenav.html')%} 17 18 <!-- Main Content Area --> 19 <div class="w-full bg-white p-4" id="main"> 20 {% block content %}{% endblock %} 21 </div> 22 23</body> 24 25</html>

We have refactored a sidenav template component into partials and included this in our layout

1<!--File: views/partials/sidenav.html--> 2<div class="w-56 bg-gray-800 text-white p-4"> 3 <a href="/" class="block py-2 px-4 text-white hover:bg-gray-600">Home</a> 4 <a href="/users" class="block py-2 px-4 text-white hover:bg-gray-600">Users</a> 5 <a href="/posts" class="block py-2 px-4 text-white hover:bg-gray-600">Posts</a> 6</div>

and created our main pages, home.html, users.html and posts.html

1<!--views/pages/home.html--> 2{% extends 'layouts/main.html' %} 3 4{% block content %} 5<h1 class="text-2xl font-bold mb-4">HTMX Nav</h1> 6{% endblock %}
1<!--views/pages/users.html--> 2{% extends 'layouts/main.html' %} 3 4{% block content %} 5<h1 class="text-2xl font-bold mb-4">Users</h1> 6{% endblock %}
1<!--views/pages/posts.html--> 2{% extends 'layouts/main.html' %} 3 4{% block content %} 5<h1 class="text-2xl font-bold mb-4">Posts</h1> 6{% endblock %}

When we run the server we have navigation, but with full page reloads:

We need to fix this. Let's add the small HTMX library and show how we can use it to help create a smoother more interactive navigation user experience

The fastest way to get going with htmx is to load it via a CDN. You can simply add this to your head tag and get going:

1<!--File: views/layouts/main.html--> 2... 3 <script src="https://unpkg.com/htmx.org@latest"></script> 4 <title>HTMX App</title> 5</head> 6...

We now can make a small change to the sidenav

  1. Remove href attributes and replace with hx-get attributes. When a user clicks on this link, an HTTP GET request is issued.
  2. Add hx-target attribute either to each anchor or one to the anchor parent div. The hx-target attribute allows you to target an element for swapping the response of the hx-get.
  3. Add hx-push-url="true" to each anchor. The hx-push-url attribute allows you to push a URL into the browser location history. This creates a new history entry, allowing navigation with the browser’s back and forward buttons.

What does this do: We are declaratively instructing the HTMX lib to make a server call when the anchor is clicked and insert the response into the div of id 'main'

1<div class="w-56 bg-gray-800 text-white p-4" hx-target="#main" > 2 <a hx-get="/" hx-push-url="true" class="block py-2 px-4 text-white hover:bg-gray-600">Home</a> 3 <a hx-get="/users" hx-push-url="true" class="block py-2 px-4 text-white hover:bg-gray-600">Users</a> 4 <a hx-get="/posts" hx-push-url="true" class="block py-2 px-4 text-white hover:bg-gray-600">Posts</a> 5</div>

We now have the following.

We solved the flicker issue and the url correctly pushes to the new url if we might want to share the navigation

We need to determine whether each incoming server request is an HTMX call or not. If it is, we must instruct the template engine to skip using the layout and simply return the HTML of that template. To achieve this, we'll need to incorporate specific middleware:

1//File: app.js 2... 3app.use((req, res, next) => { 4 res.locals.useLayout = req.headers["hx-request"] !== "true"; 5 next(); 6}) 7 8... 9 10app.listen(3000, () => { 11 console.info(`Application running http://localhost:3000`) 12})

and we need to add a conditional in the main.html layout

1<!--File: views/layouts/main.html--> 2{% if useLayout %} 3<!DOCTYPE html> 4<html lang="en"> 5 6<head> 7 <meta charset="UTF-8"> 8 <meta name="viewport" content="width=device-width, initial-scale=1.0"> 9 <link rel="stylesheet" 10 href="https://cdnjs.cloudflare.com/ajax/libs/tailwindcss/2.0.2/tailwind.min.css" /> 11 <script src="https://unpkg.com/htmx.org@latest"></script> 12 <title>HTMX App</title> 13</head> 14 15<body class="bg-gray-200"> 16 <div class="flex h-screen"> 17 <!-- Side Navigation --> 18 {%- include('partials/sidenav.html')%} 19 <!-- Main Content Area --> 20 <div class="w-full bg-white p-4" id="main"> 21{% endif %} 22 23 {% block content %}{% endblock %} 24 25{% if useLayout %} 26 </div> 27</body> 28 29</html> 30{% endif %}

We've successfully accomplished a seamless and flicker-free navigation experience that allows for sharing URLs.

Source Code

Code: https://github.com/nanosoftonline/express-htmx