SPA Navigation

Every <a> inside the LiveTemplate wrapper is auto-intercepted, so ordinary links behave like a single-page app with no router and no client code. Same-pathname links (here, ?step=) route through the in-band __navigate__ action over the WebSocket and re-render in place; cross-pathname links fetch the new page and reconnect the socket transparently — both update history with pushState and neither does a hard reload. External links opt out with lvt-nav:no-intercept.

SPA Navigation

Every <a> link inside the LiveTemplate wrapper is auto-intercepted. Same-pathname links use the in-band WebSocket __navigate__ action; cross-pathname links fetch and reconnect transparently. External targets opt out with lvt-nav:no-intercept.

Same-pathname (in-band __navigate__)

Click these to update ?step= without an HTTP round-trip. The page does not reload — only this section re-renders.

Step 1 of 3.

Cross-pathname (WebSocket reconnect)

Each pattern is its own handler with its own data-lvt-id. Clicking these links triggers a fetch + DOM swap + WebSocket reconnect — without a hard page reload.

Opt-out (lvt-nav:no-intercept)

External links should opt out so the browser handles them natively.

example.com — opt-out via lvt-nav:no-intercept

Template

The included snippet shows the same-pathname case: ?step= links the framework routes over the WebSocket. The full template also demonstrates cross-pathname links (fetch + reconnect) and an lvt-nav:no-intercept external opt-out.

<section>
    <h4>Same-pathname (in-band <code>__navigate__</code>)</h4>
    <p>Click these to update <code>?step=</code> without an HTTP round-trip. The page does not reload &mdash; only this section re-renders.</p>
    <nav aria-label="Step">
        <ul>
            <li><a href="?step=1" {{if eq .Step 1}}aria-current="page"{{end}}>Step 1</a></li>
            <li><a href="?step=2" {{if eq .Step 2}}aria-current="page"{{end}}>Step 2</a></li>
            <li><a href="?step=3" {{if eq .Step 3}}aria-current="page"{{end}}>Step 3</a></li>
        </ul>
    </nav>
    <p><strong>Step {{.Step}} of 3</strong>.</p>
</section>

spa-navigation.tmpl:7-18

Handler & state

Mount reads and range-checks the step param; out-of-range or non-integer values fall back to step 1.

type SPANavController struct{}

const spaNavMaxStep = 3

func (c *SPANavController) Mount(state SPANavState, ctx *livetemplate.Context) (SPANavState, error) {
	if ctx.Action() == "" {
		// Out-of-range or non-integer step → fall through to the default below.
		if s := ctx.GetString("step"); s != "" {
			if n, err := strconv.Atoi(s); err == nil && n >= 1 && n <= spaNavMaxStep {
				state.Step = n
			}
		}
		if state.Step == 0 {
			state.Step = 1
		}
	}
	return state, nil
}

func spaNavigationHandler() http.Handler {
	tmpl := newLayoutTmpl("templates/layout.tmpl", "templates/navigation/spa-navigation.tmpl")
	return tmpl.Handle(&SPANavController{}, livetemplate.AsState(&SPANavState{
		Title:    "SPA Navigation",
		Category: "Dialogs, Tabs & Navigation",
	}))
}

handlers_navigation.go:135-161

type SPANavState struct {
	Title    string
	Category string
	Step     int
}

state_navigation.go:33-38

When to use

For a single group of in-band links rather than whole-page navigation, see Tabs (HATEOAS).

source: livetemplate/docs · path: examples/patterns/templates/navigation/spa-navigation.tmpl