Creating an Application
This guide walks through setting up an rfw v2 project, project layout, template embedding, root component, routing, and SSC configuration.
Project Layout
myapp/
├── main.go
├── components/
│ ├── home.go
│ ├── counter.go
│ └── templates/
│ ├── Home.rtml
│ └── Counter.rtml
├── static/
│ └── (images, styles, etc.)
└── rfw.json
components/, Go structs and thecompositionAPI. Templates live in atemplates/subdirectory (any depth works withgo:embed).
static/, assets copied intobuild/static/and served at/.
rfw.json, build and runtime configuration.
Embedding Templates
In v2, templates are resolved from embed.FS instances registered at init time. Place your .rtml files under a templates directory and use //go:embed:
package components
import (
_ "embed"
"embed"
"github.com/rfwlab/rfw/v2/composition"
)
//go:embed templates
var templates embed.FS
func init() {
composition.RegisterFS(&templates)
}
composition.RegisterFS adds the FS to a global resolver. When composition.New looks for a template, it searches all registered FS instances. Call RegisterFS in init() or at package level, it must run before any composition.New calls.
Main Entry Point
The entry point registers routes and starts the router:
package main
import (
"github.com/rfwlab/rfw/v2/router"
_ "github.com/username/myapp/components"
)
func main() {
homeView, _ := composition.NewFrom[components.Home]()
counterView, _ := composition.NewFrom[components.Counter]()
router.Page("/", homeView)
router.Page("/counter", counterView)
router.InitRouter()
select {}
}
router.Page
router.Page(path, component) is the v2 shorthand for registering a route. It accepts a *View, a func() *View, or a func() core.Component:
view, err := composition.New(&About{})
if err != nil {
log.Fatal(err)
}
router.Page("/", view) // singleton, same instance every visit
router.Page("/about", func() *composition.View {
v, _ := composition.New(&About{})
return v
})
router.Group
For nested routes under a common prefix:
profileView, _ := composition.NewFrom[Profile]()
securityView, _ := composition.NewFrom[Security]()
router.Group("/settings", func(g *router.GroupBuilder) {
g.Page("/profile", profileView)
g.Page("/security", securityView)
})
This registers /settings/profile and /settings/security.
Root Component
Components embed composition.Component and use typed fields for type-based auto-wiring. The template is found automatically by struct name:
//go:build js && wasm
package components
import (
"github.com/rfwlab/rfw/v2/composition"
"github.com/rfwlab/rfw/v2/types"
)
type Home struct {
composition.Component
Count *t.Int
Body *t.View
}
func (h *Home) OnMount() {
h.Count.Set(0)
}
func (h *Home) Increment() {
h.Count.Set(h.Count.Get() + 1)
}
The struct name Home resolves to Home.rtml (or Home.html) in any registered embed.FS. Override this by implementing the Template() string method:
func (h *Home) Template() string {
return "pages/index.rtml"
}
The corresponding template:
<root>
<h1>Home</h1>
<p>@signal:Count</p>
<button @on:click:Increment>+</button>
@include:content
</root>
Wiring Summary
| Field Type | Effect |
|---|---|
*t.Int, *t.String, *t.Bool, *t.Float, *t.Any |
Detected as reactive signals; nil fields are auto-initialized |
*t.Store |
Creates a component-scoped store by field name |
*t.Inject[T] |
Resolves type T from the DI container |
*t.View |
Slot for composition (slot name = lowercase field name) |
*t.Ref |
Template ref for DOM element access |
*t.History |
Wires undo/redo handlers |
t.HInt, t.HString, etc. |
Host signal types for server-side data |
t.Prop[T] |
Declared prop of type T |
Template() string method |
Overrides convention-based template resolution |
SSC Mode
rfw v2 uses Server-Side Composition (SSC), no SPA fallback. Configure it in rfw.json:
{
"mode": "ssc",
"entry": "main.go",
"output": "build"
}
With SSC, every navigation is a real page load processed by the server. The router handles client-side transitions for registered routes, but there is no client-side fallback for unmatched paths, the server must serve the correct HTML shell.
Next Steps
- Components Basics, typed fields, signals, includes, and lifecycle
- Composition, how
composition.Newworks under the hood
- Template Syntax, RTML directives and expressions