Coming from React
A migration guide for React/Next.js developers moving to GO(A)HT.
If you’re coming from React or Next.js, the main migration is not finding the GO(A)HT version of React state. It is learning to ship HTML from the server and move that HTML around when the page changes.
The Core Shift
The server usually returns HTML directly. HTMX moves that HTML around. Alpine handles small local interactions when the page needs them.
func (h *Handler) Todos(w http.ResponseWriter, r *http.Request) {
todos, err := h.svc.ListTodos(r.Context())
if err != nil {
http.Error(w, err.Error(), http.StatusInternalServerError)
return
}
h.tmpl.ExecuteTemplate(w, "todos", todos)
}
That is the default shape of the app: data on the server, HTML in the response, browser on display duty.
What Replaces What
| React / Next.js | GO(A)HT |
|---|---|
| JSX components | Go html/template partials |
useEffect + fetch | HTMX requests that return HTML |
useState for server data | Server state and rendered fragments |
| SPA navigation | net/http routes first, HTMX swaps only for partial updates |
| Component props | Template data |
| Client-side re-rendering | Server-side rendering |
| Local UI state | Alpine x-data |
What To Keep In Mind
Most React state management is just a cache for server data. In GO(A)HT, the server stays the source of truth and the browser gets fresh HTML when something changes.
<!-- HTMX moves HTML fragments -->
<div
hx-get="/todos"
hx-trigger="refresh from:body"
hx-swap="innerHTML">
{{ template "todos" . }}
</div>
<!-- Alpine stays local -->
<div x-data="{ score: {{ .Score }} }">
<button @click="score++">+1</button>
<span x-text="score"></span>
</div>
Migration Strategy
- Start with one page that already renders well on the server.
- Replace client-driven fetch/render loops with HTML responses.
- Add Alpine only when you need local UI state, not as a React substitute.
- Keep the server responsible for the interesting state transitions.