Sites built with JavaScript frameworks like Next.js are often complicated and rely a lot on JavaScript for even primitive tasks like submitting form, state management, hydration etc.
Modern sites built with React tend to use JavaScript as a holistic language for serving, rendering, and routing in a website. The problem with this approach is that if you have your business logic in a different programming language you need to create more abstractions and APIs to use it within JavaScript or write the business logic in Node.js.
HTMX is a small (~14k minified and gzipped), dependency-free JavaScript library, it doesn't care about which language you use for your backend or business logic, which is a great thing, because now we can use our existing business logic code in Go to make a website.
You can use a Go templating language to generate HTML markup and use HTMX to put it in your website.
Templ is a tool which can be used to build HTML with Go. It includes templating-language like features but can really do more than that like — Server-side rendering, static rendering, use Go etc.
You can create a templ component by creating a .templ
file.
package main
templ hello(name string) {
<div>Hello, { name }</div>
}
Templ also comes with a CLI that can be used to convert the .templ
to a Go file which can be served using any http library.
- Create a new Go project:
mkdir hello-world
cd hello-world
go mod init github.com/<username>/<repository>
- Let's use the Go's standard
net/http
library to create a simple web server:
// main.go
package main
import (
"fmt"
"net/http"
)
func main() {
http.Handle("/", func(w http.ResponseWriter, _ *http.Request) {
io.WriteString(w, "Hello World! \n")
})
fmt.Println("Listening on :3000")
http.ListenAndServe(":3000", nil)
}
- To install the templ CLI you can simply use
go install
:
go install github.com/a-h/templ/cmd/templ@latest
- To Install templ in your project run:
go get github.com/a-h/templ
- Create a templ file
hello.templ
in the same directory asmain.go
// hello.templ
package main
templ hello(name string) {
<div>Hello, { name }</div>
}
- Generate Go code
templ generate
The generate
commands creates a hello_templ.go
files which has Go code.
The Go code will have a function called hello
that can be called with a name argument.
- Use templ with the web server
Import
templ
and replace the existing handler function with atempl.Handler
function
// main.go
// ...
import (
// ...
"github.com/a-h/templ"
)
func main() {
component := hello("Templ!")
http.Handle("/", templ.Handler(component))
fmt.Println("Listening on :3000")
http.ListenAndServe(":3000", nil)
}
- We first start by creating a
Layout.templ
file in the root directory:
// Layout.templ
package main
templ Layout() {
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<title>Title</title>
</head>
<body>
{ children... }
</body>
</html>
}
All of our pages will be wrapped by the Layout
component. We can use the template composition syntax in templ to achieve this. Notice the { children... }
.
- The recommended way to setup HTMX is to download a copy of minified HTMX from unpkg.com and include it in a script tag in the head of the
Layout.templ
file:
<!-- Layout.templ -->
<!-- ... -->
<head>
<meta charset="UTF-8">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<title>Title</title>
<!-- HTMX -->
<script src="../lib/htmx.min.js"></script>
</head>
<!-- ... -->
- We need to install and configure the tailwind compiler
npm install -D tailwindcss
npx tailwindcss init
- Configure tailwind for templ
/** @type {import('tailwindcss').Config} */
module.exports = {
content: ["./**/*.{templ}"],
theme: {
extend: {},
},
plugins: [],
}
Make sure to specify the path where your templ files are located.
- Create an
input.css
file in the root directory and add:
@tailwind base;
@tailwind components;
@tailwind utilities;
- Start the Tailwind CLI build process:
yarn tailwindcss -i input.css -o static/css/tw.css --watch
- Add a CSS file to your
layout.templ
<!-- Layout.templ -->
<!-- ... -->
<head>
<meta charset="UTF-8">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<link rel="stylesheet" href="/static/css/tw.css"/>
<title>Title</title>
<!-- HTMX -->
<script src="../lib/htmx.min.js"></script>
</head>
<!-- ... -->
We now have Templ, HTMX, and tailwind setup ready to use.
The setup works but is quite tedious to use at this point, it requires us to restart the Go server and re-run templ generate
every time we make a change in the Go code or the templ code.
We also need to refresh the browser every time to see the change.
There are a few options for watching file changes and re-running Go automatically like air and gow. Both of them are excellent tool and works for us. Let's use gow
to watch for changes and restart Go server:
gow run main.go -e go,mod,html,css
Templ's command line tool comes in with a built-in watcher which recompiles the templ
files to Go everytime there is a change:
templ generate -watch --include-timestamp false --include-version false
We don't need to re-run go
or templ
anymore but we still need to refresh the browser every time there is a change, we can use the templ
's command line tool's proxy for hot reloading the browser.
templ generate -watch -include-timestamp false -include-version false -proxy="http://localhost:3000" -proxyport="8080" -open-browser=false -cmd="go run main.go"
This command will start a proxy server to run on the port 8080
. You can visit localhost:8080 for development.
- There are no meta-frameworks
- Features like hot-live reload have to be manually setup
- The setup can be challenging compared to JS out of box meta frameworks
- The setup can be unreliable sometimes are require manual restarts
- Poor MDX support
- example - Resolving Filesystem request resolution needs a custom solution
- Go learning curve for me personally, not being aware of std lib
- State and interactivity Alpine.js