September 22, 2024

Go templates with Astro

How to use Golang templates with Astro to create dynamic content and reusable components in your Astro projects

Daniel Berigoi

Daniel

Go templates with Astro

Image source: Pexels

Introduction

Astro is a modern static site generator that allows you to build fast, modern websites using a component-based architecture. More details about Astro at https://astro.build

Go templates are a powerful tool for creating dynamic content and reusable components in your Go applications. More details about Go templates at https://pkg.go.dev/text/template

At the point of writing this article, Astro does not support Golang as a SSR adapter. So we will use use Go templates to inject dynamic content in your Astro projects by serving the static files generated by Astro with a Go server.

It doesn’t even have to be Astro - any static site generator will work - but we will focus on Astro in this article as it’s our favorite.

Prerequisites

Before you begin, make sure you have the following prerequisites:

Setup

Initialize a new Go project:

go mod init github.com/wingravity/go-astro

Create a new Go file named main.go:

package main

import (
	"log"
	"net/http"
)

func main() {
    app := NewRouter()
    tmp := NewTemplatesHandler()

    app.mux.Handle("GET /", http.HandlerFunc(tmp.IndexView))

    log.Fatal(app.ListenAndServe(":8080"))
}

Here we have a simple Go program that initializes a new router and a templates handler.
The router listens on port 8080.

Let’s create the router and templates handler in a new Go file named router.go:

package main

import (
    "net/http"
	"text/template"
)

type Router struct {
	mux *http.ServeMux
}

func NewRouter() *Router {
	return &Router{
		mux: http.NewServeMux(),
	}
}

func (r *Router) ListenAndServe(address string) error {
	return http.ListenAndServe(address, r.mux)
}

type ViewsHandler struct {
	Index *template.Template
}

func NewTemplatesHandler() *ViewsHandler {
	return &ViewsHandler{
        // IMPORTANT: We specify custom delimiters for Go templates,
        //            so they don't conflict with Astro's delimiters {{ and }}
		Index: template.Must(template.New("index").Delims("[[", "]]").ParseFiles("client/dist/index.html")),
	}
}

type IndexParams struct {
	Name string
}

func (h *ViewsHandler) IndexView(w http.ResponseWriter, r *http.Request) {
	if r.URL.Path == "/" {
		h.Index.ExecuteTemplate(w, "index.html", IndexParams{Name: "Go Astro"})
		return
	}
	http.ServeFile(w, r, "client/dist"+r.URL.Path)
}

The IndexView method in the ViewsHandler struct renders the index.html template with the name “Go Astro”. Here, we can inject dynamic content into the Astro-generated static files. This can be any data you want to pass to the template.

We also specify custom delimiters for Go templates so they don’t conflict with Astro’s delimiters.

Now let’s add the client files generated by Astro to the project. Let’s initialize a new Astro project:

npm create astro@latest

Put the Astro project in the client directory:

  • Where should we create your new project? ./client

Respond to the other questions as you see fit.

What we’re interested in is the index page, as this is the one we will be injecting dynamic content into.

Create a new file named index.astro under client/src/pages and add the following content (you can remove the existing content):

---
import { Image } from "astro:assets";
import logo from "../astro-logo-dark.png";
---

<html lang="en">
	<head>
		<meta charset="utf-8" />
		<link rel="icon" type="image/svg+xml" href="/favicon.svg" />
		<meta name="viewport" content="width=device-width" />
		<meta name="generator" content={Astro.generator} />
		<title>Astro</title>
	</head>
	<body>
		<Image src={logo} alt="LOGO" width={200} />
        <!-- This is where the dynamic content will be injected. -->
		<h1>Hello [[.Name]]</h1>
	</body>
</html>

There are 2 important things to note here:

  1. We use [[.Name]] as the template variable for the name. This is because we have specified custom delimiters for Go templates in the router.go file.
  2. We left in the Astro logo on purpose. This is because we also need to serve the static assets generated by Astro with the Go server.

Now we need to build the Astro project:

cd client
npm run build

This will generate the static files in the client/dist directory.

Now we need to run the Go server:

go run .

Open your browser and navigate to https://localhost:8080. You should see the Astro logo and the text “Hello Go Astro”.

localhost:8080

This experiment is available as a GitHub repository for you to clone and play around with.

Conclusion

This method introduces some dynamism to your Astro projects and can be expanded to include components, similar to the GoTH stack. You also have full flexibility on how to handle routing, data fetching and other server-side logic. However, it also comes with limitations: you won’t be able to use Astro’s built-in features like partial hydration or server-side rendering, lose file-based routing and other Astro-specific features. But if you’re looking for a way to inject dynamic content into your Astro projects, this is a good starting point.

Follow us