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 (
EventSourceAPI) - 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
- ARCHITECTURE.md — Full technical architecture
- DECISION-LOG.md — Chronological decision history
- RALLY-HQ.md — Application specification
- HTMX Documentation
- Templ Documentation
- sqlc Documentation