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.jsGO(A)HT
JSX componentsGo html/template partials
useEffect + fetchHTMX requests that return HTML
useState for server dataServer state and rendered fragments
SPA navigationnet/http routes first, HTMX swaps only for partial updates
Component propsTemplate data
Client-side re-renderingServer-side rendering
Local UI stateAlpine 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

  1. Start with one page that already renders well on the server.
  2. Replace client-driven fetch/render loops with HTML responses.
  3. Add Alpine only when you need local UI state, not as a React substitute.
  4. Keep the server responsible for the interesting state transitions.