Reconnection Recovery

State fields tagged lvt:"persist" survive WebSocket disconnects, server restarts, and full page reloads — the framework restores them from the session store via the group cookie before the first render after reconnect. The counter and notes here are both persisted; the notes textarea also uses lvt-form:preserve so in-progress typing isn't lost when another action re-renders the page.

Reconnection Recovery

State fields tagged lvt:"persist" survive WebSocket disconnects, server restarts, and full page reloads — the framework restores them from the session store via the group cookie before the first render after reconnect.

Counter: 0 (lvt:"persist")

Try: Increment a few times and save some notes, then reload the page. State is restored before the first render. Why both tags on Notes? lvt:"persist" (server-side) survives reconnect. lvt-form:preserve (client-side) keeps your in-progress text in the DOM across re-renders that other actions trigger — without it, typing while the Counter is incrementing would lose the textarea draft.

Template

A persisted counter and a persisted notes textarea — reload the page and both come back.

{{define "content"}}
<article>
    <h3>Reconnection Recovery</h3>
    <p><small>State fields tagged <code>lvt:"persist"</code> survive WebSocket disconnects, server restarts, and full page reloads — the framework restores them from the session store via the group cookie before the first render after reconnect.</small></p>

    <p>Counter: <strong>{{.Counter}}</strong> <small>(<code>lvt:"persist"</code>)</small></p>
    <form method="POST">
        <button name="increment">Increment</button>
    </form>

    <form method="POST" lvt-form:preserve>
        <label>
            Notes <small>(<code>lvt:"persist"</code> + <code>lvt-form:preserve</code>)</small>
            <textarea name="notes" rows="3" placeholder="Type something…">{{.Notes}}</textarea>
        </label>
        <button name="saveNotes">Save Notes</button>
    </form>

    <p><small><strong>Try:</strong> Increment a few times and save some notes, then reload the page. State is restored before the first render. <strong>Why both tags on Notes?</strong> <code>lvt:"persist"</code> (server-side) survives reconnect. <code>lvt-form:preserve</code> (client-side) keeps your in-progress text in the DOM across re-renders that other actions trigger — without it, typing while the Counter is incrementing would lose the textarea draft.</small></p>
</article>
{{end}}

reconnection.tmpl

Handler & state

The actions are ordinary mutations; persistence comes entirely from the lvt:"persist" struct tags.

type ReconnectionController struct{}

func (c *ReconnectionController) Increment(state ReconnectionState, ctx *livetemplate.Context) (ReconnectionState, error) {
	state.Counter++
	return state, nil
}

func (c *ReconnectionController) SaveNotes(state ReconnectionState, ctx *livetemplate.Context) (ReconnectionState, error) {
	// Notes is a free-form textarea — leading/trailing whitespace AND
	// internal newlines are deliberate user content. Unlike Send/Join
	// inputs (which use TrimSpace to reject all-whitespace submissions),
	// SaveNotes preserves whatever the user typed verbatim.
	state.Notes = ctx.GetString("notes")
	return state, nil
}

func reconnectionHandler() http.Handler {
	tmpl := newLayoutTmpl("templates/layout.tmpl", "templates/realtime/reconnection.tmpl")
	return tmpl.Handle(&ReconnectionController{}, livetemplate.AsState(&ReconnectionState{
		Title:    "Reconnection Recovery",
		Category: "Real-Time & Multi-User",
	}))
}

handlers_realtime.go:235-258

type ReconnectionState struct {
	Title    string
	Category string
	Counter  int    `lvt:"persist"`
	Notes    string `lvt:"persist"`
}

state_realtime.go:48-54

When to use

For per-connection state that should not persist across tabs, see Pubsub.

source: livetemplate/docs · path: examples/patterns/templates/realtime/reconnection.tmpl