Routing

The router maps URL paths to components and keeps navigation in sync with browser history. Both /docs and /docs/ resolve to the same component, trailing slashes don’t matter.


Registering Routes

router.Page()

The preferred API. Registers a single route with a path, component, and optional guards:

router.Page("/", func() *types.View {
    return composition.New(&Home{})
})

router.Page accepts the same component forms as Route.Component (see below).

router.Group()

Creates nested routes under a common path prefix:

router.Group("/admin", func(r *router.GroupBuilder) {
    r.Page("/dashboard", func() *types.View {
        return composition.New(&Dashboard{})
    })
    r.Page("/settings", func() *types.View {
        return composition.New(&Settings{})
    })
})

This registers /admin/dashboard and /admin/settings.

router.Singleton()

Wraps a pre-created *types.View so every navigation reuses the same instance:

home := composition.New(&Home{})
router.Page("/", router.Singleton(home))

Use singletons when a component is stateless or you want to preserve state across navigations.

router.RegisterRoute()

The low-level API, still available but Page() is preferred:

router.RegisterRoute(router.Route{
    Path:      "/",
    Component: func() *types.View { return composition.New(&Home{}) },
})

Component Forms

Route.Component (and the second argument to Page()) accepts three forms:

Form Behavior
func() *types.View Factory, called each navigation for a fresh instance
*types.View Singleton, the same instance is reused every navigation
// Fresh instance each navigation
router.Page("/items", func() *types.View {
    return composition.New(&ItemList{})
})

// Singleton, same instance reused
view := composition.New(&Layout{})
router.Page("/", router.Singleton(view))

Path Parameters

Use :name segments to capture dynamic parts of the path:

router.Page("/users/:id", func() *types.View {
    return composition.New(&UserProfile{})
})

When navigating to /users/42, the component receives map[string]string{"id": "42"} via SetRouteParams. Access params in OnMount:

type UserProfile struct {
    composition.Component
}

func (u *UserProfile) OnMount() {
    params := u.HTMLComponent.RouteParams()
    id := params["id"]
    // fetch user by id...
}

Query parameters are merged into the same params map.


Guards

Guards control whether navigation is allowed. Pass them as variadic arguments to Page():

func requireAuth(params map[string]string) bool {
    return session.IsAuthenticated()
}

router.Page("/dashboard", func() *types.View {
    return composition.New(&Dashboard{})
}, requireAuth)

If any guard returns false, navigation is blocked. If the current component is nil, the router falls back to /.


Not Found

Set NotFoundComponent for unmatched paths:

router.NotFoundComponent = func() *types.View {
    return composition.New(&NotFound{})
}

Accepts the same component forms as Route.Component.


Programmatic Navigation

router.Navigate()

router.Navigate("/users/42")

Renders the matching component, pushes browser history, and passes path/query params.

router.ExposeNavigate()

Makes Navigate available from JavaScript and intercepts internal <a> clicks:

router.ExposeNavigate()

After calling ExposeNavigate, clicking <a href="/about"> triggers client-side navigation instead of a full page load, provided the path matches a registered route.


router.CanNavigate()

Reports whether a path matches a registered route, useful for pre-checking before navigation:

if router.CanNavigate("/secret") {
    router.Navigate("/secret")
}

router.InitRouter()

Call once at startup to begin listening for popstate events and navigate to the current URL:

func main() {
    router.Page("/", func() *types.View {
        return composition.New(&Home{})
    })
    router.InitRouter()
    select {}
}

Full Example

//go:build js && wasm

package main

import (
    "embed"

    "github.com/rfwlab/rfw/v2/composition"
    "github.com/rfwlab/rfw/v2/router"
    "github.com/rfwlab/rfw/v2/types"
)

//go:embed Home.rtml About.rtml
var fs embed.FS

func init() {
    composition.RegisterFS(&fs)
}

type Home struct{ composition.Component }
type About struct{ composition.Component }

func requireAuth(params map[string]string) bool {
    return true
}

func main() {
    router.Page("/", func() *types.View {
        return composition.New(&Home{})
    })

    router.Group("/app", func(r *router.GroupBuilder) {
        r.Page("/about", func() *types.View {
            return composition.New(&About{})
        }, requireAuth)
    })

    router.NotFoundComponent = func() *types.View {
        return composition.New(&Home{})
    }

    router.ExposeNavigate()
    router.InitRouter()
    select {}
}