---
date: "2023-07-04"
title: Making static Hugo sites dynamic with htmx and Go
alias: /posts/2023/2023-07-04-creating-dynamic-hugo-sites-with-htmx-and-go/
tags:
    - hugo
    - htmx
    - go
    - html/template
    - templ
---

## Update :: live demo now available

A demo of this concept is now available at https://garbagespeak.com and the demo source code is available at https://github.com/acaloiaro/garbagespeak.com/.

## Intro

I've encountered a lot of skepticism around the idea of adding dynamic behavior to [Hugo](https://gohugo.io/) sites with [hugo-htmx-go-template](https://github.com/acaloiaro/hugo-htmx-go-template). That skepticism is well founded, because Hugo bills itself as a static site generator.

So why would anyone want to add dynamic functionality to Hugo sites when Hugo aspires to be a simple static site generator?

Because Hugo is a lot more than a static site generator. "Staticness" is simply the end result of a lot of great site building features coming together as a "built" site that doesn't change. Staticness is one feature among many great Hugo features. I encourage you to visit https://gohugo.io/ if you're not familiar with Hugo's capabilities.

**What does it even mean to add dynamic behavior to Hugo sites? I thought Hugo only output HTML and site assets**

Adding dynamic behavior to static sites can be achieved in a number of ways. It can be as simple as dynamically loading new content onto the screen to give pages a more "interactive" feel, or it can be as complex as adding an entire comment system to blog posts. Using the techniques outlined in this post, there is no limit to how complex one's Hugo site can become, though I recommend exercising restraint in making static sites dynamic.

**Is it wrong to make non-static Hugo sites? Doesn't that violate Hugo's intended purpose?**

First of all, _it's not wrong_. Like most Hugo features, staticness is optional. There is no morality police dictating that Hugo sites are all static. Hugo is a great tool for building websites and if you have dynamic needs, entertain the approach outlined here. Don't reach for a SaaS service because your Hugo site needs a contact form.

## Examples

I'm a big proponent of explaining ideas through examples. Let's start with the first example above: **dynamically loading site content**.

While this example may seem contrived and overly simple, I encourage you to ponder how the technique shown can be used to create more interactive websites.

If you'd like to follow along, clone the [hugo-htmx-go-template](https://github.com/acaloiaro/hugo-htmx-go-template) starter project:

> git clone git@github.com:acaloiaro/hugo-htmx-go-template.git

### Dynamically loading content

As the name of the project suggests, I've opted to use [htmx](https://htmx.org) for interactive behavior. If you're not familiar with htmx, it is what allows us to dynamically fetch content without writing a lick of Javascript.

We start by configuring Hugo to serve all the HTML files in a directory. Its configuration file is edited as follows:


> open ./config/\_default/config.toml
```
[module]
 [[module.mounts]]
 source = 'partials'
 target = 'content'
```

These configuration lines tell Hugo to take the directory `partials` and all of its contents, and make them available as `content`. That is, make the contents available to our site users.

This allows us to fetch any HTML file from `partials` with htmx (or any HTTP requests).

So for example, we can fetch the file `partials/content.html` by adding the following HTML to any markdown file. You can find the following snippet at the bottom of `content/posts/hello-world.md`.

{{< code language="html" >}}
{{</* html.inline */>}}
<div
  hx-get="/content.html"
  hx-trigger="click"
  hx-target="#content_target" />
  <button>Load content</button>
  <div id="content_target"></div>
</div>
{{</* /html.inline */>}}
{{< /code >}}

This HTML div adorned with `htmx` attributes renders a button that when clicked, dynamically loads the contents of `content.html` into the `#content_target` div.

{{< image src="/img/hugo-htmx-go-template-load-content.gif" alt="hugo-htmx-go-template fetching content dynamically" position="center" style="border-radius: 8px;" >}}

This is all good and well. We can dynamically load content from our Hugo site when users interact with it. But what about adding real application functionality to Hugo sites?

### Introducing an API server

[hugo-htmx-go-template](https://github.com/acaloiaro/hugo-htmx-go-template) comes with a bare-bones web server from which much more complex functionality can be built. It's important to remember that all web applications are simply a combination of forms and buttons that do stuff when users interact with them. We've shown how to make a button load content dynamically, but what about forms? Can our Hugo site process form data?

Indeed it can. And that means we can build entire web applications with Hugo as the templating and build system, and something else as the "backend".

[hugo-htmx-go-template](https://github.com/acaloiaro/hugo-htmx-go-template) provides such a backend. The project's `server.go` is a Go web server that renders `html/template` or [templ](https://github.com/a-h/templ) templates, for those who prefer to write templates in pure Go. Of course, this is only a rudimentary web server. As the name suggests, `hugo-htmx-go-template` is simply a template from which to build projects. It is itself a simple Hugo project with some added tooling, and an optional web server, but we'll get to that.

**Let's process form data from our Hugo site.**

Again open `contents/posts/hello-world.md` and examine the section starting with **## Let's add a form**

Here we again see an inline Hugo shortcode that renders some HTML adorned with htmx attributes to make it dynamic.

{{< code language="html" >}}
{{</* html.inline */>}}
<!-- Make sure the api base URL is set -->
<form hx-post="{{ .Site.Params.apiBaseUrl }}/hello_world_form">
  <label>Name</label>
  <input type="text" name="name">
  <br/>
  <button>Submit</button>
</form>
{{</* /html.inline */>}}
{{< /code >}}


We see a few new things here. First of all you may notice that this is a `html/template`, which allows the form to issue a POST request to `{{ .Site.Params.apiBaseUrl }}`. This is the URL where our web server is running.

> apiBaseUrl is configured in `config/_default/config.toml` for development and `config/production/config.toml` for production. It should point to wherever you run your API server.

The second thing is this new endpoint `/hello_world_form`.

If we open up `server.go`, we'll see the line:
```go
mux.HandleFunc("/hello_world_form", helloWorldForm)
```

And `helloWorldForm` simply processes the form value and says hello to it using a `templ` template:

```go
func helloWorldForm(w http.ResponseWriter, r *http.Request) {
	name := "World"
	if err := r.ParseForm(); err != nil {
		ise(err, w)
		return
	}

	name = r.FormValue("name")
	if err := partials.HelloWorldGreeting(name).Render(r.Context(), w); err != nil {
		w.WriteHeader(http.StatusInternalServerError)
		return
	}
}
```

**Let's see it in action**

{{< image src="/img/hugo-htmx-go-template-form.gif" alt="hugo-htmx-go-template processing form data and responding to it" position="center" style="border-radius: 8px;" >}}

## Deployment

So how does this all fit together, and how can this be done in production? For details on deployment, I'll direct you to the project's [README on that topic](https://github.com/acaloiaro/hugo-htmx-go-template/#deploy), but I'd like to nudge the reader toward deploying these hybrid hugo-htmx-go-api projects as single **fat binaries**. You can continue deploying your Hugo sites as static HTML and assets as you always have and run the API server separately. However, I find fat binary deployment far more pleasant.

I mentioned some "tooling" earlier. If you ran through the [getting started](https://github.com/acaloiaro/hugo-htmx-go-template/#getting-started) documentation you'll have two tools available to you

- `bin/develop`: This runs both `hugo serve` and `go run server.go` together to retain Hugo's development experience. If the user installs the [air](https://github.com/cosmtrek/air) utility, they will also get hot reloading of `server.go` when the file is saved.
- `bin/build`: This builds your entire Hugo site and API server as a single fat binary. Yes, this is not a "static" site, but it is a Hugo site, with the added benefit of a dynamic API for building richer Hugo-based web applications.

The output from `bin/build` is your entire Hugo site embedded in a single binary. It can be deployed as a single file, cross-compiled to any system architecture and operating system supported by Go, and run anywhere that you can run applications.

## I hope you can see that this is not crazy

While I hope you can see that this is not _entirely_ crazy, I don't blame you if you do. I've certainly found a use for this frakensite pattern, and I think you might as well.

It should also be said that while I've used `go` as the language to implement the api server, this pattern is in no way limited by language. Use whatever you want; everything will be fine.

Enjoy!

