Modern Web Development with Go: A Lightweight Alternative to React SSR
In recent years, the web development landscape has significantly evolved, especially with React’s server-side rendering (SSR) capabilities. However, for developers using non-JavaScript backend languages like Go, this presents a unique challenge. This article explores a modern, efficient approach to web development that combines the best of both worlds.
The Challenge with React SSR
React’s server components are revolutionary but come with a caveat: they require a Node.js runtime. For teams using Go or other non-JavaScript backends, this means:
- Running separate servers for frontend and backend
- Increased infrastructure complexity
- Higher deployment costs
- Limited serverless options
- Larger deployment sizes
A Modern Alternative
After extensive research and experimentation, we’ve developed a lightweight stack that provides similar capabilities without the overhead:
- Templ: For server-side rendering in Go
- HTMX: For dynamic server interactions
- Petite Vue (6kb): For client-side reactivity
- Go’s embed: For static asset management
Why This Stack?
- Single Codebase: Everything lives in your Go application
- Minimal JavaScript: Only 6kb of client-side JS with Petite Vue
- Tiny Deployment Size: Around 19MB for a complete application
- Development Simplicity: Node.js needed only in development
- Enterprise-Ready: Perfect for companies prioritizing efficiency
How It Works
Architecture Overview
Our architecture is designed to be modular and efficient:
This architecture allows us to maintain a clean separation of concerns while keeping everything in a single, manageable codebase. The main application orchestrates different components, including REST APIs, gRPC services, and web applications, all sharing the same runtime.
Rendering Strategy
Our approach to rendering is optimized for both performance and developer experience:
This rendering strategy provides:
- Full-page loads with server-side rendering for initial requests
- Partial updates through HTMX for dynamic content
- Lightweight client-side reactivity with Petite Vue
- Minimal JavaScript footprint (only 6kb)
1. Server-Side Rendering with Templ
// Dashboard component example
templ Dashboard(account models.Account) {
@Layout("Dashboard") {
@templ.JSONScript("account-data", account)
<div class="grid grid-cols-2 gap-6">
<div id="account-widget" v-cloak v-scope="accountWidget()" @vue:mounted="init">
<div class="bg-white rounded-lg shadow-lg p-6" v-if="account">
<h3 class="text-xl font-semibold text-gray-900 mb-4">Account Balance</h3>
<div class="text-3xl font-bold text-gray-900 mb-6">{{ formatAmount(account.balance) }}</div>
// ... template continues ...
</div>
</div>
</div>
}
}
2. Dynamic Interactions with HTMX and Petite Vue Combined
<div id="transaction-list" v-cloak v-scope="transactionList()" @vue:mounted="init">
<div class="bg-white rounded-lg shadow-lg p-6">
<div v-if="loading" class="py-3 text-gray-600">
Loading...
</div>
<div v-else class="border-t border-gray-200 pt-4">
<div v-for="tx in transactions" class="flex justify-between items-center py-3">
<span class="text-gray-600">{{ tx.description }}</span>
<span :class="['font-medium', tx.amount < 0 ? 'text-red-600' : 'text-green-600']">
{{ formatAmount(tx.amount) }}
</span>
</div>
</div>
</div>
</div>
Benefits for Enterprise
1. Cost Efficiency
- Single server deployment
- Reduced infrastructure complexity
- Lower maintenance overhead
2. Performance
- Minimal client-side JavaScript
- Efficient server-side rendering
- Small deployment size (≈19MB)
3. Developer Experience
- Single language (Go) for backend logic
- Clear separation of concerns
- Familiar templating patterns
4. Security
- Reduced attack surface
- Server-side validation
- Built-in CSRF protection
When to Use This Approach?
This stack is particularly suitable for:
- Enterprise applications requiring high performance
- Teams with Go expertise
- Projects needing quick time-to-market
- Applications with security constraints
- Systems requiring minimal deployment footprint
Getting Started
The project structure follows a clean, modular approach:
webstack/
├── api/
│ ├── web/ # Web application
│ │ ├── handlers/ # HTTP handlers
│ │ ├── templates/ # Templ templates
│ │ └── static/ # Static assets
│ ├── rest/ # REST API endpoints
│ └── rpc/ # gRPC services
├── internal/ # Internal packages
└── cmd/ # Application entrypoints
Development and Deployment
Development Environment
We use Docker for development to ensure consistency. Here’s our development Dockerfile:
FROM golang:1.23-alpine
WORKDIR /app
COPY go.mod go.sum ./
RUN --mount=type=cache,target=/root/.cache/go-build \
--mount=type=cache,target=/go/pkg/mod \
--mount=type=cache,target=/go/pkg/sumdb \
go mod download
COPY . .
RUN go install github.com/a-h/templ/cmd/templ@latest
RUN go install github.com/air-verse/air@latest
CMD ["air", "-c", ".air.toml"]
Production Deployment
Our production deployment uses a multi-stage build process:
- Node.js Stage: Builds frontend assets
- Go Build Stage: Compiles the application
- Final Stage: Creates a minimal production image
# Node Stage
FROM node:20.11.1-slim AS node_builder
WORKDIR /
COPY ./api/web ./
RUN npm install
RUN npm run build
# Go Stage
FROM golang:1.23-alpine AS builder
RUN apk update && apk add --no-cache ca-certificates
ENV CGO_ENABLED=0 GO111MODULE=on GOOS=linux
WORKDIR /
COPY go.* ./
RUN --mount=type=cache,target=/go/pkg/mod \
go mod download
COPY . .
# install templ
RUN go install github.com/a-h/templ/cmd/templ@latest
RUN templ generate ./api/web/templates/
COPY ./api/web/templates ./api/web/templates
RUN --mount=type=cache,target=/go/pkg/mod \
--mount=type=cache,target=/root/.cache/go-build \
go build -o main ./cmd/main.go
# Final Stage
FROM scratch
ENV HTTP_PORT=8080
ENV RPC_PORT=9090
COPY --from=builder /etc/ssl/certs/ca-certificates.crt /etc/ssl/certs/
COPY --from=builder /main .
COPY --from=node_builder /static/dist /api/web/static/dist
COPY --from=node_builder /templates /api/web/templates
EXPOSE $HTTP_PORT $RPC_PORT
CMD ["/main"]
This approach results in a tiny production image (≈19MB) containing only the necessary components for running the application.
Conclusion
This modern approach to web development offers a compelling alternative to React SSR for Go-based teams. It provides all the benefits of modern web development — reactivity, dynamic updates, and server-side rendering — while maintaining simplicity and efficiency.
The combination of Templ, HTMX, and Petite Vue creates a powerful, lightweight stack that’s particularly well-suited for enterprise applications where performance, security, and maintainability are crucial.
By embracing this approach, teams can build robust web applications with a fraction of the complexity and resource overhead typically associated with modern web development.