func Mount(mux Mux, page any, route, title string, options ...Option) (*StructPages, error)Mount is the main entry point for setting up routes. It parses the page structure, registers routes on the provided mux, and returns a StructPages instance for URL generation.
Parameters:
mux: HTTP router implementing theMuxinterface (e.g.,*http.ServeMux). Passnilto usehttp.DefaultServeMux.page: Struct containing route definitions using struct tagsroute: Base route path (e.g.,"/")title: Page title for the root routeoptions: Configuration options (WithArgs, WithErrorHandler, WithMiddlewares, etc.)
Returns:
*StructPages: Instance for generating type-safe URLs viaURLFor,ID, andIDTargeterror: Error if mounting fails
Example:
mux := http.NewServeMux()
sp, err := structpages.Mount(mux, &pages{}, "/", "Home")
if err != nil {
log.Fatal(err)
}
// Use mux for serving HTTP
http.ListenAndServe(":8080", mux)
// Use sp for generating URLs
url, _ := sp.URLFor(productPage{}, "123")type Mux interface {
Handle(pattern string, handler http.Handler)
}The Mux interface allows StructPages to work with any HTTP router that follows Go's standard routing pattern. *http.ServeMux implements this interface.
type StructPages struct {
// Internal fields
}Returned by Mount(), StructPages provides methods for type-safe URL generation.
func (sp *StructPages) URLFor(page any, args ...any) (string, error)Generate a URL for a page type with optional path parameters.
Example:
// Simple path
url, _ := sp.URLFor(homePage{}) // "/"
// With path parameter
url, _ := sp.URLFor(userPage{}, "123") // "/users/123"
// With named parameters
url, _ := sp.URLFor(postPage{}, "year", 2024, "slug", "hello") // "/blog/2024/hello"func (sp *StructPages) ID(v any) (string, error)
func (sp *StructPages) IDTarget(v any) (string, error)Generate consistent HTML IDs for component methods.
IDreturns raw ID (for HTMLidattributes):"todo-page-todo-list"IDTargetreturns CSS selector (for HTMXhx-target):"#todo-page-todo-list"
Example:
id, _ := sp.ID((*todoPage).TodoList) // "todo-page-todo-list"
target, _ := sp.IDTarget((*todoPage).TodoList) // "#todo-page-todo-list"Options are passed to Mount() to configure behavior.
func WithArgs(args ...any) func(*StructPages)Add global dependency injection arguments available to all page methods.
Example:
type Database struct { /* ... */ }
type Logger struct { /* ... */ }
db := &Database{}
logger := &Logger{}
// Pass dependencies using WithArgs
sp, err := structpages.Mount(mux, &pages{}, "/", "Home",
structpages.WithArgs(db, logger),
structpages.WithErrorHandler(errorHandler),
)Handler methods can receive injected dependencies:
func (p productPage) Props(r *http.Request, db *Database, logger *Logger) (ProductProps, error) {
// Use db and logger
}func WithErrorHandler(handler func(w http.ResponseWriter, r *http.Request, err error)) func(*StructPages)Set a custom error handler for handling errors during request processing.
Example:
errorHandler := func(w http.ResponseWriter, r *http.Request, err error) {
log.Printf("Error: %v", err)
http.Error(w, "Internal Server Error", http.StatusInternalServerError)
}
sp, err := structpages.Mount(mux, &pages{}, "/", "Home",
structpages.WithErrorHandler(errorHandler),
)func WithMiddlewares(middlewares ...MiddlewareFunc) func(*StructPages)Add global middleware that applies to all routes.
Example:
loggingMiddleware := func(next http.Handler, pn *PageNode) http.Handler {
return http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
log.Printf("%s %s", r.Method, r.URL.Path)
next.ServeHTTP(w, r)
})
}
sp, err := structpages.Mount(mux, &pages{}, "/", "Home",
structpages.WithMiddlewares(loggingMiddleware),
)func WithTargetSelector(selector TargetSelector) func(*StructPages)Set a global target selector function that determines which component to render. The selector returns a RenderTarget that is passed to your Props method, enabling conditional data loading and component selection.
The default selector is HTMXRenderTarget, which handles HTMX partial rendering automatically.
Example:
// Custom selector for API requests
selector := func(r *http.Request, pn *PageNode) (structpages.RenderTarget, error) {
if r.Header.Get("Accept") == "application/json" {
// Return a specific component for JSON responses
method := pn.Components["APIResponse"]
return structpages.NewMethodRenderTarget("APIResponse", method), nil
}
// Fall back to default HTMX behavior
return structpages.HTMXRenderTarget(r, pn)
}
sp, err := structpages.Mount(mux, &pages{}, "/", "Home",
structpages.WithTargetSelector(selector),
)func WithWarnEmptyRoute(warnFunc func(*PageNode)) func(*StructPages)Customize or suppress warnings for pages with no handler and no children.
Example:
// Use default warning (prints to stdout)
sp, err := structpages.Mount(mux, &pages{}, "/", "Home",
structpages.WithWarnEmptyRoute(nil),
)
// Custom warning function
customWarn := func(pn *PageNode) {
log.Printf("Skipping empty page: %s", pn.Name)
}
sp, err := structpages.Mount(mux, &pages{}, "/", "Home",
structpages.WithWarnEmptyRoute(customWarn),
)
// Suppress warnings entirely
sp, err := structpages.Mount(mux, &pages{}, "/", "Home",
structpages.WithWarnEmptyRoute(func(*PageNode) {}),
)Pages can implement several optional methods:
func (p PageType) Page() ComponentRequired for pages that render content. Returns the component to render.
func (p PageType) Props(r *http.Request, target RenderTarget, deps ...any) (any, error)Optional. Prepare data before rendering. Receives:
r *http.Request: The HTTP requesttarget RenderTarget: Indicates which component will be rendereddeps ...any: Injected dependencies (fromWithArgs)
Use target.Is(component) to conditionally load data based on which component is being rendered.
Example:
func (p DashboardPage) Props(r *http.Request, target RenderTarget, db *Database) (DashboardProps, error) {
switch {
case target.Is(p.UserList):
// Only load user data for partial update
users := db.LoadUsers()
return DashboardProps{}, RenderComponent(target, users)
case target.Is(p.Page):
// Load all data for full page
return DashboardProps{
Users: db.LoadUsers(),
Stats: db.LoadStats(),
}, nil
}
}
### ServeHTTP
```go
func (p PageType) ServeHTTP(w http.ResponseWriter, r *http.Request, deps ...any)Optional. Handle HTTP requests directly without component rendering.
func (p PageType) Middlewares() []MiddlewareFuncOptional. Return page-specific middleware.
For use within handlers:
func URLFor(ctx context.Context, page any, args ...any) (string, error)Generate URLs using context (available during request handling).
func ID(ctx context.Context, v any) (string, error)
func IDTarget(ctx context.Context, v any) (string, error)Generate IDs using context (available during request handling).
IDreturns raw ID (for HTMLidattributes)IDTargetreturns CSS selector (for HTMXhx-target)
The RenderTarget interface represents the component that will be rendered for a request. It's passed to your Props method, enabling conditional data loading.
type RenderTarget interface {
Is(method any) bool
}func (target RenderTarget) Is(method any) boolCheck if the target matches a specific component. Works with:
- Page methods:
target.Is(p.Page),target.Is(p.UserList) - Standalone functions:
target.Is(UserStatsWidget)(templ components that are functions)
Example:
func (p DashboardPage) Props(r *http.Request, target RenderTarget) (DashboardProps, error) {
// Check against page method
if target.Is(p.UserList) {
users := loadUsers()
return DashboardProps{}, RenderComponent(target, users)
}
// Check against standalone function component
if target.Is(UserStatsWidget) {
stats := loadUserStats()
return DashboardProps{}, RenderComponent(target, stats)
}
// Full page
return loadFullPageData(), nil
}func RenderComponent(targetOrMethod any, args ...any) errorOverride which component to render and pass specific arguments to it. Can be called from Props:
Same-page component:
// Render the component specified by target with custom args
return DashboardProps{}, RenderComponent(target, userData)Cross-page component:
// Render a component from another page using method expression
return DashboardProps{}, RenderComponent(OtherPage{}.Content, data)Standalone function:
// Render a standalone function component
return DashboardProps{}, RenderComponent(target, stats)func HTMXRenderTarget(r *http.Request, pn *PageNode) (RenderTarget, error)The default TargetSelector that handles HTMX partial rendering. It:
- Checks for
HX-Requestheader - Reads
HX-Targetheader value - Matches it to a component method or function
- Returns appropriate
RenderTarget
For non-HTMX requests or missing targets, returns the Page component.
Automatic behavior:
HX-Target: "content"→ matchesContent()methodHX-Target: "index-todo-list"→ matchesTodoList()method (strips page prefix)HX-Target: "dashboard-page-user-stats-widget"→ matchesUserStatsWidgetfunction
var ErrSkipPageRender = errors.New("skip page render")Return this error from Props to skip rendering (useful for redirects).
Example:
func (p loginPage) Props(r *http.Request) (any, error) {
if isAuthenticated(r) {
http.Redirect(w, r, "/dashboard", http.StatusSeeOther)
return nil, ErrSkipPageRender
}
return LoginProps{}, nil
}