Project Doc

Stack Decisions

Stack choices and rationale

Updated: December 2025 Source: DECISIONS.md

Stack Decisions

Current stack choices for Forge applications. For rationale and history, see DECISION-LOG.md. For full architecture, see ARCHITECTURE.md.

Last updated: December 2025


The Opinion

"Forge apps use Go + HTMX for the core, Svelte islands for escape hatches. Server renders HTML. State lives on the server. Primitives over frameworks."

This is an opinionated toolchain. The opinion is the value. We pick technologies closest to browser primitives with the smallest surface area for AI to mess up.


Core Stack (80% of Application)

Layer Choice Size Why
Language Go 1.23+ 0kb client Fast, typed, single binary
Router net/http (stdlib) 0kb Zero dependencies
Templates Templ 0kb runtime Type-safe HTML
Interactivity HTMX 2.x ~14kb HTML-over-the-wire
Styling Tailwind CSS 4.0 ~10kb Utility CSS
Database PostgreSQL 16 N/A Proven, scalable
DB Access sqlc 0kb runtime Type-safe SQL
Real-time SSE (stdlib) 0kb Browser-native

Total client JS for core: ~25kb (HTMX + Tailwind)


Islands Stack (20% - When Needed)

Layer Choice Size Why
Framework Svelte 5 ~5-10kb/island Smallest runtime
Build Vite Dev only Standard tooling
Language TypeScript Dev only Type safety

Use islands ONLY for:

  • Drag and drop
  • Rich text editing
  • Complex data visualization
  • Interactive bracket builders
  • Gesture-based interactions

Infrastructure

Layer Choice Why
Hosting Fly.io Go-native, edge deploys
Database Supabase or Neon Managed Postgres
CDN Cloudflare Free, edge caching
Auth Session-based Simple, secure

What Changed from Original Plan

Original (Dec 21) New (Dec 21) Why Changed
SvelteKit full-stack Go + HTMX Closer to primitives, simpler AI patterns
Svelte everywhere Svelte islands only 80% doesn't need reactive framework
JSON APIs HTML responses HTML is universal
Vercel Fly.io Better Go support
Drizzle ORM sqlc SQL-first, no abstraction
Supabase client Postgres direct Fewer moving parts

See DECISION-LOG.md for full rationale.


AI Friendliness Scores

Stack Score Reasoning
Go + HTMX A HTTP handlers return HTML. No client state.
Phoenix LiveView A No client state, typed
SvelteKit B+ Simpler than React but has client reactivity
Next.js B Hooks, effects, hydration complexity
React SPA C AI struggles with hooks/effects

Go + HTMX is the simplest pattern for AI: request → handler → HTML → browser.


Island Decision Matrix

Need Use HTMX? Use Island?
Form submission Yes No
Live updates Yes (SSE) No
Pagination Yes No
Modals Yes No
Tabs/accordions Yes No
Search autocomplete Yes No
Drag and drop No Yes
Rich text editing No Yes
Complex charts No Yes
Interactive brackets No Yes

Database Access

Decision: sqlc (not an ORM)

Evaluated Verdict
Prisma Heavy, requires codegen, TypeScript-centric
Drizzle Good, but still an abstraction layer
GORM Magic, not SQL-first
sqlc Selected — write SQL, get typed Go

Rationale:

  • Write real SQL queries
  • sqlc generates type-safe Go code
  • No runtime overhead
  • AI can generate SQL more reliably than ORM syntax
-- queries.sql
-- name: GetMatch :one
SELECT * FROM matches WHERE id = $1;
// Generated by sqlc
func (q *Queries) GetMatch(ctx context.Context, id uuid.UUID) (Match, error)

Real-time Strategy

Decision: Server-Sent Events (SSE)

Evaluated Verdict
WebSockets Overkill for one-way updates
Polling Inefficient
SSE Selected — browser-native, simple

Rationale:

  • SSE is built into browsers (EventSource API)
  • Perfect for live scores, notifications
  • HTMX has native SSE support (hx-ext="sse")
  • Falls back gracefully
  • Simpler than WebSocket connection management

Deployment

Decision: Fly.io

Evaluated Verdict
Vercel Great for JS, not Go-native
Railway Good, more expensive
Render Good alternative
Fly.io Selected — Go-native, edge, simple

Rationale:

  • Single binary deploys
  • Edge locations worldwide
  • Built-in Postgres (or use Supabase)
  • Excellent Go support
  • Affordable at scale

The Complete Stack

Layer Choice Version/Size
Language Go 1.23+
Router net/http stdlib
Templates Templ latest
Interactivity HTMX 2.x (~14kb)
Styling Tailwind CSS 4.0 (~10kb)
Islands Svelte 5 ~5-10kb each
Database PostgreSQL 16
DB Access sqlc latest
Real-time SSE stdlib
Deploy Fly.io -

Key Hypotheses

ID Hypothesis Target
H1 Go + HTMX produces more reliable AI code >85% compile success
H2 HTML responses are simpler than JSON → JS → DOM Fewer bugs than SvelteKit
H3 Islands needed for <20% of features Audit after MVP
H4 SSE handles real-time without WebSockets <100ms latency
H5 sqlc is more AI-friendly than ORMs >90% correct queries
H6 Bundle stays under 50kb for most pages Measure at deploy

What We're NOT Using

Rejected Why
React/Next.js Hooks, effects, hydration complexity
Full SvelteKit Still has client state AI can mess up
Prisma/Drizzle ORMs add abstraction; sqlc is SQL-first
WebSockets SSE is simpler for our use case
Vercel Not Go-native
JWT auth Sessions are simpler

Re-evaluation Triggers

Revisit these decisions if:

  • HTMX limitation blocks a feature (escalate to island)
  • Go ecosystem gap blocks development
  • SSE proves insufficient (escalate to WebSockets)
  • Island usage exceeds 30% of features
  • Bundle size exceeds 100kb consistently

Until then, these decisions are final for Phase 1.


References