Back to newsletters
Wednesday, April 2, 2025
#3

Rethinking SaaS Stacks: Why I’m Trying Go Instead of NextJS

Rethinking SaaS Stacks: Why I’m Trying Go Instead of NextJS

The go stack header

Hey friends,

This past week, I found myself tinkering with an idea that’s been sitting in the back of my mind for a while:

"What if I tried building a SaaS app in Go instead of NextJS?"

Let’s be clear, it’s not the most “productive” move.

I’ve spent the past couple of years pretty deep in the NextJS/TypeScript ecosystem, and I’m comfortable there.

But sometimes curiosity wins. And honestly? I like showing what it’s like to start something from scratch, especially when it means challenging your defaults.

So this is me exploring the idea of a new stack, something called the GOAT stack: Go + AlpineJS + Templ.

Let me walk you through the thinking.

Why Golang?

First, Golang (created by Google) was designed to solve specific issues Google encountered in other programming languages while building their services.

As a result, Go is actively maintained, frequently updated, and rapidly growing.

Key advantages:

  • Compiled & Fast: Compiles quickly into efficient binaries, resulting in fast startup times, better scalability, easy Dockerization, and improved SEO from quicker page loads.

  • Strongly Typed: Typed languages introduce friction, but greatly enhance readability, maintainability, and integration with AI coding assistants.

  • Clear Syntax: Simple and readable code.

  • Efficient Concurrency: Easy implementation of concurrent tasks and asynchronous functions.

Additionally, as someone with a backend-focused background, I've found server-side rendering with Go simpler and lighter than with NextJS.

The GOAT Stack

The GOAT stack leans into that clarity.

  • Go handles the backend and rendering

  • Templ is a type-safe HTML templating engine built for Go

  • AlpineJS adds just enough interactivity on the frontend without dragging in a huge JS framework

In other words, it’s intentionally not a React-based SPA. It’s backend-first, minimal, and refreshingly old-school, but modernized in the right places.

That might not be everyone’s cup of tea.

But I wanted to see how far you can get building a SaaS without leaning on the usual React + Next + client-heavy stack.

Templ: Type-safe templates with Go

Templ lets you write HTML using Go syntax.

Think reusable components, logic in templates, and compile-time safety. It’s elegant, especially if you’re already in the Go mindset.

One of the things I missed in Go was a first-class way to render views. Templ fills that gap perfectly.

AlpineJS: Just enough frontend

Sometimes you just need a button that toggles a div. Or a form that shows a loader. Not everything needs React, state management libraries, and hydration strategies.

AlpineJS is like modern-day jQuery, but much more elegant. It lets you interact in your HTML with declarative syntax. That’s a perfect fit for server-rendered pages.

Example AlpineJS Usage:

<div x-data="{ open: false }">       
    <button @click="open = !open">Toggle</button>       
    <div x-show="open">Content...</div>   
</div>

goat stack tweet

Setting it up

Here's a concise setup guide:

1. Prerequisites

To set up your development environment for the GOAT stack, you'll need to install Go, Templ, and Make. Here are the official sources where you can find installation instructions for each:

  • Go Installation Guide: https://go.dev/doc/install
  • Templ Installation Guide: https://templ.guide/quick-start/installation/
  • Make Installation Guide for MacOS: brew install make

2. Initialize Your Project

mkdir goat-project
cd goat-project
export GO_MODULE_PATH="github.com/yourusername/goat-project"
go mod init ${GO_MODULE_PATH}

3. Install Templ

go get github.com/a-h/templ

4. Project Structure

Organize your directories:

goat-project/
├── .gitignore
├── .env
├── go.mod
├── main.go
├── Makefile
├── internal/
│   ├── api/
│   ├── components/
│   ├── config/
│   ├── db/
│   ├── handlers/
│   ├── models/
│   └── pages/
│       ├── home.templ
│       └── layout.templ
├── secure/
└── static/

5. Makefile

Simplify build, run, and development tasks:

.PHONY: build run clean

build:
	templ generate
	go build -o bin/main .

run: build
	./bin/main

dev:
	templ generate --watch --cmd="go run main.go"

clean:
	rm -rf bin/
	rm -f *_templ.go

6. Templ Layout Example

Create layout.templ in internal/pages:

package pages

templ Layout(children ...templ.Component) {
	<!DOCTYPE html>
	<html lang="en">
	<head>
		<meta charset="UTF-8"/>
		<meta name="viewport" content="width=device-width, initial-scale=1"/>
		<title>GOAT Project</title>
		<script src="https://unpkg.com/htmx.org@1.9.10"></script>
		<script defer src="https://unpkg.com/alpinejs@3.x.x/dist/cdn.min.js"></script>
	</head>
	<body>
		<div class="pt-16">
			for _, child := range children {
				@child
			}
		</div>
	</body>
	</html>
}

7. Basic Page (home.templ)

package pages

templ main() {
	<div x-data="{ open: false }">       
	    <button @click="open = !open">Toggle</button>       
	    <div x-show="open">Hello World</div>   
	</div>
}

templ Home() {
	@Layout(main())
}

8. Your Go Server (main.go)

package main

import (
	"log"
	"net/http"
	"os"

	"github.com/yourusername/goat-project/internal/pages"
	"github.com/a-h/templ"
)

func main() {
	log.Println("Starting server...")
	homePage := pages.Home()

	mux := http.NewServeMux()
	mux.Handle("/", templ.Handler(homePage))

	port := os.Getenv("PORT")
	if port == "" {
		port = "5050"
		log.Printf("Defaulting to port %s", port)
	}

	log.Printf("Server starting on port %s", port)
	log.Fatal(http.ListenAndServe(":"+port, mux))
}

9. Running Your Project

Start the development server:

make dev

Visit http://localhost:5050 to see your page. Click on the button you should see the “Hello Word" text appear.

This guide provides a strong foundation for building SaaS apps with Go, AlpineJS, and Templ. Happy coding!

See you next week !